Skip to content

feat: add Epic 172 deterministic filesystem grant recovery#197

Merged
Joncallim merged 42 commits into
mainfrom
architecture/issue-178-filesystem-grant-recovery
Jul 22, 2026
Merged

feat: add Epic 172 deterministic filesystem grant recovery#197
Joncallim merged 42 commits into
mainfrom
architecture/issue-178-filesystem-grant-recovery

Conversation

@Joncallim

@Joncallim Joncallim commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Closes #178.
Part of #172.
Requires the merged Step 0 bridge from #204.

What this completes

Forge now stores filesystem approvals as immutable history and changes authority only through preallocated current-decision pointers. Task and project decisions share one project-serialized revision, one root-binding revision, exact capability coverage, and a canonical project-to-package lock order. Denials, revocations, consumed one-time approvals, root repoints, and stale pointers converge to typed operator holds without misreporting the task as failed.

The branch adds the S3 migration, project- and task-level mutation services, approval/handoff reconciliation, explicit reapproval in the task UI, release-evidence completion, and real PostgreSQL race coverage. It does not enable protocol-v2 packet production or reopen Epic 172 ingress.

Safety and compatibility

  • Legacy approvals without a stored root binding remain non-issuable and require explicit reapproval; Forge never invents authority from current paths.
  • Grant decisions and runtime audits remain retained evidence. Ordinary E2E cleanup archives projects and does not require TRUNCATE rights.
  • All mutation routes pass the Step 0 fail-closed ingress gate before reading parameters, request bodies, database state, or filesystem state.
  • One-time reapproval inserts a fresh immutable decision and nonce, then advances the current pointer by compare-and-set.
  • Project-root changes preserve decision history, increment the binding revision, revoke issuance, and require an explicit approval for the new binding.
  • enabled_build_tests_green binds the exact enabled S3, S4, S5, and S6 build tuple; graph-node build tuples remain phase-specific.
  • Migration 0026 is ordered strictly after the integrated Step 0 migration chain.

Validation

  • 1,419 unit tests passed; the seven PostgreSQL-only tests remain deliberate skips in the generic command.
  • All seven signed release-recorder tests passed against real PostgreSQL with separate ordinary-app, evidence-writer, and transition principals.
  • All 13 mandatory S3 PostgreSQL concurrency scenarios passed with one worker and no skips.
  • The dedicated disabled-ingress/mixed-lock browser proof passed.
  • The fail-closed bridge passed: 17 cases ran and 53 mobile, PostgreSQL-gated, or signed-activation cases were intentionally skipped from the 70-case matrix.
  • Fresh non-superuser migrations from 0000 through 0026 passed on an isolated PostgreSQL database.
  • ESLint, TypeScript, production build, and git diff --check passed.
  • An independent full orthogonal review and exact-head GitHub CI must pass before merge.

Stack

PR #198 remains stacked on this branch until #197 merges. Afterward it will be restacked onto main and validated independently.

Copy link
Copy Markdown
Owner Author

Architecture review update

Completed two orthogonal architecture passes for #178.

Round 1 findings addressed

  • Narrowed project-wide reconciliation to always_allow/project revocation; package-local allow_once no longer scans unrelated packages.
  • Clarified that reconciliation composes with an endpoint-owned, already-locked project row and fresh nextMcpConfig rather than reacquiring locks.
  • Aligned the cross-slice lock order with [FEATURE] S4 — Specialist prompt and bounded context packet assembly with run evidence #179: project → tasks → packages → approval → audit claim.
  • Made post-commit Redis wake-up explicitly non-authoritative and idempotent.
  • Bounded historical failed-package recovery to an exact, versioned compatibility adapter.

Architecture updates are in docs/architecture/issue-178-review-amendments.md.

Round 2 result

No further architecture findings identified in the reviewed scope. Remaining uncertainty is implementation-level PostgreSQL concurrency and legacy-data behavior; the PR requires the listed real database tests before implementation can be considered correct.

