Skip to content

research.completed C-Outbox emit + reusable ProvenanceHandoff SDK capability (P2 Go + P3) - #3

Merged
tha-hammer merged 3 commits into
mainfrom
feat/int-02-b1-research-completed-emit
Jul 12, 2026
Merged

research.completed C-Outbox emit + reusable ProvenanceHandoff SDK capability (P2 Go + P3)#3
tha-hammer merged 3 commits into
mainfrom
feat/int-02-b1-research-completed-emit

Conversation

@tha-hammer

@tha-hammer tha-hammer commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Control-plane + SDK side of the cross-app research→reels hand-off, governed by specs/cross-app-handoff.pattern.md. Pairs with the reel-af PR (tha-hammer/silmari-reels-af#12).

What's here

  • P2 — same-tx emit (C-Outbox) (2445e16b, 288e1b89): research.completed CloudEvent is appended to event_outbox inside the terminal succeeded state-write transaction — the run-completion write and the event both commit or neither does (kills the dual-write bug). New AppendEventOutboxTx (append on the caller's *sql.Tx), UpdateExecutionRecordWithOutbox, and an ExecutionOutboxUpdater capability-interface hook in completeExecution (falls back cleanly, so existing mock stores compile unchanged). Gated on AgentNodeID == meta_deep_research + succeeded. Small owner DTO only — never the body (C-Notification); subject = execution_id (C-Correlation).
  • P3 — generalize into a reusable SDK capability (7e942a1a): agentfield/provenance_handoff.py implements the spec's Producer port (OUT-P) + Consumer port (IN-C/OUT-C) as a drop-in ProvenanceHandoff capability, plus a conformance kit so the four waiting apps plug in by implementing the port — not by re-solving integration or writing each other's tables. Behaviors 1–8.

Verification

  • Control-plane Go: go build ./... + go vet clean; events/storage/handlers (+subpackages) all green. Same-tx closure proven via real SQLite BeginTx/Commit/Rollback.
  • SDK Python: 1660 passed / 4 skipped (incl. the new provenance-handoff capability + conformance kit).
  • Downstream reel-af consumer suite unaffected: 645 passed.

🤖 Generated with Claude Code

…-Outbox)

INT-02 Behavior 1, control-plane Go half. The research.completed event must
append to event_outbox in the SAME transaction as the execution's terminal
succeeded write (C-Outbox) — otherwise a dual-write bug reintroduces itself
(run marked done, event lost, or vice-versa).

- internal/events/research_completed.go: builds the research.completed
  CloudEvent (envelope + small owner DTO) from a terminal execution, gated on
  the deep-research node id + succeeded status. Mirrors the shape already
  shipped by silmari-af-deep-research/research_completed_event.py (the
  canonical producer + golden fixture).
- internal/storage/event_outbox.go: AppendEventOutboxTx appends on the
  caller's *sql.Tx via raw SQL (postgres RETURNING seq / sqlite
  LastInsertId), since the existing AppendEventOutbox appends via GORM on its
  own connection and can't share a transaction.
- internal/storage/execution_records.go: UpdateExecutionRecord now delegates
  to UpdateExecutionRecordWithOutbox, which appends the outbox record on the
  same tx before Commit().
- internal/handlers/execute.go: completeExecution (the one confirmed
  terminal-succeeded write path for every agent call) type-asserts its store
  to an ExecutionOutboxUpdater capability and wires in the emit when
  available, falling back to the plain path otherwise.

Closure tests use a real on-disk SQLite store via database/sql transactions
(matching internal/events/durable_bus_test.go's existing pattern for
same-tx claims): prove the append lands in-tx, a rollback discards it, the
gate skips non-research/non-succeeded executions, and the old plain path
never appended (red-at-seam baseline).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

📊 Coverage gate

Thresholds from .coverage-gate.toml: per-surface ≥ 84%, aggregate ≥ 85%, max per-surface regression ≤ 1.0 pp, max aggregate regression ≤ 0.50 pp.

Surface Current Baseline Δ
control-plane 87.00% 87.40% ↓ -0.40 pp 🟡
sdk-go 92.00% 92.00% → +0.00 pp 🟢
sdk-python 93.73% 93.73% ↑ +0.00 pp 🟢
sdk-typescript 90.50% 90.42% ↑ +0.08 pp 🟢
web-ui 84.85% 84.79% ↑ +0.06 pp 🟡
aggregate 85.64% 85.75% ↓ -0.11 pp 🟡

