Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions .github/workflows/parity-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: parity-check

# Soft-enforcement reminder when a PR modifies a file tracked in
# parity-manifest.json. Posts a comment listing the touched tracked
# files and asks the author to cross-reference the private cueapi
# monorepo. NEVER blocks merge — exits 0 regardless. See HOSTED_ONLY.md
# for the open-core policy this enforces.

on:
pull_request:
types: [opened, synchronize, reopened]

permissions:
contents: read
pull-requests: write

jobs:
parity-check:
runs-on: ubuntu-latest
steps:
- name: Checkout PR head
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0

- name: Check if manifest exists
id: manifest_exists
run: |
if [ -f parity-manifest.json ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "::notice::parity-manifest.json not found — skipping parity check."
fi

- name: Compute touched tracked files
if: steps.manifest_exists.outputs.exists == 'true'
id: compute
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail

# Union of staged + PR diff
git diff --name-only "$BASE_SHA" "$HEAD_SHA" > /tmp/touched.txt || true

# Extract every tracked path from parity-manifest.json
# (flat list across all subdirectory groups)
python3 - <<'PY' > /tmp/tracked.txt
import json
with open("parity-manifest.json") as f:
manifest = json.load(f)
files = manifest.get("files", {})
if isinstance(files, list):
entries = files
else:
entries = []
for group in files.values():
entries.extend(group)
for e in entries:
print(e["path"])
PY

# Intersection — tracked files this PR touches
grep -Fxf /tmp/tracked.txt /tmp/touched.txt > /tmp/matches.txt || true

if [ -s /tmp/matches.txt ]; then
echo "has_matches=true" >> "$GITHUB_OUTPUT"
{
echo 'matches<<EOF'
cat /tmp/matches.txt
echo 'EOF'
} >> "$GITHUB_OUTPUT"
else
echo "has_matches=false" >> "$GITHUB_OUTPUT"
fi

- name: Post parity-check comment
if: steps.manifest_exists.outputs.exists == 'true' && steps.compute.outputs.has_matches == 'true'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
MATCHES: ${{ steps.compute.outputs.matches }}
with:
script: |
const matches = process.env.MATCHES.trim().split('\n').filter(Boolean);
const fileList = matches.map(f => `- \`${f}\``).join('\n');
const marker = '<!-- parity-check-comment -->';
const body = `${marker}
## Parity check

This PR modifies files tracked in [\`parity-manifest.json\`](../blob/main/parity-manifest.json):

${fileList}

Please confirm **one** of the following in a reply or PR description update:

1. **The equivalent change has been applied to the private cueapi monorepo.** Link the PR.
2. **This change is OSS-only and does not need porting.** Briefly explain why (e.g. "fixes a bug that only exists in the OSS build").
3. **A follow-up issue has been filed to port the reverse direction.** Link the issue.

This is a soft check — it does not block merge. The goal is visibility, not friction. See [HOSTED_ONLY.md](../blob/main/HOSTED_ONLY.md) for the open-core policy.`;

// Update-in-place: if we've commented before, edit instead of spamming
const {data: comments} = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}

- name: No tracked files touched
if: steps.manifest_exists.outputs.exists == 'true' && steps.compute.outputs.has_matches != 'true'
run: echo "::notice::No parity-tracked files modified in this PR."
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ All notable changes to cueapi-core will be documented here.
- **Dedup** — alerts collapse on `(user_id, alert_type, execution_id)` inside a 5-minute window.
- **Migrations 018 + 019** — alerts table with indexes and CHECK constraints; two columns on users.
- `examples/alert_webhook_receiver.py` — 30-line Flask receiver demonstrating signature verification.
- `HOSTED_ONLY.md` documenting the open-core policy — which features are OSS, which are intentionally hosted-only on cueapi.ai, and why.
- `parity-manifest.json` enumerating files that have a same-path counterpart in the private cueapi monorepo. Used by the new parity-check workflow.
- `.github/workflows/parity-check.yml` — soft-enforcement CI that posts a comment on PRs which touch tracked files, asking the author to cross-reference the private repo. Never blocks merge; exits 0 regardless.
- README "Open core model" section near the top, linking to `HOSTED_ONLY.md`.