This is an architecture-only PR and does not implement #178.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — round 3 findings (before correction)

Verdict: Needs architecture changes. Implementation must not proceed from this revision.

Blocker — the proposed hold is interpreted as terminal task failure

High — grant precedence has no durable ordering identity

  • Architecture: primary document lines 112-125 says a “later” project grant supersedes denial.
  • Current implementation: web/lib/mcps/admission.ts:406-413 compares application timestamps.
  • Failure: equal/skewed timestamps or reverse commit order can make an older decision win.
  • Invariant/downstream: deterministic exact-capability precedence; [FEATURE] S4 — Specialist prompt and bounded context packet assembly with run evidence #179 must snapshot the same authority.
  • Smallest correction: allocate a project-serialized monotonic grantDecisionRevision under the project lock for every package/project decision; compare revisions, use timestamps only for display, and fail closed for incomparable legacy denials until explicit re-decision.
  • Proof: equal/reversed timestamp and opposite lock/commit interleaving tests.

High — revocation/narrowing lacks a negative reconciliation transition

Medium — project-wide ownership still conflicts with the retained amendment

The primary service trigger includes package-local task_allow_once (lines 135-145), while the amendment correctly makes allow_once package-scoped. Fold that rule into the primary document and make the addendum explicitly historical.

Cross-PR and rollout requirements

  • S3 must publish marker v2 with a dual reader before S4 issuance relies on the hold state.
  • Global order is project → tasks ascending → packages ascending → grant approval/current-decision row → runtime audit claim.
  • Redis remains post-commit wake-up transport only.
  • Bounded marker arrays, fingerprints, and operator strings need explicit limits; no raw paths or secrets.

Inspected scope: PR/issue/ADR documents; current admission reader, handoff/orchestrator, both grant routes, schema/migration, and PostgreSQL test shape. Confidence: high. This is not proof of implementation correctness; executable PostgreSQL interleavings remain mandatory.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — round 7 findings before correction

The fresh integrated pass found one S3 contract precision issue:

  • Medium / blocking: FilesystemGrantBlockMetadata.grantPhase narrows the canonical EffectiveGrantState['phase'] vocabulary and omits proposed/not_issued, while the projection says it carries the canonical phase. The marker and downstream reader must either preserve the exact canonical phase plus consumed, or define one explicit lossless normalization. Known-but-unrepresentable states must not fall into reason parsing or an invented phase.

Required correction: align the marker type and ADR with the canonical reader and add exhaustive phase/consumed compatibility tests. This is an architecture finding only; no production code is being changed in this review pass.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 9 finding (before correction)

Medium / blocking-by-consistency: S3 restates the cross-slice “full” lock order as ending at packet artifact. Round 8 correctly added review-gate rows as the S4 tail because gate decisions must not lock backward into package/finalizer state. Leaving #178's authoritative lock-order text unchanged gives implementers two different “complete” orders.

Required correction: append review-gate rows after packet artifact in #178 and the S3 ADR summary, while keeping the explicit statement that ordinary S3 transactions stop at approval/decision rows. This changes no S3 mutation ownership; it only restores one canonical order.

Inspected final-stack head: 85156ab0fba8bf5309a97ef75dc3702adf9caa89. Finding posted before correction.

@Joncallim

Copy link
Copy Markdown
Owner Author

Round 9 addendum — S3 to S4 resolver suffix

The S3 one-time-reapproval handoff repeats the S4 resolver suffix as prior run → audit, but Round 8's complete-tuple rule also requires the exact packet artifact. Align this reference to prior run → audit → exact artifact and require canonical typed equality before the S4 marker can be cleared. S3 still owns only nonce rotation; S4 owns the downstream validation and mutation.

This is the #197 side of the consistency finding already recorded on #198, posted before correction here.

@Joncallim

Copy link
Copy Markdown
Owner Author

Round 9 addendum — complete shared lock tail

The final concurrency pass found that the order called “complete” still names one packet artifact but omits other run artifacts and issuance-recovery action rows that S4 locks/inserts. Before correction, #197's cross-slice reference must be aligned to the complete S4 tail:

agent runs ascending → runtime audits ascending → all artifact rows ascending by stable key → issuance-recovery action rows by unique key → review-gate rows ascending.

S3 still stops at approval in ordinary mutations. This is only the shared-order reference; S4 owns the suffix implementation.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 10 finding (before correction)

Medium / blocking-by-consistency: the S4 host-effect correction added run-scoped apply ledger entries plus integrity alert/resolution rows, but the cross-slice order repeated by S3 does not yet place them. The complete tail must be:

runtime audits → host-apply ledgers/entries → all artifacts → issuance-recovery actions → integrity alerts → integrity resolutions → review gates, each by the documented stable key.

Ordinary S3 mutations still stop at the grant decision. This updates only the shared suffix reference.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 10 addendum

Disposition: Changes requested before architecture readiness

High — S3 can clear the mandatory-review task barrier

S3 currently moves a task from running to approved whenever no sibling owns a live execution lease. S4 intentionally keeps the task running while any sibling is awaiting_review, even after leases clear. A grant denial, revocation, or reconciliation on another package can therefore misstate the task aggregate and bypass the mandatory-review barrier.

Required correction:

  • use the shared sibling-aware task reconciler;
  • allow running → approved only when there is neither a live sibling lease nor a sibling in awaiting_review;
  • converge after review decisions; and
  • add grant hold/revocation versus review-decision races in both transaction orderings.

Medium — S3's one-time handoff still names the old lock tail

The one-time reapproval handoff says S4 continues only through the prior run, audit, and exact packet artifact. Any mutating resolver must state the full applicable suffix: host ledger/entries → all artifacts → existing/new recovery action → integrity alerts/resolutions → review gates. Its task-wide review check must lock sibling packages in the canonical package order; “package-local” limits grant evaluation, not the mandatory task barrier.

The canonical top-level order is now correct; these individual paths must consume it without shorthand that lets an implementation skip rows.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 11 addendum

Severity: High
Disposition: Blocking

Legacy approval authority cannot be proven retrospectively

The current database has no immutable canonical-root fingerprint or root-binding revision captured when a pre-v2 approval was made. A host-binding command that validates today's configured path cannot prove that the approval originally authorized the same repository. Binding such an approval to revision 1 would manufacture authority.

Required correction: every legacy filesystem approval without a stored root-binding revision remains non-issuable after project-root binding and requires explicit operator reapproval. The binding command may bind the project, never upgrade approval authority. Remove every “proven unchanged root may bind legacy approval” exception from S3, ADR 0009, rollout, and S6.

The same pass also requires the S3 copy of the complete suffix to include the worker-instance row introduced by S4 after the protocol epoch.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 11 consistency finding

Severity: Low
Disposition: Blocking by closed-contract consistency

The S3 reconciliation input lists only task_always_allow, project_always_allow, and project_grant_revocation, while the same document requires a project-root repoint to invoke that service and revoke old-root authority.

Required correction: add a closed project_root_repoint trigger, or state normatively that repoint uses project_grant_revocation and define the reason/revision behavior. The clearer contract is a distinct trigger carrying the incremented root-binding revision while leaving the unrelated grant-decision revision ordering intact.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 12 mixed-version finding (before correction)

Medium — The epoch-1 root trigger cannot invoke S3's sole TypeScript reconciler

S4 says the PostgreSQL root-mutation trigger handles a legacy epoch-1 repoint by invoking S3's negative reconciler. S3 defines that reconciler as the sole server-side TypeScript service operating after the route has locked project -> tasks -> packages -> approvals. PostgreSQL cannot invoke it; acquiring those rows after the trigger has reached project -> epoch would reverse the global order.

Required correction: preserve S3 as the sole reconciliation policy and define an executable cutover bridge. A safe route is to keep the root trigger out of the live legacy expand window, disable project ingress, revoke/terminate v1 web credentials/sessions, drain old services, run the canonical S3 reconciliation/backfill under its normal order, then install/enable the root trigger and activate epoch 2 before v2 ingress. A trigger never implements or locks S3 rows. If mixed-version root writes are retained instead, every writer must call an application bridge and the trigger may validate only an exact transaction-local proof.