✅ Gate passed

No surface regressed past the allowed threshold and the aggregate stayed above the floor.

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

📐 Patch coverage gate

Threshold: 80% on lines this PR touches vs origin/main (from .coverage-gate.toml:thresholds.min_patch).

Surface Touched lines Patch coverage Status
control-plane 139 94.00%
sdk-go 0 ➖ no changes
sdk-python 0 ➖ no changes
sdk-typescript 0 ➖ no changes
web-ui 0 ➖ no changes

✅ Patch gate passed

Every surface whose lines were touched by this PR has patch coverage at or above the threshold.

tha-hammer and others added 2 commits July 12, 2026 17:35
CI's coverage-summary gate failed at 75.00% patch coverage (min_patch=80%,
34/139 touched lines uncovered). Closes the reachable gaps:

- research_completed.go: cover the nil-execution guard directly; simplify
  BuildResearchCompletedOutboxRecord to stop duplicating the succeeded-only
  check that BuildResearchCompletedEvent already owns (the duplicate made
  its own error branch dead code) — the existing SkipsNonSucceeded test now
  exercises the real (single) validation path.
- execute.go: add tests for the "already cancelled/waiting" guard inside
  completionUpdater (pre-existing logic, newly extracted into a named
  closure by the previous commit).
- event_outbox.go: cover the empty-payload default, and drive the postgres
  RETURNING-clause branch directly — mattn/go-sqlite3 supports RETURNING, so
  wrapping the same real on-disk *sql.Tx with mode="postgres" exercises that
  branch's actual SQL + Scan behavior without a live postgres connection.
  Also cover the dead-tx error path for both branches.

Remaining 8 uncovered patch lines (94% final) are defensive-only branches
not reachable without contriving the failure (json.Marshal erroring on a
fixed struct with no cyclic/unsupported fields, sql.Result.LastInsertId
failing on a normal rowid-table insert, and a fake test store that
pre-checks existence and can never invoke the updater with a nil record the
way the real DB-backed store does on a genuinely missing row).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… kit (Behaviors 1-8)

Extract the Phase-2 research→reels handoff pattern into a drop-in
AgentField ProvenanceHandoff capability with ports, adapters, and a
conformance kit certifying R1–R4 rules.

SDK capability (provenance_handoff.py):
- CompletionEmitter / CompletionConsumer / ProvenanceStore ports
  (@runtime_checkable Protocol encoding R1–R4)
- CompletionEnvelope value object (CloudEvents 1.0, subject == correlation_id)
- OutboxCompletionEmitter adapter (appends via DurableExecutionBus.Publish)
- IdempotentConsumer adapter (dedup on correlation_id, store.has() guard)
- build_envelope() with depth/size validation

Conformance kit (testing/conformance.py):
- run_provenance_conformance() certifies R1–R4 against a fake outbox
- C-Outbox and C-AtLeastOnce delegated to live-Postgres @integration proofs
- Deliberately-broken fixture assertions (R1/R2/R3/R4 each proven red)

Cross-language golden fixture (testdata/completion_envelope.golden.json):
- Go round-trip test asserts same shape as Python

testing.py → testing/ package conversion:
- Moved to testing/_testing_helpers.py with fixed imports
- Added conformance.py to the package

Behavior 6: Phase-2 refactored onto generic port:
- ReelAfConsumer + ReelAfProvenanceStore adapters prove the existing
  research→reels pattern is expressible via the generic ports
- Conformance kit certifies the pair R1–R4
- All Phase-2 suites remain green (645 carousel-impl, 22 Go research)

Test coverage: 41 new Python tests (7 files), 2 new Go tests.
All pass: SDK 1664 passed, Go events green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tha-hammer tha-hammer changed the title feat(events): emit research.completed in the terminal succeeded tx (C-Outbox) research.completed C-Outbox emit + reusable ProvenanceHandoff SDK capability (P2 Go + P3) Jul 12, 2026
@github-actions

Copy link
Copy Markdown

Performance

SDK Memory Δ Latency Δ Tests Status
Python 9.4 KB +4% 0.33 µs -6%

✓ No regressions detected

@tha-hammer
tha-hammer merged commit 2b16920 into main Jul 12, 2026
29 of 33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant