Skip to main content

What this chapter is

Every other chapter in this handbook describes what Parmana does today. This one describes how it got there: the real incidents, the corrections, the things that were tried and reversed, and the questions that remain genuinely open rather than quietly resolved. The two files this chapter draws from most, and which a reader should treat as the primary sources rather than this summary, are docs/VERIFICATION-GAPS.md (a dated incident and gap log going back to the original external audit) and docs/CLAIMS.md (the audit ledger, one entry per claim with cited evidence). This chapter is the narrative connecting them, written by reading both directly rather than trusting either blindly, and by direct, firsthand knowledge of the two most recent sessions, both from 2026-09-15/16.

Why a history chapter exists at all

A codebase that has been audited, found wanting, fixed, and re-audited multiple times has a choice about how to present that history: quietly rewrite the record so it looks like everything was always correct, or keep the record honest, including the mistakes. This codebase has consistently chosen the second option. docs/VERIFICATION-GAPS.md’s own structure, gaps organized by the session that found and closed them, with a dedicated “Decision required” section for things nobody has decided yet, exists specifically so a reader can tell the difference between “this was always solid” and “this broke once, here is exactly how, and here is what changed.” Chapter 17 of the older docs/book/ calls this citation integrity; whatever it is called, the discipline is the same: a claim without a file and line citation is not trusted, in this document or in the ones it draws from.

The broad shape of the project’s history

Rather than repeating every entry in docs/VERIFICATION-GAPS.md (over 2,900 lines as of this writing, covering audits from an original external review through at least seven distinct closure sessions), this section names the eras and points at where to read more:
  • The original external audit (dated July 2026 and earlier). The root-level docs/000-CONSTITUTION.md through docs/017-CONFORMANCE.md, plus ARCHITECTURE.md, SPECIFICATION.md, TRUST_MODEL.md, GUARANTEES.md, and PROOFS.md date from this period. They are frozen, not deleted, and should not be read as current, per docs/book/’s own README.
  • 2026-07-17 audit closeout session. Nine tasks closing findings from a July 16 external audit. See docs/VERIFICATION-GAPS.md’s “Gaps closed in the 2026-07-17 audit closeout session” section.
  • Phase 3D certification session. A further, larger closure pass; see that session’s own heading in docs/VERIFICATION-GAPS.md.
  • 2026-09-07 through 2026-09-14: a run of focused sessions covering production readiness, real-deployment verification, PQC production-readiness remediation, and execution-audit-trail hardening, each with its own dated section in docs/VERIFICATION-GAPS.md.
  • Gaps 35 through 40 (audit-artifact-driven, same day as the gaps-24-34 pass). These closed detection-then-prevention gaps in Policy Governance specifically: a five-minute periodic integrity re-check (gap 36), a legacy-policy backfill script (gap 39), and execution-time prevention itself (gap 40, PolicyExecutionVerifier, feature-flagged off by design at the time it was built, since every real production policy was still PENDING_APPROVAL). Chapter 7 of this handbook covers what changed about that precondition on 2026-09-16.
  • 2026-09-15/16: the AWS KMS migration and Policy Governance completion. The two most recent sessions, covered in detail below, since they are the freshest and least likely to already be reflected accurately anywhere else.

The 2026-09-15 AWS KMS gateway signing migration

The gateway’s signing key moved from a local file-based key to AWS KMS, authenticated via Vercel OIDC rather than static AWS credentials. Six real bugs were found and fixed in the course of this migration; docs/operations/2026-09-15-kms-migration-troubleshooting-guide.md is the detailed account. The one worth naming specifically here, because of how it was found: SignerKeyProviderAdapter (packages/crypto) had a divergence between the key used to sign and the key used to verify, a signing/verification key mismatch that would have silently produced signatures nothing could verify, had it reached production unnoticed. It is documented as G-48/G-49 in docs/VERIFICATION-GAPS.md, and Chapter 3 of this handbook covers the cryptography layer this fix lives in. Two other findings from the same session are directly relevant to chapters elsewhere in this handbook and are documented as their own tutorials rather than just prose: examples/tutorials/113-kms-key-id-resolution (AWS KMS rejects a bare logical key ID like "default"; a resolver maps it to the alias/ARN/key-ID shape KMS actually accepts) and examples/tutorials/115-per-limiter-rate-limit-stores (a shared rate-limit Store instance reused across two limiters, which express-rate-limit’s own contract disallows, including a documented correction to an initially wrong diagnosis: the observed 500 error was blamed on the store-reuse warning by proximity in a log, when the real cause was the separate signing/verification key divergence above; the correction is kept visible rather than silently rewritten, matching this codebase’s own stated discipline).

The night of 2026-09-16: closing the Policy Governance backfill