This is an architecture-contract finding, not an implementation finding.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 12 rollout wording finding (before correction)

Low — The cutover sequence does not name the epoch activation step unambiguously

S3's rollout enables S3 writers after root binding without explicitly placing epoch activation between binding and enablement. Downstream prose says “that command” after naming the binding command, even though a different command owns epoch activation.

Required correction: use one repeated exact sequence and command ownership: disable ingress/issuance -> revoke v1 credentials and drain -> reconcile/backfill and bind roots -> run protocol:activate-work-package-v2 -> enable registered S3/root writers -> enable packet issuance. project-roots:bind-v2 never advances the epoch.

This is architecture/rollout wording, not an implementation finding.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 12 root-revision finding (before correction)

Medium — “Initial revision 1” is incompatible with pre-bind root changes

The rollout says every project binds at initial root-binding revision 1, while downstream schema/compatibility text lets an unbound legacy repoint increment the same counter. A project can reach revision 2 before its first binding; resetting it to 1 breaks monotonicity, while requiring exactly 1 makes it unbindable.

Required correction: represent unbound state explicitly as revision 0 (or null, with one canonical choice). The binding procedure compare-and-set allocates the next positive revision and never resets/decrements it. Every later repoint allocates the next revision; repoint-away-and-back remains a new revision and never restores authority.

This is an architecture-contract finding, not an implementation finding.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 12 corrections

Corrected in 5af719ae:

  • unbound projects now use root-binding revision 0; initial bind and every repoint compare-and-set the next positive revision without reset or decrement;
  • the mixed-version design no longer asks a PostgreSQL trigger to invoke S3's TypeScript reconciler;
  • the cutover now disables ingress, revokes v1 credentials/sessions, drains old services, runs S3 reconciliation, binds roots, invokes the exact epoch activation command, then enables registered writers and issuance in order;
  • project-roots:bind-v2 is explicitly not an epoch activation command.

Validation: documentation-only diff; git diff --check passed; branch stack rebased and pushed. Round 13 will re-run all 12 passes from the integrated S6 head before readiness is declared.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 13 finding (before correction)

High — An S3-only hold can remain non-actionable forever

S3 deliberately leaves a task running when it places a filesystem-grant hold while a sibling lease or mandatory review exists, then delegates later running → approved convergence to S4's shared sibling-aware reconciler. The defined S4 service requires at least one S4 packet marker. Once the sibling clears, a task containing only the S3 filesystem marker is therefore rejected by the reconciler and can remain running indefinitely; Redis is only a wake transport and cannot repair the missing state-machine branch.

Required correction: define one shared operator-hold task-convergence service over the recognized marker union, including filesystem_grant and packet_issuance/integrity hold types. It applies one locked sibling predicate, mutates only task running → approved, preserves every package marker and blocked status, and creates no run/attempt. S3 terminalization, post-sibling/gate completion, startup recovery, and periodic discovery must all invoke or enqueue that same service. If S3 exposes a wrapper, it must call the same predicate rather than duplicate it.

This is an architecture-contract finding, not an implementation finding.

@Joncallim

Copy link
Copy Markdown
Owner Author

Integrated architecture review — Round 13 correction

Corrected in 2cda7d70.

  • Replaced the S4-marker-only convergence seam with one database-owned recognized operator-hold service covering S3 filesystem, S4 packet/integrity, and downstream local-effect holds.
  • Defined the exact project → task → sibling-package predicate, task-only running → approved mutation, marker preservation, direct callback, and startup/periodic database fallback.
  • Added S3-only, S4-only, mixed-hold, live-lease, and mandatory-review regression requirements.

Validation: docs-only; git diff --check passes; branch clean. Round 14 is running. PR remains draft and unmerged.

@Joncallim

Copy link
Copy Markdown
Owner Author

