M4-1b-i: the contract-1-only slice of the hierarchy domain layer. Ships the §5.2 audit event + outbox machinery with zero mutation routes; the command family (controllers, DTOs, grant evaluation per contract 2) is M4-1b-ii and waits for #1436.
What lands
Schema (migration 0019)
hierarchy_audit_events: append-only semantic events for create / rename / transfer / delete / grant_create / grant_change / grant_revoke over company / estate / platform_project / grant targets. Deletion-safe linkage per §5.2: the target is captured as an immutable jsonb snapshot (id, slug, name, root-first parent chain) — no FK into any class table, so events survive target deletion. CHECK constraints pin the verb set, target-kind set, and the transfer shape (transfer_from/transfer_to present exactly on transfers). Unique idempotency key (REQ-AUD-001 duplicate suppression at the database level), generated-always seq for per-target ordering, causation self-FK (RESTRICT).
hierarchy_outbox: dedicated outbox written in the same transaction as its event — FK RESTRICT to the event, unique event_id + idempotency key, status enum pending/processing/delivered, (status, created_at) claim index.
Gateway
HierarchyAuditRepository (apps/gateway/src/hierarchy/): append composes event + outbox onto the caller's transaction; a duplicate idempotency key with identical content returns the prior event (replayed: true), different content throws. Snapshot comparison uses canonical (sorted-key) JSON because Postgres jsonb does not preserve key order. buildNodeSnapshot produces the root-first parent chain on the same transaction as the mutation it audits. Outbox lifecycle is claim-by-CAS: claim (pending→processing, guarded UPDATE), complete (→delivered + deliveredAt), release (→pending, replayable).
HierarchyModule: providers only, no controllers. Registered in AppModule.
Witnesses
packages/db/src/hierarchy-audit.witness.test.ts — dual-leg (§6.8: PGlite always, real PG when DATABASE_URL is set) schema witnesses: verb/target-kind/transfer CHECKs (negative + accept controls), duplicate idempotency key refused, seq strictly increasing per target, catalog assertion that the events table's only FK is the causation self-FK, events survive company deletion, causation RESTRICT, outbox FK/uniqueness/status-enum refusals.
apps/gateway/src/hierarchy/hierarchy-audit.integration.test.ts — repository-level §6.4 witnesses over PGlite + Nest TestingModule: atomic commit of state + event + outbox in one transaction, rollback leaves no residue, idempotent replay, conflicting-key refusal, root-first parent chains, events survive target deletion in per-target seq order, outbox claim/complete/release CAS (oldest-first, lost-claim returns null, double-complete no-op).
apps/gateway/src/__tests__/hierarchy-route-inventory.test.ts — §6.3(a) baseline: static Reflect-metadata walk of the AppModule graph enumerates every declared route, with a positive control on known routes, and pins zero routes matching /hierarch|compan|estate|platform[-_]?project/i. A hierarchy route landing before M4-1b-ii's command-family inventory fails here first.
S1 residual (rev-code-01)
hierarchy-writer-coverage.test.ts scan perimeter widened from <pkg>/src to the full package tree (node_modules/dist/build/coverage/test-results and dotdirs excluded). The 31 newly-covered files were enumerated and probe-tested free of every analyzer trigger token before widening (positive control confirmed the probe detects schema.ts/client.ts). WRITER_ALLOWLIST stays empty: this repository writes only audit/outbox tables, never a class table.
Command family (controllers/DTOs), grant evaluation per contract 2, WRITER_ALLOWLIST entries, retirement of the two §1.1 admin bypass paths, replacement of the §6.3(a) baseline with the exact command-family inventory. Company-create semantics stay frozen until the Ruling 4b amendment.
tsc: rc=0 both packages; eslint rc=0 on all touched files
migration 0019 DDL reviewed against schema.ts
No self-merge without review (gate 16). Reviewer: independent detached review + rev-code-01.
M4-1b-i: the contract-1-only slice of the hierarchy domain layer. Ships the §5.2 audit event + outbox machinery with zero mutation routes; the command family (controllers, DTOs, grant evaluation per contract 2) is M4-1b-ii and waits for #1436.
## What lands
**Schema (migration 0019)**
- `hierarchy_audit_events`: append-only semantic events for create / rename / transfer / delete / grant_create / grant_change / grant_revoke over company / estate / platform_project / grant targets. Deletion-safe linkage per §5.2: the target is captured as an immutable jsonb snapshot (id, slug, name, root-first parent chain) — no FK into any class table, so events survive target deletion. CHECK constraints pin the verb set, target-kind set, and the transfer shape (`transfer_from`/`transfer_to` present exactly on transfers). Unique idempotency key (REQ-AUD-001 duplicate suppression at the database level), generated-always `seq` for per-target ordering, causation self-FK (RESTRICT).
- `hierarchy_outbox`: dedicated outbox written in the same transaction as its event — FK RESTRICT to the event, unique event_id + idempotency key, status enum pending/processing/delivered, (status, created_at) claim index.
**Gateway**
- `HierarchyAuditRepository` (`apps/gateway/src/hierarchy/`): `append` composes event + outbox onto the caller's transaction; a duplicate idempotency key with identical content returns the prior event (`replayed: true`), different content throws. Snapshot comparison uses canonical (sorted-key) JSON because Postgres jsonb does not preserve key order. `buildNodeSnapshot` produces the root-first parent chain on the same transaction as the mutation it audits. Outbox lifecycle is claim-by-CAS: claim (pending→processing, guarded UPDATE), complete (→delivered + deliveredAt), release (→pending, replayable).
- `HierarchyModule`: providers only, no controllers. Registered in AppModule.
**Witnesses**
- `packages/db/src/hierarchy-audit.witness.test.ts` — dual-leg (§6.8: PGlite always, real PG when DATABASE_URL is set) schema witnesses: verb/target-kind/transfer CHECKs (negative + accept controls), duplicate idempotency key refused, seq strictly increasing per target, catalog assertion that the events table's only FK is the causation self-FK, events survive company deletion, causation RESTRICT, outbox FK/uniqueness/status-enum refusals.
- `apps/gateway/src/hierarchy/hierarchy-audit.integration.test.ts` — repository-level §6.4 witnesses over PGlite + Nest TestingModule: atomic commit of state + event + outbox in one transaction, rollback leaves no residue, idempotent replay, conflicting-key refusal, root-first parent chains, events survive target deletion in per-target seq order, outbox claim/complete/release CAS (oldest-first, lost-claim returns null, double-complete no-op).
- `apps/gateway/src/__tests__/hierarchy-route-inventory.test.ts` — §6.3(a) baseline: static Reflect-metadata walk of the AppModule graph enumerates every declared route, with a positive control on known routes, and pins **zero** routes matching `/hierarch|compan|estate|platform[-_]?project/i`. A hierarchy route landing before M4-1b-ii's command-family inventory fails here first.
**S1 residual (rev-code-01)**
- `hierarchy-writer-coverage.test.ts` scan perimeter widened from `<pkg>/src` to the full package tree (node_modules/dist/build/coverage/test-results and dotdirs excluded). The 31 newly-covered files were enumerated and probe-tested free of every analyzer trigger token before widening (positive control confirmed the probe detects schema.ts/client.ts). WRITER_ALLOWLIST stays empty: this repository writes only audit/outbox tables, never a class table.
## Explicitly not in this PR (M4-1b-ii, after #1436)
Command family (controllers/DTOs), grant evaluation per contract 2, WRITER_ALLOWLIST entries, retirement of the two §1.1 admin bypass paths, replacement of the §6.3(a) baseline with the exact command-family inventory. Company-create semantics stay frozen until the Ruling 4b amendment.
## Verification
- packages/db vitest: green (hierarchy-audit witness 9 passed + 9 real-PG skipped locally; writer-coverage 9/9; schema witness green)
- gateway vitest full run: 840 passed / 45 skipped / 0 failed
- tsc: rc=0 both packages; eslint rc=0 on all touched files
- migration 0019 DDL reviewed against schema.ts
No self-merge without review (gate 16). Reviewer: independent detached review + rev-code-01.
Review round 1 blocker: sameEvent omitted transferFrom/transferTo, so a
retried transfer re-routed to a different destination would silently
replay instead of conflicting. Transfer source/destination are semantic
event content per §5.2. Adds a witness varying the transfer destination
on a duplicate idempotency key; also fixes a stale cross-reference in
the db witness header.
rev-code-01
approved these changes 2026-08-28 02:13:47 +00:00
APPROVED — rev-code-01, pinned to head 08410dbd84ca169ca6ffe105aca2a405bc24777b (re-verified unmoved immediately before posting).
Scope: 11 files, +6559/−15 — the contract-1 §5.2 audit/outbox slice, no routes. Verified against contract 1 (my id-328 review) independently of the two GLM rounds:
Schema exact vs §5.2:hierarchy_audit_events carries the seven-verb / four-target-kind CHECK sets, deletion-safe linkage by immutable jsonb snapshot with NO FK into class tables (only the causation self-FK, RESTRICT), transfer-shape CHECK ((verb='transfer') = (from AND to present)), unique idempotency key (REQ-AUD-001 duplicate suppression at the DB), generated seq with per-target (target_id, seq) index for REQ-AUD-001 ordering. hierarchy_outbox: same-transaction sibling with event-FK RESTRICT, unique event_id + idempotency, pending/processing/delivered enum, (status, created_at) claim index. Both tables correctly OUTSIDE the record class (§1.1) — and correctly outside the writer-coverage CLASS_SYMBOLS (still exactly the five class tables, verified).
The fix commit (GLM round-1 finding) verified correct: replay comparison now includes transferFrom/transferTo in the canonical-JSON content equality, and the new witness pins both directions (identical retry replays; a retry re-routed to a different destination throws HierarchyAuditIdempotencyConflictError). Without it, a transfer retry with a different destination would silently replay the old event — a real idempotency hole, now closed and witnessed.
Repository read by me:append composes event + outbox onto the caller's transaction (atomicity is the caller's transaction, witnessed); snapshot comparison uses sorted-key canonical JSON (jsonb key-order independence); buildNodeSnapshot builds the root-first parent chain on the mutation's own transaction; outbox lifecycle is claim-by-CAS (guarded UPDATE pending→processing, oldest-first), complete, and release-to-pending (replayable failure) — no update/delete path for events (append-only at the application layer, honestly noted as such; the INSERT/SELECT-only DB role is a deployment concern outside this schema).
Zero-routes baseline (§6.3a): the Reflect-metadata walk enumerates the AppModule graph with a positive control on known routes, then pins zero /hierarch|compan|estate|platform[-_]?project/i paths and that HierarchyModule declares no controllers — a hierarchy route landing before M4-1b-ii fails here first. Module wiring verified: providers only, registered in AppModule.
My S1 residual adopted and verified with my own red control: the writer-coverage perimeter is widened to the full package tree (build/caches/dot-dirs excluded; the 31 newly-covered files were probe-tested per the PR body). I planted a violation at apps/gateway/ top level — OUTSIDE src/, invisible under the old perimeter, exactly the blind spot my #1459 control exposed — and the widened scanner FAILS with [prong i-symbol] … dbtest-perimeter-probe.ts: .insert(companies); removal restores green. The residual is closed for real, not on paper. WRITER_ALLOWLIST stays empty and correct (this repository writes only audit/outbox tables).
Gates run by me at this head (fresh worktree, build-first): build rc=0; db suite 61 pass/32 skip (skips = real-PG legs, CI-bound); audit witness 9/9 PGlite leg; gateway suite 841 pass / 45 skip incl. hierarchy integration 8/8 and route-inventory 3/3; typecheck 45/45; lint 25/25; format rc=0.
Non-blocking:
[S1] The route-inventory zero-pins use a path regex; when M4-1b-ii replaces this baseline with the exact command-family inventory, carry the positive-control pattern forward (enumeration-based, not regex) — already the stated plan.
CI note: 2923 running at review time (2922 queue-killed). Merge is fred's on this approval + 2923 terminal green.
**APPROVED — rev-code-01, pinned to head `08410dbd84ca169ca6ffe105aca2a405bc24777b`** (re-verified unmoved immediately before posting).
Scope: 11 files, +6559/−15 — the contract-1 §5.2 audit/outbox slice, no routes. Verified against contract 1 (my id-328 review) independently of the two GLM rounds:
1. **Schema exact vs §5.2:** `hierarchy_audit_events` carries the seven-verb / four-target-kind CHECK sets, deletion-safe linkage by immutable jsonb snapshot with NO FK into class tables (only the causation self-FK, RESTRICT), transfer-shape CHECK (`(verb='transfer') = (from AND to present)`), unique idempotency key (REQ-AUD-001 duplicate suppression at the DB), generated `seq` with per-target (target_id, seq) index for REQ-AUD-001 ordering. `hierarchy_outbox`: same-transaction sibling with event-FK RESTRICT, unique event_id + idempotency, pending/processing/delivered enum, (status, created_at) claim index. Both tables correctly OUTSIDE the record class (§1.1) — and correctly outside the writer-coverage CLASS_SYMBOLS (still exactly the five class tables, verified).
2. **The fix commit (GLM round-1 finding) verified correct:** replay comparison now includes `transferFrom`/`transferTo` in the canonical-JSON content equality, and the new witness pins both directions (identical retry replays; a retry re-routed to a different destination throws `HierarchyAuditIdempotencyConflictError`). Without it, a transfer retry with a different destination would silently replay the old event — a real idempotency hole, now closed and witnessed.
3. **Repository read by me:** `append` composes event + outbox onto the caller's transaction (atomicity is the caller's transaction, witnessed); snapshot comparison uses sorted-key canonical JSON (jsonb key-order independence); `buildNodeSnapshot` builds the root-first parent chain on the mutation's own transaction; outbox lifecycle is claim-by-CAS (guarded UPDATE pending→processing, oldest-first), complete, and release-to-pending (replayable failure) — no update/delete path for events (append-only at the application layer, honestly noted as such; the INSERT/SELECT-only DB role is a deployment concern outside this schema).
4. **Zero-routes baseline (§6.3a):** the Reflect-metadata walk enumerates the AppModule graph with a positive control on known routes, then pins zero `/hierarch|compan|estate|platform[-_]?project/i` paths and that HierarchyModule declares no controllers — a hierarchy route landing before M4-1b-ii fails here first. Module wiring verified: providers only, registered in AppModule.
5. **My S1 residual adopted and verified with my own red control:** the writer-coverage perimeter is widened to the full package tree (build/caches/dot-dirs excluded; the 31 newly-covered files were probe-tested per the PR body). I planted a violation at `apps/gateway/` top level — OUTSIDE `src/`, invisible under the old perimeter, exactly the blind spot my #1459 control exposed — and the widened scanner FAILS with `[prong i-symbol] … dbtest-perimeter-probe.ts: .insert(companies)`; removal restores green. The residual is closed for real, not on paper. WRITER_ALLOWLIST stays empty and correct (this repository writes only audit/outbox tables).
6. **Gates run by me at this head (fresh worktree, build-first):** build rc=0; db suite 61 pass/32 skip (skips = real-PG legs, CI-bound); audit witness 9/9 PGlite leg; gateway suite **841 pass / 45 skip** incl. hierarchy integration 8/8 and route-inventory 3/3; typecheck 45/45; lint 25/25; format rc=0.
Non-blocking:
- **[S1]** The route-inventory zero-pins use a path regex; when M4-1b-ii replaces this baseline with the exact command-family inventory, carry the positive-control pattern forward (enumeration-based, not regex) — already the stated plan.
CI note: 2923 running at review time (2922 queue-killed). Merge is fred's on this approval + 2923 terminal green.
fred
merged commit 2148c20d26 into next2026-08-28 02:22:54 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
M4-1b-i: the contract-1-only slice of the hierarchy domain layer. Ships the §5.2 audit event + outbox machinery with zero mutation routes; the command family (controllers, DTOs, grant evaluation per contract 2) is M4-1b-ii and waits for #1436.
What lands
Schema (migration 0019)
hierarchy_audit_events: append-only semantic events for create / rename / transfer / delete / grant_create / grant_change / grant_revoke over company / estate / platform_project / grant targets. Deletion-safe linkage per §5.2: the target is captured as an immutable jsonb snapshot (id, slug, name, root-first parent chain) — no FK into any class table, so events survive target deletion. CHECK constraints pin the verb set, target-kind set, and the transfer shape (transfer_from/transfer_topresent exactly on transfers). Unique idempotency key (REQ-AUD-001 duplicate suppression at the database level), generated-alwaysseqfor per-target ordering, causation self-FK (RESTRICT).hierarchy_outbox: dedicated outbox written in the same transaction as its event — FK RESTRICT to the event, unique event_id + idempotency key, status enum pending/processing/delivered, (status, created_at) claim index.Gateway
HierarchyAuditRepository(apps/gateway/src/hierarchy/):appendcomposes event + outbox onto the caller's transaction; a duplicate idempotency key with identical content returns the prior event (replayed: true), different content throws. Snapshot comparison uses canonical (sorted-key) JSON because Postgres jsonb does not preserve key order.buildNodeSnapshotproduces the root-first parent chain on the same transaction as the mutation it audits. Outbox lifecycle is claim-by-CAS: claim (pending→processing, guarded UPDATE), complete (→delivered + deliveredAt), release (→pending, replayable).HierarchyModule: providers only, no controllers. Registered in AppModule.Witnesses
packages/db/src/hierarchy-audit.witness.test.ts— dual-leg (§6.8: PGlite always, real PG when DATABASE_URL is set) schema witnesses: verb/target-kind/transfer CHECKs (negative + accept controls), duplicate idempotency key refused, seq strictly increasing per target, catalog assertion that the events table's only FK is the causation self-FK, events survive company deletion, causation RESTRICT, outbox FK/uniqueness/status-enum refusals.apps/gateway/src/hierarchy/hierarchy-audit.integration.test.ts— repository-level §6.4 witnesses over PGlite + Nest TestingModule: atomic commit of state + event + outbox in one transaction, rollback leaves no residue, idempotent replay, conflicting-key refusal, root-first parent chains, events survive target deletion in per-target seq order, outbox claim/complete/release CAS (oldest-first, lost-claim returns null, double-complete no-op).apps/gateway/src/__tests__/hierarchy-route-inventory.test.ts— §6.3(a) baseline: static Reflect-metadata walk of the AppModule graph enumerates every declared route, with a positive control on known routes, and pins zero routes matching/hierarch|compan|estate|platform[-_]?project/i. A hierarchy route landing before M4-1b-ii's command-family inventory fails here first.S1 residual (rev-code-01)
hierarchy-writer-coverage.test.tsscan perimeter widened from<pkg>/srcto the full package tree (node_modules/dist/build/coverage/test-results and dotdirs excluded). The 31 newly-covered files were enumerated and probe-tested free of every analyzer trigger token before widening (positive control confirmed the probe detects schema.ts/client.ts). WRITER_ALLOWLIST stays empty: this repository writes only audit/outbox tables, never a class table.Explicitly not in this PR (M4-1b-ii, after #1436)
Command family (controllers/DTOs), grant evaluation per contract 2, WRITER_ALLOWLIST entries, retirement of the two §1.1 admin bypass paths, replacement of the §6.3(a) baseline with the exact command-family inventory. Company-create semantics stay frozen until the Ruling 4b amendment.
Verification
No self-merge without review (gate 16). Reviewer: independent detached review + rev-code-01.
APPROVED — rev-code-01, pinned to head
08410dbd84ca169ca6ffe105aca2a405bc24777b(re-verified unmoved immediately before posting).Scope: 11 files, +6559/−15 — the contract-1 §5.2 audit/outbox slice, no routes. Verified against contract 1 (my id-328 review) independently of the two GLM rounds:
hierarchy_audit_eventscarries the seven-verb / four-target-kind CHECK sets, deletion-safe linkage by immutable jsonb snapshot with NO FK into class tables (only the causation self-FK, RESTRICT), transfer-shape CHECK ((verb='transfer') = (from AND to present)), unique idempotency key (REQ-AUD-001 duplicate suppression at the DB), generatedseqwith per-target (target_id, seq) index for REQ-AUD-001 ordering.hierarchy_outbox: same-transaction sibling with event-FK RESTRICT, unique event_id + idempotency, pending/processing/delivered enum, (status, created_at) claim index. Both tables correctly OUTSIDE the record class (§1.1) — and correctly outside the writer-coverage CLASS_SYMBOLS (still exactly the five class tables, verified).transferFrom/transferToin the canonical-JSON content equality, and the new witness pins both directions (identical retry replays; a retry re-routed to a different destination throwsHierarchyAuditIdempotencyConflictError). Without it, a transfer retry with a different destination would silently replay the old event — a real idempotency hole, now closed and witnessed.appendcomposes event + outbox onto the caller's transaction (atomicity is the caller's transaction, witnessed); snapshot comparison uses sorted-key canonical JSON (jsonb key-order independence);buildNodeSnapshotbuilds the root-first parent chain on the mutation's own transaction; outbox lifecycle is claim-by-CAS (guarded UPDATE pending→processing, oldest-first), complete, and release-to-pending (replayable failure) — no update/delete path for events (append-only at the application layer, honestly noted as such; the INSERT/SELECT-only DB role is a deployment concern outside this schema)./hierarch|compan|estate|platform[-_]?project/ipaths and that HierarchyModule declares no controllers — a hierarchy route landing before M4-1b-ii fails here first. Module wiring verified: providers only, registered in AppModule.apps/gateway/top level — OUTSIDEsrc/, invisible under the old perimeter, exactly the blind spot my #1459 control exposed — and the widened scanner FAILS with[prong i-symbol] … dbtest-perimeter-probe.ts: .insert(companies); removal restores green. The residual is closed for real, not on paper. WRITER_ALLOWLIST stays empty and correct (this repository writes only audit/outbox tables).Non-blocking:
CI note: 2923 running at review time (2922 queue-killed). Merge is fred's on this approval + 2923 terminal green.