All ten real production policies had sat PENDING_APPROVAL since 2026-08-19, waiting on a genuinely distinct second human checker (SameActorCannotApproveOwnChangeError meant the original proposer could never approve their own proposals). This session provisioned that checker identity (policy-reviewer-1) and approved all ten, plus four more pre-existing policies that had never been proposed through the governance system at all. Chapter 7 covers the mechanism; this section covers what went wrong along the way, since every one of these was a genuine, previously-unexercised bug, not a repeat of something already known.
  1. EROFS on the very first live approval. PolicyChangeApprovalService.approve() writes the newly-approved policy content via PolicyRepository.save(). FilePolicyRepository does that with a local filesystem write. Vercel’s serverless Functions run on a read-only filesystem. Because no real approval had ever been attempted against a live, deployed instance before this session (every prior approval attempt in this codebase’s history was against local dev or test environments), this code path had simply never yet executed for real. The first time it did, in production, it failed immediately. Fixed with a new SupabasePolicyRepository (packages/policy/src/SupabasePolicyRepository.ts, migration 20260916060000_add_policies_table.sql), used whenever a real database is configured.
  2. An eager-construction bug in the first version of that fix. The first attempt at wiring SupabasePolicyRepository into packages/api/src/application.ts constructed it, and the PostgresPoolFactory singleton pool underneath it, at module import time rather than on first actual use. This violated a discipline already established elsewhere in this exact codebase (packages/api/src/repositories.ts’s lazyRepository, closing gap G-15, whose own comment states the same rule: importing a module must never itself construct a live database connection). The concrete symptom: constructing the pool eagerly meant it connected using whatever DATABASE_URL was live at import time, before examples/tutorials/89-readiness-probe’s third scenario (a deliberately unreachable database) could ever configure it differently, so that test’s NOT_READY assertion silently became READY. Fixed by making the construction lazy, mirroring repositories.ts’s own pattern exactly.
  3. PostgresPoolFactory hanging indefinitely against an unreachable database, discovered via the same tutorial, instead of failing fast. Fixed by adding connectionTimeoutMillis: 5000 to the pool configuration.
  4. Stale content in the original 2026-08-19 proposals. pending_policy_changes stores a one-time snapshot of proposed content, taken when the proposal is created, never re-checked against the live file while the proposal sits open. A month is an unusually long time for a proposal to remain open, and in that time the live policy files gained a documentation field (unboundSignalReasons, harmless) and, for two policies specifically (connector-capability, customer-refund), a functional field (boundSignals, which SignalIntentBinder needs to derive paymentAmount/refundAmount from a request’s own parameters rather than treating them as needing independent verification). Approving the stale content as originally proposed would have quietly persisted that gap. Caught during review, not by any automated check, and fixed by re-proposing and re-approving all fourteen policies with their current, correct content, see Chapter 7 for the verification step (scripts/verify-policy-changes-approved.ts --full-scan) that confirms the fix.
None of these four were regressions of something previously working. Each was code, or a data path, that had genuinely never been exercised against a real deployment before this session, caught the first time it actually ran.

Genuinely open questions

These are unresolved by design, not oversights. Each is documented in full, with real options weighed, in docs/VERIFICATION-GAPS.md’s “Decision required” section; this is a short index, not a replacement for reading the original entries.
  • D-2 (partially resolved). Hybrid/post-quantum signatures are wired for Trust Records and Receipts, opt-in via CRYPTO_MODE, but not for RuntimeAuthorizationSigner, gateway attestation signing, or connector-signing call sites. Deliberately deferred to a fast-follow milestone, not a rejection.
  • D-3. OverrideService exists, is presumably complete, but is not wired to any real HTTP route and is not exercised by tests beyond a storage-layer bypass. Undecided whether to wire it in or mark it explicitly future-scope.
  • D-4. HubSpot’s TRUSTED_APPROVAL_ISSUERS is an empty array by design (fail-closed), meaning every preAuthorizedForAmountChange claim currently fails closed since no real approver key has been provisioned. A proposed hardcoded dev key was explicitly rejected during review as inconsistent with this codebase’s own never-commit-a-private-key convention. Status: undecided.
  • D-5. Upstream authorization verification (NF-001-UPSTREAM-AUTHORIZATION-VERIFICATION.md) is explicit future scope: today’s authorization boundary is caller identity plus principal/capability scoping, not an independent signature on the Authority/ Authorization domain objects themselves. This becomes a real gap only for a delegation scenario (multi-party approval, an external OAuth/SAML/risk-service authorization source), and work has deliberately not started pending a real trigger.
  • D-6. The CI job that would prevent an unapproved policy file from merging (scripts/verify-policy-changes-approved.ts, run by .github/workflows/ci.yml’s verify-policy-approvals job) is real and fail-closed, but is not configured as a required GitHub branch-protection check. Attempting to enable it during a real session failed with 403 Upgrade to GitHub Pro or make this repository public, an externally imposed constraint (private-repository plan limits), not a gap in this codebase’s own logic. See docs/CLAIMS.md §2.26’s “Preventive Git-layer enforcement” entry for the full account.
  • The internal-vs-external policy authoring question. Should Parmana be the system of record for policy approval at all, or should policies be authored and approved in an external system, with Parmana staying strictly read-only/enforcement-only? Nothing in the codebase picks a side; the maker-checker system described in Chapter 7 exists because policy authoring was previously outside any governance surface at all, not because building it internally was compared against and preferred over the external alternative.
  • POLICY_EXECUTION_VERIFICATION_ENFORCED remains false. As of this session, every real production policy has a genuine, verified approval record, closing the precondition that originally justified leaving this flag off. The flag itself was deliberately left unchanged as part of closing that precondition; turning it on is reserved as its own, separate, later decision (docs/operations/policy-approval-runbook.md Part 5), not something to do as a side effect of finishing the backfill.

How to keep this chapter honest going forward

The same rule that produced this chapter applies to updating it: a new incident gets added here (or to docs/VERIFICATION-GAPS.md, whichever is the more precise home for it) with a real file citation and an honest account of what broke and why, not a summary that makes the codebase look more finished than it is. If a claim in this chapter is ever found to be wrong against the actual current source, the correction belongs here, visible, the same way the rate-limiter/signing-key misdiagnosis in the 2026-09-15 session is kept visible in examples/tutorials/115-per-limiter-rate-limit-stores/README.md rather than quietly rewritten.