Round 14 finding — High / blocking — cross-PR lock/state contradiction (PR #197 primary; ADR/S4 and S6 downstream)

The S3 one-time reapproval resolver still uses the pre-generic-evidence lock suffix: epoch → worker instance → run → runtime audit → host ledger → artifacts → issuance actions → integrity → gates. It validates only packet audit/artifact/ledger state. The corrected S4 contract now requires authenticated instance → binding generation/rotation → hierarchy guard → run → generic local-run evidence → optional runtime audit → ledgers/artifacts/actions, and packet/local-effect markers may coexist without either owner clearing the other.

Evidence:

  • docs/architecture/issue-178-filesystem-grant-recovery.md:48-54,287-297,365-379,524-528
  • docs/adr/0009-mcp-admission-contract.md:1017-1029,1146-1453

Concrete failure: one-time reapproval can lock the runtime audit before the generic local record (the reverse of S4 recovery/finalization), or omit the generic record and clear packet_issuance / move blocked → ready while local_effect_recovery or working-tree/Git-control review remains unresolved. That creates a deadlock edge and can admit a new claim past the packet-independent barrier.

Required correction: use one canonical database lock order. S3 must prelock every sibling and the resolver must lock/validate binding generation, generic local-run evidence, working-tree and Git-control review fingerprints, local-effect marker/task projection, then the optional packet audit/artifact/actions. It may clear only the packet marker. If local-effect review or marker remains, the package stays blocked and no wake/new run occurs.

Required proof: race one-time reapproval against generic local recovery/finalization in both orderings; prove observed waits with no deadlock; cover coexisting packet/local markers; prove unresolved or mismatched generic evidence returns conflict without consuming the nonce or clearing markers; and prove exact local review preserves the task projection while only eligible packet state clears.

This finding was published before correction. Architecture only; no implementation or merge is authorized.

@Joncallim

Copy link
Copy Markdown
Owner Author

Round 14 finding — Medium / blocking — authoritative S3 rollout omits the mandatory expansion-window journal drain (PR #197 primary; #198/#200 activation impact)

S3 tells operators to add only revision/marker fields, disable and drain legacy services, reconcile projects that “may have changed,” and then run project-roots:bind-v2. S4 and ADR 0009 now require an expansion-phase project-root change journal, a post-session-termination watermark, and the exact expansion reconciliation command with every generation and deleted-row outcome proven before binding.

Evidence:

  • docs/architecture/issue-178-filesystem-grant-recovery.md:429-460,559-573
  • docs/architecture/issue-179-context-packet-evidence.md:1936-2001
  • docs/adr/0009-mcp-admission-contract.md:1688-1703

Cross-PR impact: an implementer following S3’s authoritative primary can skip the durable completeness proof assumed by S4 and S6 activation, leaving a legacy root mutation outside the binding scan.

Required correction: synchronize S3 schema, rollout, implementation order, stop conditions, and tests with the journal trigger, post-session watermark capture, exact project-roots:reconcile-expansion -- --through <generation> --actor <operator-id> --apply command, gap/deleted-row blockers, then bind, enable the root trigger, and activate.

Required proof: legacy insert, repoint, repoint-back, archive, and delete around the drain; an in-flight commit crossing the drain boundary; crash/resume reconciliation; binding and activation reject every missing generation/outcome.

This finding was published before correction. Architecture only; no implementation or merge is authorized.

@Joncallim

Copy link
Copy Markdown
Owner Author

Round 14 correction completed at fd32217. Resolved the stale recovery suffix by carrying the canonical all-mode lock order through authenticated instances, binding generation and rotation, hierarchy, generic local evidence and task projection, optional audit, ledgers, artifacts, actions, integrity rows, and gates. The one-time resolver now validates working-tree, Git-control, and Git-storage fingerprints, clears only the packet marker, and cannot wake work while a local barrier remains. The expansion-window journal, post-session watermark, and exact restartable reconciliation command are now mandatory cutover evidence. Validation: git diff --check, clean worktree, exact stacked ancestry, and integrated stale-contract scan.

@Joncallim

Copy link
Copy Markdown
Owner Author

Round 15 finding 15 — LOW / ADVISORY. #178 implementation order at docs/architecture/issue-178-filesystem-grant-recovery.md:582-585 tells the S3 slice to add #179 expansion-window root-change journal/trigger, while #181 lines 956-964 assigns the change journal and mixed-version schema to #179 and #178 otherwise calls it #179-owned. The rollout dependency is clear, but the per-PR migration owner is not; two implementers could create duplicate migrations or a dependency cycle. Required correction: name one owner. Prefer #179 owning the journal/trigger and command while #178 defines/consumes the canonical S3 reconciliation callback and remains compatible until the downstream schema lands, unless the journal is intentionally pulled forward and #179 explicitly consumes it without redefining it.

@Joncallim
Joncallim force-pushed the architecture/issue-178-filesystem-grant-recovery branch from fd32217 to c33a9b1 Compare July 15, 2026 05:35
@Joncallim

Copy link
Copy Markdown
Owner Author

Round 15 corrections are published at c33a9b1e.

Resolved the #178 ownership ambiguity: S3 owns only the decision schema and dual reader in this slice; #179 owns the expansion journal/trigger, reconciler command, and cutover checkpoint. This keeps the implementation order from assigning the same migration authority to two slices.

Validation: clean worktree, git diff --check, and the corrected stack ancestry starts from this head. The PR remains draft and unmerged.

@Joncallim

Copy link
Copy Markdown
Owner Author

Round 16 finding — High / blocking cross-PR contradiction

docs/architecture/issue-178-filesystem-grant-recovery.md still opens the expansion journal with an archive/hard-delete trigger and requires deleted-row outcomes (mixed-version rollout lines 447–466; regression item 14 around lines 561–563).

That now contradicts the corrected canonical S4/S6 rollout: the legacy filesystem-first delete route must be replaced and drained before retained-evidence expansion; evidence foreign keys must be RESTRICT|NO ACTION; and the database hard-delete guard must already make deletion impossible before the journal window begins.

Concrete failure mode: an implementation following #178 can deploy the journal while the current delete route still removes the repository before SQL and while cascade foreign keys erase retained task/run/audit/artifact evidence. The later journal outcome cannot restore either the filesystem or deleted evidence.

Affected invariant: retained immutable evidence and bridge-first migration safety.

Downstream: #179 migration/cutover, ADR 0009 S3/S4, and #181 mixed-version regression.

Smallest correction: make #178 explicitly depend on the pre-expansion bridge/drain/FK/guard checkpoint; journal only insert/root-update/archive generations; remove deleted-row test/command expectations; keep #179 as the sole journal implementation owner.

Implementation test: kill/race the old delete route around the filesystem/SQL boundary, prove the bridge conflicts/archives before fs.rm, prove direct SQL/cascades fail without evidence loss, then open the insert/update/archive journal and reconcile through its watermark.

This finding is published before edits. The PR remains draft and unmerged.

@Joncallim
Joncallim force-pushed the architecture/issue-178-filesystem-grant-recovery branch from c33a9b1 to 611b248 Compare July 15, 2026 05:53
@Joncallim

Copy link
Copy Markdown
Owner Author

Round 16 correction published in 611b248. The S3 rollout now requires #179’s bridge removal route, pre-bridge process/session drain, retention-safe foreign keys, and database hard-delete guard before the expansion journal opens. The journal and reconciliation watermark now truthfully cover insert/root-update/archive generations only; hard delete is already impossible. Migration tests separately prove the bridge/guard boundary and no longer claim a later journal can reconstruct deleted rows. Validation: git diff --check; architecture-only diff; no production implementation added. A fresh integrated orthogonal review will run after all four corrected slice heads are restacked.

1 similar comment
@Joncallim

Copy link
Copy Markdown
Owner Author

Round 16 correction published in 611b248. The S3 rollout now requires #179’s bridge removal route, pre-bridge process/session drain, retention-safe foreign keys, and database hard-delete guard before the expansion journal opens. The journal and reconciliation watermark now truthfully cover insert/root-update/archive generations only; hard delete is already impossible. Migration tests separately prove the bridge/guard boundary and no longer claim a later journal can reconstruct deleted rows. Validation: git diff --check; architecture-only diff; no production implementation added. A fresh integrated orthogonal review will run after all four corrected slice heads are restacked.

@Joncallim

Copy link
Copy Markdown
Owner Author

Round 17 orthogonal review finding — MEDIUM / blocking (global lock-order parity): #179’s canonical order names worker/root-writer instance rows and the combined local-run-evidence/task-projection source rows. #178’s normative restatements shorten those families, and ADR 0009 also omits explicit root-writer/task-projection members. An implementer following a shortened list can acquire a different suffix and invalidate the deadlock proof. Required correction: make #178 and ADR import/reference the one canonical #179 ordered list or enumerate the identical row families byte-for-byte. Add a static sequence-parity sentinel plus opposing-order PostgreSQL races across root-writer/generation and local-evidence/projection locks. Published before editing.

@Joncallim

Copy link
Copy Markdown
Owner Author

Round 17 orthogonal review finding — HIGH / blocking (cross-slice ownership and rollout): #178 implementation step 1 requires #179’s bridge/drain/retention-safe-FK/hard-delete-guard checkpoint before S3 schema, while #179 implementation step 1 requires #178 decision/operator-hold contracts to land before #179 bridge work. Neither slice can legally land first. Required correction: define a separately landable #179 prerequisite Step 0 containing only bridge route, drain, FK replacement, and database hard-delete guard; land it before #178, then land #178 S3, then the remaining #179 expansion/producers. Add a release-order manifest/fixture proving the acyclic order and retention seam. Published before editing.

@Joncallim
Joncallim force-pushed the architecture/issue-178-filesystem-grant-recovery branch from 98c5021 to 833e4a2 Compare July 17, 2026 08:47
@Joncallim Joncallim changed the title [architecture] Define deterministic filesystem grant recovery for #178 feat: add Epic 172 deterministic filesystem grant recovery Jul 17, 2026
…TP fixtures, legacy adapter, and logged 500

- Add 8-kind local projection head table with package preallocation (256 package limit, 2048 heads)
- Enforce head identity immutability, reject reassignment, deletion, and missing heads
- Persist full canonical requestedCapabilities in grant block metadata, not just missing subset
- Add time-bounded legacy adapter contract bridging old filesystem-grants to S3 types
- Add generic logged 500 error response utility with deduplication
- Add authenticated HTTP grant fixture tests with mutation schema validation
- Add S3 mutation-vs-claim contention E2E test from lower sibling
- Update Step 0 E2E inventory with new concurrency test entry

@Joncallim Joncallim left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultra-deep orthogonal review of DeepSeek's local S3 head ce9ae5a922073f530c00aa685258b253703c5b5f against this PR's contract head.

Verdict: blocked — 3 blocker clusters and 2 high-risk validation gaps. The local implementation commits are not on the remote PR, so the inline findings are anchored to the exact contract clauses they violate. Remote green checks do not validate this local head. No implementation files were changed.

Axes inspected: contract/architecture, PostgreSQL ownership/ACLs, concurrency/atomicity, runtime wiring, compatibility/error paths, and regression evidence. Confidence: high for the cited scope. This review is not proof of correctness; fresh and upgrade PG16 migrations plus the real authenticated HTTP/race suite remain required.

Comment thread docs/architecture/issue-178-filesystem-grant-recovery.md
Comment thread docs/architecture/issue-178-filesystem-grant-recovery.md
Comment thread docs/architecture/issue-178-filesystem-grant-recovery.md
Comment thread docs/architecture/issue-178-filesystem-grant-recovery.md
Joncallim and others added 14 commits July 17, 2026 23:32
- Add task_id column to work_package_local_projection_heads with FK to tasks
- Update preallocation trigger to populate task_id from work_packages.task_id
- Update backfill loop for task-scoped heads
- Add task_id index for per-task head queries
- Update fingerprint format: head:v1:{taskId}:{packageId}:{kind}:{index}
- Update TypeScript types and tests for task-level head identity
…ity marker tests, HTTP auth gate tests

- Fix projection head fingerprint CHECK for two-UUID task_id:package_id format
- Add PUBLIC SELECT grant for application role head reads; revoke ALL then grant SELECT
- Add full-capability marker unit tests verifying canonical three-capability set
- Add HTTP authentication gate tests for filesystem-grant and filesystem-grants routes
…pter

Resolves the remaining P1 compatibility/error-hardening thread on #197.

Error boundary:
- Add web/lib/http/route-error.ts with a branded PublicHttpError. Only 4xx
  errors constructed via publicHttpError() are trusted as public; an object
  that merely forges a numeric `status` can no longer impersonate one.
- Route both the project and task filesystem-grant PUT/GET handlers through
  respondToRouteError(). Typed 4xx surface their approved message; every
  unknown/untyped 5xx returns fixed generic text plus a fresh correlation id,
  with the detail retained only in the internal server log. No exception
  message, path, SQL, prompt, nonce, or token reaches the HTTP response.
- Reconciliation's httpError() now delegates to publicHttpError() so its
  approved 4xx operator messages stay public through the new boundary.

Legacy compatibility adapter:
- Introduce one canonical readFilesystemGrantBlockFromMetadata() reader and
  route the reconciliation and predicate call sites through it (removing the
  duplicate inline readers).
- legacyFilesystemGrantBlock() now preserves the real work-package id, reads
  the block from its canonical metadata.mcpGrantBlock location, reconstructs
  the canonical compatibility record and verifies its fingerprint from the
  canonical fields, and accepts an injected clock for deadline behavior.

Tests:
- epic-172-s3-route-error-boundary.test.ts: hostile nonce/path/prompt/SQL
  message is suppressed from both PUT routes and the boundary; typed 4xx and
  forged-status cases; correlation-id contract.
- epic-172-s3-legacy-adapter-block.test.ts: work-package-id preservation,
  canonical-location read, fixture-equivalent reconstruction/fingerprint, and
  injected-clock deadline.

Validation (web/): tsc --noEmit clean, eslint --max-warnings=0 clean on
changed files, git diff --check clean, full vitest 1458 passed / 7 pre-existing
PostgreSQL skips (+10 new tests, 0 new failures/skips), next build succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHZRmoaX4WLKt785UM9og3
- Use BigInt(7) instead of the 7n literal (target is below ES2020).
- Annotate the console.error mock-calls map parameter to satisfy noImplicitAny.

npx tsc --noEmit is now clean; both suites still pass (10 tests) and lint is
clean. Vitest/esbuild and next build did not surface these because only tsc
type-checks the __tests__ tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHZRmoaX4WLKt785UM9og3
Joncallim pushed a commit that referenced this pull request Jul 18, 2026
Propagates the #197 generic-error-boundary and canonical legacy grant-block
adapter fix upward without duplicating it in this slice.
Joncallim pushed a commit that referenced this pull request Jul 18, 2026
Propagates the #197 generic-error-boundary and canonical legacy grant-block
adapter fix upward without duplicating it in this slice.
Joncallim pushed a commit that referenced this pull request Jul 18, 2026
Propagates the #197 generic-error-boundary and canonical legacy grant-block
adapter fix upward without duplicating it in this slice.
@Joncallim
Joncallim marked this pull request as ready for review July 22, 2026 06:29

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 10215ca3b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/lib/mcps/filesystem-grant-reconciliation.ts Outdated
Comment thread web/db/migrations/meta/_journal.json
@Joncallim
Joncallim merged commit d435d23 into main Jul 22, 2026
3 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.

[FEATURE] S3 — Deterministic bounded filesystem context grant/denial recovery

2 participants