### Changed
- **`POST /v1/executions/{id}/verify`** now accepts `{valid: bool, reason: str?}`. `valid=true` (default, preserving legacy behavior) transitions to `verified_success`; `valid=false` transitions to `verification_failed` and records the reason onto `evidence_summary` (truncated to 500 chars). Accepted starting states expanded to include `reported_failure`.
Expand Down
51 changes: 51 additions & 0 deletions HOSTED_ONLY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Hosted-only features

cueapi-core is the open-source primitive. [cueapi.ai](https://cueapi.ai) is the hosted product built on top. Some capabilities are intentionally hosted-only; this document explains which and why.

The line is drawn at: **scheduling + delivery + outcome reporting is OSS. Everything that's a SaaS business layer, a paid-API integration, or experimental product surface is hosted-only.** If you're self-hosting, you're running the same scheduling/delivery/outcome engine that powers cueapi.ai — no crippled OSS tier.

## Hosted-only capabilities

| Feature | Why hosted-only |
|---|---|
| Stripe billing | SaaS business layer. Self-hosters manage their own limits and don't need a payment processor. |
| GDPR endpoints (deletion, export, processing records) | Hosted-service compliance obligation. Self-hosters own their own legal surface and know which jurisdictions apply to them; a one-size policy baked into OSS would be wrong for most. |
| Blog content pipeline | Marketing infrastructure for cueapi.ai. Not a general-purpose feature. |
| Memory blocks | Product experiment not yet public. May graduate to OSS once the shape stabilizes. |
| Support tickets → GitHub issues routing | cueapi.ai customer-support automation. Self-hosters file issues directly on this repo. |
| Jenny docs chatbot | cueapi.ai-specific documentation UI. |
| Deploy hook (Railway staging) | cueapi.ai CI/CD infrastructure. |
| Dashboard (React UI) | Hosted-only. cueapi-core is API-first — build your own UI, or use the hosted dashboard at cueapi.ai. |
| Email alert delivery (SendGrid) | Paid-API integration. OSS ships **webhook-based** alert delivery instead: configure an `alert_webhook_url` on your user and forward alerts to your own Slack/Discord/ntfy/SMTP-relay pipeline. See README's "Alerts" section. |

## What's in cueapi-core

Everything needed to run a production scheduler with outcome tracking:

- Cue CRUD, scheduling, cron parsing, timezone handling
- Execution lifecycle, worker transport, webhook transport, heartbeats, replays
- Outcome reporting with verification modes and evidence
- Webhook HMAC signing, SSRF protection, retry-with-backoff
- Alert firing (via webhook delivery; add your own `alert_webhook_url`)
- API keys, device-code auth, session refresh, rate limiting
- At-least-once delivery via transactional outbox

Full feature list: see [README.md](README.md).

## Contributing a port

If you need a hosted-only feature in cueapi-core, open a GitHub issue with:

1. Your use case (what you're building, what breaks without it)
2. A rough idea of the OSS-compatible design (e.g. "swap SendGrid for a pluggable `AlertDeliveryBackend` interface")
3. Whether you'd be willing to submit the PR yourself

Community-driven ports are welcome. The hosted-only list is not permanent — features may move to OSS over time based on demand and on whether a self-hostable design exists.

## Maintainer note

If you're a cueapi maintainer porting a private-monorepo change:

- Check [`parity-manifest.json`](parity-manifest.json) to see whether the file you're touching has an OSS counterpart.
- The `parity-check` GitHub Action posts a soft warning on PRs that modify tracked files, prompting you to link the OSS PR (or file a follow-up issue).
- See the private monorepo's internal docs for the reverse direction — what to sync when OSS changes first.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@

The open source server for CueAPI. Run it yourself with Docker. Hosted at [cueapi.ai](https://cueapi.ai).

## Open core model

cueapi-core is the scheduling + delivery + outcome-tracking engine. Hosted cueapi.ai adds a dashboard, managed email alerts, billing, and a few other SaaS-business-layer features — see [HOSTED_ONLY.md](HOSTED_ONLY.md) for the full list and reasoning. Nothing in the OSS scheduler is crippled; what's here is what runs in production.

If you want a hosted-only feature ported to OSS, [open an issue](https://github.com/cueapi/cueapi-core/issues/new) — see the "Contributing a port" section in [HOSTED_ONLY.md](HOSTED_ONLY.md).

---

## The problem with cron
Expand Down
Loading
Loading