diff --git a/alembic/versions/035_agent_live_sessions_ipc_attachment.py b/alembic/versions/035_agent_live_sessions_ipc_attachment.py index b15d2ac..89a82e8 100644 --- a/alembic/versions/035_agent_live_sessions_ipc_attachment.py +++ b/alembic/versions/035_agent_live_sessions_ipc_attachment.py @@ -44,6 +44,15 @@ machinery in Phase 1. Sync-inline-ack-3s alternative deferred to a future Backlog row (meeting-room-style live agent discussions). +NOTE on forward-reference (annotated 2026-05-19 by cueapi-secondary during +Surface 6 audit, msg_410i80bctfox): the ``delivery_mode_requested='ipc'`` +value referenced above is a forward-reference. The ``messages.delivery_mode_requested`` +column does NOT exist yet in cueapi-core — it lands when Surface 6 +(cueapi-hosted alembic 050a equivalent) is ported. Until then, the ASYNC +fire-accept dispatcher path is dormant; no message-create code path produces +``delivery_mode_requested='ipc'``. See parity-manifest.json → +``pending_ports.surface_6_delivery_mode`` for the port sequencing. + Backwards-compat: all v2.x rows inherit ``transport='poll'`` + NULL on the other 3 columns. Existing fire-accept dispatcher logic untouched until a row carries ``transport='ipc'``. diff --git a/docs/parity-status.md b/docs/parity-status.md new file mode 100644 index 0000000..ee41b3f --- /dev/null +++ b/docs/parity-status.md @@ -0,0 +1,137 @@ +# Parity Status — cueapi-core ↔ cueapi-hosted + +This document is the narrative reference for what is and isn't yet ported +between cueapi-core (this repo) and the private cueapi-hosted monorepo. +The machine-readable source-of-truth is `parity-manifest.json` at the repo +root; this doc explains the WHY + WHAT'S NEXT in prose. + +Three related artifacts: + +1. **`parity-manifest.json`** — file-by-file parity tracking (what's + synced, when, deviations) + the new `pending_ports` section that + tracks whole-feature ports across multiple files. +2. **`HOSTED_ONLY.md`** — open-core policy. Lists features that are + intentionally hosted-only and will NOT be ported (Stripe billing, + GDPR cascade, dashboard, cross-org messaging, multi-tenancy). +3. **`docs/parity-status.md`** (this file) — narrative reference for + pending ports + the current gap shape. + +## How to read parity status + +When evaluating "is feature X in cueapi-core?": + +1. Check `HOSTED_ONLY.md` first — if X is listed there, it's intentionally + not ported and won't be. +2. If not in HOSTED_ONLY, check `parity-manifest.json` `oss_only_exclusions` + — file is OSS-only by design. +3. Check `parity-manifest.json` `pending_ports` — whole-feature port + pending, with port sequencing + dependencies enumerated. +4. Check `parity-manifest.json` `field_drift_annotations` — feature is + ported but specific fields diverge (often deferred for trigger or + intentional hosted-only sub-feature). +5. If feature isn't in any of the above + files are last_synced recent, + the feature is at parity. File a Backlog row if you find a gap not + yet tracked. + +## Current pending ports + +### Surface 6 — sender-controlled delivery mode + +**Status**: audit-complete; port pending Jingim Phase 1 plan signal. + +**What it does**: lets the sender pick which channel to deliver a Cue +Message through — `live` (push to recipient's attached Live session), +`bg` (background-handler-spawn), `inbox` (poll-fetchable), `webhook` +(push to recipient's webhook URL), or `auto` (server resolves based on +recipient's `preferred_contact` + cascade fallback). + +**Why it matters for self-hosters**: Dock plans to integrate +cueapi-core messaging into their UI as a customer-facing primitive. The +load-bearing primitive for Dock's Phase 1 rebuild is `delivery_mode: +"live"` (sender-controlled push), which replaces their current 4-second +polling pattern with substrate-side push delivery. + +**What's already in cueapi-core** (the foundations): + +- Event-emit primitive (PR #71, alembic 028) +- Messaging emission wiring (PR #72, alembic 029) +- Long-poll mode (PR #73) +- `agent_live_sessions` schema (PR #67, alembic 026) +- Heartbeat + live-claim attestation endpoints (PR #70) +- `agent_live_sessions` IPC attachment columns (PR #91, alembic 035) +- Per-message `send_at` (PR #77, alembic 030) +- Agent roster + `last_seen_at` (PR #80, alembic 031) + +**What's NOT yet in cueapi-core** (the gap): + +- `MessageCreate.delivery_mode` field on the wire schema (sender input) +- `messages.delivery_mode_requested` + `messages.effective_delivery_mode` + columns (sender input + server-resolved channel) +- `agents.delivery_capabilities` + `agents.preferred_contact` columns + (recipient capability declaration + auto-resolution preference) +- `agents.live_cue_id` + `agents.bg_cue_id` columns (bridge targets for + push delivery) +- Delivery-mode resolver in `message_service.create_message` (capability + check + cascade fallback) +- Bridge logic that fires the recipient's `live_cue_id` / `bg_cue_id` + when `delivery_mode=live` / `delivery_mode=bg` + +The load-bearing private migration is `alembic/versions/050a_messages_delivery_mode.py` +on cueapi-hosted, with follow-on `067_messages_queue_effective_mode.py` +extending the CHECK constraint to include a `queue` value for the +`live_fallback_mode='queue'` case. + +**Port sequencing** (4 steps; each step is its own PR): + +1. **Audit-PR** (this PR): expand `parity-manifest.json` with the + Surface 6 row + clarifying comment on alembic 035's forward-reference + + this narrative doc. +2. **Substrate migration port**: port alembic 050a + 067 → cueapi-core + (next available migration numbers). 4 agent columns + 2 message + columns + CHECK constraint. Tests: schema-invariant patterns matching + existing PR #87 / #91 style. +3. **`MessageCreate.delivery_mode` field + resolver logic**: wire-shape + addition + capability-check + auto-cascade-fallback. Tests: + per-mode happy paths + cascade fallback + invalid-mode rejection. +4. **Bridge logic**: live mode → fire `agents.live_cue_id`; bg mode → + fire `agents.bg_cue_id`. Effective-mode resolution surfaces on + `MessageResponse`. Tests: bridge fires correct cue + claim-miss + cascade to bg. + +Steps 2-4 are held pending Jingim's Phase 1 plan signal — substantive +timing depends on Dock's Phase 1 rebuild sketch. The 4-PR cut shape is +locked; only the start-timing is open. + +**Tier shape**: single-tenant self-hosted (Fly + Neon Postgres + Upstash +Redis). The 4 port-PRs are tenant-agnostic; no multi-tenancy logic +needs to ride along. + +**Cross-org gating is OUT OF SCOPE**: cueapi-hosted's `cross_org_*_enabled` +toggles + cross-org walk-trio (PRs #881-#889 on hosted) stay +hosted-only per the open-core policy. The Surface 6 port is intra-tenant +only. + +### Other pending ports + +See `parity-manifest.json` `field_drift_annotations` for field-level +divergence on already-ported files (e.g., Execution + Cue schema fields +deferred for trigger). Whole-feature ports beyond Surface 6 are tracked +on the agent-team-project-tracker Backlog; this doc lists the +narrative-worthy ones with documented sequencing. + +## Audit methodology + +Audits happen periodically (typical cadence: post-cueapi-hosted-feature +ship + every 2-4 weeks for the broader gap inventory). The full-audit +date is in `parity-manifest.json` `last_full_audit`. Audits cross-check +every file in `app/**/*.py` + `alembic/**/*.py` against the private +counterpart at audit time. + +When you find a feature you want in cueapi-core that isn't here: + +1. File a Backlog row on the agent-team-project-tracker +2. Cross-reference the cueapi-hosted PR(s) that introduced it +3. Add it to `parity-manifest.json` `pending_ports` with the coverage + matrix (load-bearing PRs + migrations + target OSS files) +4. Sequence the port-PRs (substrate migration → wire-shape → routing → + tests typically; adjust for the feature's surface) diff --git a/parity-manifest.json b/parity-manifest.json index 871a78b..91a83da 100644 --- a/parity-manifest.json +++ b/parity-manifest.json @@ -107,6 +107,115 @@ } } }, + "pending_ports": { + "comment": "Whole-feature ports from private cueapi/cueapi that are NOT YET in cueapi-core. Each entry enumerates the load-bearing private artifacts (PRs + migrations + files) and the target OSS files that would change when the port lands. Distinct from oss_only_exclusions (whole files only in OSS) and field_drift_annotations (single-field divergence on already-ported files). Backlog rows for these ports live on the agent-team-project-tracker; the matrix below is the SoT for what each port touches. Reviewed 2026-05-19 by cueapi-secondary during Surface 6 audit (msg_410i80bctfox; KICKOFF cue msg_sq6h2fcwseiu).", + "surface_6_delivery_mode": { + "summary": "Sender-controlled push delivery for the messaging primitive: MessageCreate.delivery_mode = 'live' | 'bg' | 'inbox' | 'webhook' | 'auto' with substrate-side capability check + cascade fallback + bridge to recipient's live_cue_id / bg_cue_id. Load-bearing primitive for Dock's Phase 1 rebuild per Jingim's directional read (msg_vzs6a1t5oii5 2026-05-19 ~01:43Z).", + "tier": "single-tenant-self-hosted", + "status": "audit-complete; port pending Jingim Phase 1 plan signal", + "private_origin": { + "load_bearing_prs": [ + { + "pr": "cueapi/cueapi#635 family (Surface 6 productization)", + "context": "Backlog row cmot4owm7 (substrate work) + cmot5alrh (bridge work). Adds sender-controlled delivery_mode field + capability declaration on agents + bridge logic in message_service." + } + ], + "load_bearing_migrations": [ + { + "path": "alembic/versions/050a_messages_delivery_mode.py", + "ddl": "agents.delivery_capabilities (JSONB default '[\"inbox\"]') + agents.preferred_contact (TEXT default 'inbox') + agents.live_cue_id (VARCHAR(20) FK cues.id ON DELETE SET NULL) + agents.bg_cue_id (same shape) + messages.delivery_mode_requested (TEXT nullable) + messages.effective_delivery_mode (TEXT nullable, CHECK in 'live'|'bg'|'inbox'|'webhook'); 'auto' is a sender-input value only (server resolves before write)." + }, + { + "path": "alembic/versions/067_messages_queue_effective_mode.py", + "ddl": "Extends valid_effective_delivery_mode CHECK constraint to include 'queue' value. Used when sender targets Live but recipient's Live session has no fresh heartbeat AND sender's live_fallback_mode='queue' (vs 'bg'). Stays delivery_state='queued' + effective_delivery_mode='queue'; poll-fetchable once Live session attaches." + } + ] + }, + "target_oss_files": { + "models": [ + { + "path": "app/models/agent.py", + "change": "Add 4 columns: delivery_capabilities (JSONB), preferred_contact (String(16) default 'inbox'), live_cue_id (String(20) FK cues.id ON DELETE SET NULL), bg_cue_id (String(20) FK cues.id ON DELETE SET NULL)." + }, + { + "path": "app/models/message.py", + "change": "Add 2 columns: delivery_mode_requested (Text nullable), effective_delivery_mode (Text nullable). Add CHECK constraint valid_effective_delivery_mode." + } + ], + "schemas": [ + { + "path": "app/schemas/message.py", + "change": "Add MessageCreate.delivery_mode: Optional[Literal['live', 'bg', 'inbox', 'webhook', 'auto']]. Add MessageResponse.delivery_mode_requested + MessageResponse.effective_delivery_mode." + }, + { + "path": "app/schemas/agent.py", + "change": "Add AgentResponse.delivery_capabilities (list) + AgentResponse.preferred_contact (str). Expose for the GET /v1/agents/{ref} discovery surface." + } + ], + "services": [ + { + "path": "app/services/message_service.py", + "change": "Add delivery-mode resolver in create_message: capability check + cascade fallback (auto resolves via preferred_contact). Add bridge logic to fire recipient's live_cue_id when delivery_mode=live (claim-window-aware; cascade to bg on claim miss per migration 067 'queue' state). Add bridge logic to fire recipient's bg_cue_id when delivery_mode=bg." + }, + { + "path": "app/services/agent_service.py", + "change": "Allow PATCH /v1/agents/{ref} to set delivery_capabilities + preferred_contact + live_cue_id + bg_cue_id." + } + ], + "alembic": [ + { + "path": "TBD: alembic/versions/036_messages_delivery_mode.py (or next available)", + "ddl": "Port of cueapi-hosted 050a (load-bearing migration above). All 6 columns from the private migration; CHECK constraint enum from 067 (include 'queue' value)." + } + ] + }, + "deferred_in_audit": [ + { + "feature": "live_fallback_mode (sender chooses queue vs bg on Live-miss)", + "private_origin": "alembic 067_messages_queue_effective_mode.py (queue state) + service-layer resolver", + "note": "Optional; ship Surface 6 minimal first (live + bg + inbox + webhook + auto resolver). live_fallback_mode is a sender-side preference for handling claim-miss — Dock's Phase 1 may not need it. Defer to Step 4 follow-up OR fold into Step 2 if substrate work is small." + }, + { + "feature": "cross-org delivery_mode gating (cross_org_cue_task_enabled etc.)", + "private_origin": "cueapi/cueapi PR #881-#889 (cross-org walk-trio)", + "note": "Per Jingim's tier-shape lock (single-tenant self-hosted), cross-org is OUT OF SCOPE for the OSS port. Cross-org gating + walk-trio stays hosted-only until self-hosters signal need." + } + ], + "ports_already_landed_in_oss_for_surface_6_foundations": [ + "alembic 028 + app/models/event.py + app/services/events_service.py — event-emit primitive (PR #71, ports private #731 PR-1b)", + "alembic 029 — messaging emission columns (PR #72, ports private #775 PR-2a)", + "alembic 030 — message send_at (PR #77, ports private #623)", + "alembic 031 — agents.last_seen_at + roster endpoint (PR #80, ports private #630)", + "alembic 033 — subscriptions.inline_body (PR #84, ports private #791)", + "alembic 034 — subscriptions.last_acked_event_id (PR #85, ports private #793)", + "alembic 026 — agent_live_sessions schema (PR #67)", + "alembic 027 — executions live-claim attestation (PR #70)", + "alembic 035 — agent_live_sessions IPC attachment (PR #91)" + ], + "forward_references_in_oss_pending_surface_6": [ + { + "path": "alembic/versions/035_agent_live_sessions_ipc_attachment.py", + "note": "Docstring mentions `delivery_mode_requested='ipc'` (line 42 'returns immediately with delivery_mode_requested='ipc'') as a forward-reference to behavior the (future) async fire-accept dispatcher will exhibit. The column itself is NOT added by 035 (which only touches agent_live_sessions); it lands when Surface 6's substrate migration ports. NOT a bug — the ASYNC dispatcher path won't fire until Surface 6 is complete (no message-create path produces delivery_mode_requested='ipc' until then). Verified 2026-05-19 by cueapi-secondary during Step 1 audit." + } + ], + "sequencing_recommendation": [ + "Step 1 (audit-PR, this PR): expand parity-manifest with this Surface 6 row + docs/parity-status.md narrative + 035 docstring clarifying comment.", + "Step 2 (substrate migration port): port 050a + 067 -> cueapi-core 036 + 037 (or next available numbers). 4 agent columns + 2 message columns + CHECK constraint.", + "Step 3 (MessageCreate.delivery_mode + resolver): wire-shape field + capability-check + auto-cascade-fallback logic in message_service.", + "Step 4 (bridge logic): live mode -> fire agents.live_cue_id; bg mode -> fire agents.bg_cue_id. Effective-mode resolution surfaces on MessageResponse." + ], + "audit_metadata": { + "audit_date": "2026-05-19", + "audited_by": "cueapi-secondary", + "kickoff_cue": "msg_sq6h2fcwseiu", + "scope_narrow_cue": "msg_u7x0f649gjro", + "greenlight_cue": "msg_7enby3btpl6v", + "dock_workspace": "https://trydock.ai/mike/cueapi-core-parity-for-dock", + "jingim_directional_signal": "msg_vzs6a1t5oii5", + "tier_shape": "single-tenant-self-hosted (Fly + Neon Postgres + Upstash Redis)" + } + } + }, "files": { "alembic": [ {