Skip to content

feat(OMN-19513): declare claude_hook_events and claude_agent_spans ownership ahead of their vendoring - #2958

Merged
jonahgabriel merged 3 commits into
devfrom
jonah/omn-19513-declare-claude-hook-events-ownership
Sep 26, 2026
Merged

jonahgabriel merged 3 commits into
devfrom
jonah/omn-19513-declare-claude-hook-events-ownership

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Ticket: OMN-19513 (operator ruling on the rolling ledger, lane all-hooks-capture-83-ruling: "we need all hooks captured").

This PR declares omnimarket as the catalog owner of two new relations in scripts/application-relation-ownership.yaml: omninode_internal.claude_hook_events and omninode_internal.claude_agent_spans. Both relations are configured with database_ref set to application, access set to read_write, and roles set to claude_hook_events and claude_agent_spans. The migration path is src/omnimarket/nodes/node_projection_claude_hook_events/migrations/0000_create_claude_hook_events.sql. This change mirrors the node contract in omnimarket#2956. The PR carries no SQL. It is step 1 of the three-PR order documented in the file, similar to how omnimarket#2937 handled session_content. No exemption is added. The relation inventory evidence file under docs/evidence/ was regenerated using scripts/generate_application_relation_inventory.py, updating source_declared_tables from 81 to 83, while source_created_tables remains 72.

Proof

Unit tests in tests/unit/scripts/test_application_relation_inventory.py show 12 passed. The generator --check passes. Ruff is clean. Per Rule 24, there are no runtime behaviour changes, so nothing is exercised on the lab. The tables named in the PR already exist on the .201 dev lane from the omnimarket#2956 lab pass.

Merge order

  1. This PR
  2. omnibase_infra#4169 (vendors the two migrations)
  3. omnimarket#2956 (the node and its migrations)

The omnibase_infra Application Database Domain Enforcement gate resolves ownership from this file at omnimarket dev tip and requires exactly one declaration. The omnimarket node-migration-vendor-parity gate refuses a node migration until omnibase_infra dev carries the vendored copy.

Hand-authored change-control companion: onex_change_control#11491 (the autobind declined this head terminally with skip:NO_RED_DERIVABLE_CHECK, so evidence is hand-authored under the hand-authored evidence procedure, mirroring the sibling #2937's companion). It must merge before this PR's change-control checks turn green.

Evidence-Ticket: OMN-19513
Evidence-Source: OCC#11491

…nership ahead of their vendoring

Step 1 of the three-PR order scripts/application-relation-ownership.yaml
documents, as omnimarket#2937 did for session_content. omnibase_infra#4169
vendors the node_projection_claude_hook_events migrations and its
Application Database Domain Enforcement gate resolves ownership from this
file at omnimarket dev tip, so the declaration has to land first;
omnimarket#2956 then lands the node and its migration. No SQL, no exemption.
The relation inventory is regenerated: source_declared_tables 81 to 83,
source_created_tables unchanged at 72.
@github-actions

Copy link
Copy Markdown

✅ Architectural Review — PASSED

Errors: 0
Warnings: 0

What this checks

Rule Description
ARCH-TOPIC-001 No hardcoded Kafka topic strings in handler code
ARCH-DI-001 No event_bus=None bypass
ARCH-DI-002 No direct Handler instantiation outside workflow_runner/adapters
ARCH-DI-003 No reinvented DI containers
ARCH-TOPIC-002 contract.yaml topics follow `onex.{cmd

No architectural violations found.

Static architectural lint — no model inference (OMN-14176).

@onexbot-occ-writer

Copy link
Copy Markdown
Contributor

OCC autobind did not mint a companion for this PR: no changed-file candidate could be proven RED against the merge base, and emitting a PR-existence probe instead would be non-falsifiable evidence (OMN-15247). Hand-authored evidence is required.

@jonahgabriel
jonahgabriel enabled auto-merge (squash) September 26, 2026 15:06
@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

✅ Hostile Reviewer — PASSED

Blocking findings (critical/error): 0
Total findings: 8
Models succeeded: qwen3-review,gpt-oss-review


Gate semantics

Verdict Meaning Blocks merge?
passed >=2 models succeeded, no critical/error findings No
blocked CRITICAL or ERROR findings found Yes
degraded Fewer than 2 models succeeded (infra unavailable/timeout) Yes (OMN-15110)

Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524)

… their tables

The first version of this declaration named claude_hook_events and
claude_agent_spans only. The OMN-15361 SQL ownership gate treats a sequence
named by a GRANT as a target in its own right (the OMN-18999 correction
recorded beside prod_promotion_gate_decisions_projection_cursor_seq), and the
vendored 0001 grants USAGE on both BIGSERIAL cursor sequences by literal name.

Measured by running omnibase_infra scripts/ci/check_application_database_sql.py
over omnibase_infra#4169 with this manifest as the omnimarket input:
- at 304b049 (tables only): FAIL, two findings, one per sequence;
- with these two database_objects entries: PASS.

Test first: tests/test_omn19513_claude_hook_events_ownership_declared.py failed
on both sequence cases before the entries and passes after. The generated
relation inventory is unchanged (--check clean before and after --write).
jonahgabriel added a commit that referenced this pull request Sep 26, 2026
… sequences

Two changes carried here so this node package stays byte-equal to what
omnibase_infra#4169 vendors and to what omnimarket#2958 declares:

- 0001 now asserts the omninode_runtime USAGE grant on schema omninode_internal
  with has_schema_privilege, answering the Hostile Reviewer MAJOR finding on
  omnibase_infra#4169 that it was the one issued privilege left unasserted. The
  grant set itself is unchanged: it is already exactly the writer scope. The
  file is byte-identical to the vendored copy on omnibase_infra#4169 (cmp).

- scripts/application-relation-ownership.yaml gains the two
  database_objects sequence entries, identical to omnimarket#2958, because the
  OMN-15361 SQL gate refused both sequences by name with only the tables
  declared. The ownership test from #2958 is carried too, so the hunks are
  identical and merge cleanly in either order.

Lab (.201 dev lane, omnibase-infra-postgres, database omnidash_analytics):
both files applied inside BEGIN ... ROLLBACK with ON_ERROR_STOP=1, clean; with
the schema USAGE revoked inside a rolled-back transaction the new assertion
raised division by zero. Post-state unchanged (USAGE true, 21 event rows,
4 span rows).
Resolves the relation-inventory conflict with OMN-19716 (topic_activity):
the inventory is regenerated from the merged tree (84 declared, 73 created,
21 sequences) and the census assertion counts both declarations.

Onex-Lane: all-hooks-contract-83
Onex-Session: 6950f7ce306645a3b424fc90448d07c0
jonahgabriel added a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 26, 2026
…market#2958 (#11491)

* evidence(OMN-19513): hand-authored OCC companion for OmniNode-ai/omnimarket#2958

occ-preflight/eligibility declined omnimarket#2958 terminally with
skip:NO_RED_DERIVABLE_CHECK: the diff is a db_io.db_tables ownership
declaration in scripts/application-relation-ownership.yaml plus its
regenerated evidence json and a test-count assertion. Per OMN-15247 the
remedy is hand-authored evidence.

Appends two dod_evidence entries to contracts/OMN-19513.yaml plus their
receipts: a content-bound probe (role: claude_hook_events present at the
PR head 304b049a57, absent at the merge base 6c13a5fcb5, positive control
on a pre-existing role) and the product diff scope (3 files, 0 SQL). No
acceptance criterion of OMN-19513 is bound: #2958 is step 1 of the forced
order declare -> vendor (omnibase_infra#4169) -> create (omnimarket#2956).

Purely additive. Local: check_contract_change_rebinds_receipts --staged,
check_receipt_hardening --staged (positive control: a tampered entry hash
is refused), check_contract_substance_floor, check_contract_dod_authoring
and pre-commit over the touched files all pass.

OMN-19513

* evidence(OMN-18921): mint the owed OCC self-bind for PR #11491

* evidence(OMN-19513): drop the self-bind minted before the dev merge so the mint re-binds it to the merged contract hash

Onex-Lane: all-hooks-contract-83
Onex-Session: 58fb36e9cf4f473f85a53ae4d1016c46

* evidence(OMN-18921): mint the owed OCC self-bind for PR #11491

---------

Co-authored-by: onexbot-occ-writer[bot] <onexbot-occ-writer[bot]@users.noreply.github.com>
@jonahgabriel
jonahgabriel merged commit 1c37ee7 into dev Sep 26, 2026
181 of 189 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-19513-declare-claude-hook-events-ownership branch September 26, 2026 23:50
jonahgabriel added a commit that referenced this pull request Sep 28, 2026
…ode hook, with subagent lineage (#2956)

* feat(OMN-19513): node_projection_claude_hook_events -- every Claude Code hook, with subagent lineage

The consumer half of the all-hooks capture contract (omniclaude#2370). One
subscribe topic, onex.evt.omniclaude.hook-event.v1, carries all 33 Claude
Code hook types with lineage on every event. This node projects it into
omninode_internal.claude_hook_events (one row per event, session_id as a real
column, agent_id NULL for the main thread) and folds subagent lineage into
omninode_internal.claude_agent_spans (parent_agent_id plus parent_resolution:
main_thread, resolved, workflow or unknown).

Rule 7a pair: HandlerProjectionClaudeHookEvents is the pure definition-B
fold; ClaudeHookEventsProjectionWriter is the in-process writer that reads
the spawning PreToolUse, calls the fold and persists. Idempotent by
construction: event rows insert ON CONFLICT (event_id) DO NOTHING, the span
tool_call_count is recounted from the event table rather than incremented,
and a known parent replaces unknown but never the reverse. A child written
before its spawning call is repaired when the call arrives.

Only the writer is a routed entry. The contract's event has a top-level
payload mapping and an event_id, which the runtime's typed arm reads as a
transport envelope and unwraps through (OMN-16050); a routed fold would
receive the per-hook payload without its lineage and dead-letter every event.

The wire model refuses a payload key the capture contract does not declare
for the metadata topic, so content that reaches this topic by a producer
regression is dead-lettered, not stored. It drops, by name, the five
top-level stamps the emit seam adds (measured on a live lab record).

Tests reproduce the contract's expected_projection rows for the nested
subagent, workflow-agent and orphan-agent scenarios through the writer's
runtime entry, pin the hook list, payload keys and projection columns to the
contract, and prove the SQL against real Postgres (skips without a DB).

* fix(OMN-19513): the hook wire model drops the journal appender's stamps by name

The wire model for onex.evt.omniclaude.hook-event.v1 forbids extra top-level
keys and drops transport stamps by name. It previously dropped only five
stamps: correlation_id, causation_id, entity_id, session_id, and
redaction_state. The real appender (omniclaude hook_emit_append.py) also
merges lane, lane_source, lane_ticket, workspace_path, and turn_id into
every record. Because these five keys were not in the drop set, a fixture
event journaled through the real appender was rejected with extra_forbidden,
so every real hook event would have been dead-lettered.

The fix adds the five appender keys to the named stamp set so they are
dropped instead of rejected. The event's lineage.turn_id remains
authoritative over the appender's top-level turn_id. The seam test now
carries the appender stamps with a different top-level turn_id and asserts
the stored turn_id is the lineage one. A new test proves an unnamed
top-level key such as prompt is still refused.

* feat(OMN-19513): declare claude_hook_events and claude_agent_spans ownership ahead of their vendoring

Step 1 of the three-PR order scripts/application-relation-ownership.yaml
documents, as omnimarket#2937 did for session_content. omnibase_infra#4169
vendors the node_projection_claude_hook_events migrations and its
Application Database Domain Enforcement gate resolves ownership from this
file at omnimarket dev tip, so the declaration has to land first;
omnimarket#2956 then lands the node and its migration. No SQL, no exemption.
The relation inventory is regenerated: source_declared_tables 81 to 83,
source_created_tables unchanged at 72.

* fix(OMN-19513): the hook wire model drops the drainer's hook_fired_at stamp by name

The omniclaude emit drainer stamps hook_fired_at on every published record, so every real drained hook event failed validation and was dead-lettered. This adds hook_fired_at to TRANSPORT_STAMP_KEYS, so the wire model drops it by name while still refusing unknown top-level keys. Tests carry the new key and assert it is accepted while an unknown key is still rejected.

Onex-Lane: all-hooks-contract-83
Onex-Session: 9a8f8ac2b564447cbcbd3142788e28a1

* test(OMN-19513): pin the new writer, node and relations in the repo rosters

CI on the first push failed three roster tests this node moves.
ClaudeHookEventsProjectionWriter joins the pinned exact set of
in-process-dispatch writers as the eighth, on the same reviewed terms (its
pure fold does not declare the capability). The relation inventory is
regenerated after merging the omnimarket#2958 ownership declaration:
source_created_tables 72 to 74 and the arithmetic unreconciled bound 14 to
12, declared unchanged here. The dogfood inventory counts node_dirs 425 to
426 and entry_points 416 to 417.

* fix(OMN-19513): assert the schema USAGE grant and declare both cursor sequences

Two changes carried here so this node package stays byte-equal to what
omnibase_infra#4169 vendors and to what omnimarket#2958 declares:

- 0001 now asserts the omninode_runtime USAGE grant on schema omninode_internal
  with has_schema_privilege, answering the Hostile Reviewer MAJOR finding on
  omnibase_infra#4169 that it was the one issued privilege left unasserted. The
  grant set itself is unchanged: it is already exactly the writer scope. The
  file is byte-identical to the vendored copy on omnibase_infra#4169 (cmp).

- scripts/application-relation-ownership.yaml gains the two
  database_objects sequence entries, identical to omnimarket#2958, because the
  OMN-15361 SQL gate refused both sequences by name with only the tables
  declared. The ownership test from #2958 is carried too, so the hunks are
  identical and merge cleanly in either order.

Lab (.201 dev lane, omnibase-infra-postgres, database omnidash_analytics):
both files applied inside BEGIN ... ROLLBACK with ON_ERROR_STOP=1, clean; with
the schema USAGE revoked inside a rolled-back transaction the new assertion
raised division by zero. Post-state unchanged (USAGE true, 21 event rows,
4 span rows).
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