@arnilo/prism 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +3 -1
  3. package/dist/agent-definitions.js +4 -1
  4. package/dist/agent-loops.js +45 -8
  5. package/dist/agent-session/helpers.js +2 -2
  6. package/dist/cache-helpers.d.ts +11 -0
  7. package/dist/cache-helpers.js +29 -5
  8. package/dist/cli-provider-add.js +2 -1
  9. package/dist/context-budget.js +9 -6
  10. package/dist/contracts-core/agent.d.ts +25 -2
  11. package/dist/contracts-core/provider.d.ts +2 -0
  12. package/dist/event-multiplexer.js +0 -4
  13. package/dist/index.d.ts +6 -4
  14. package/dist/index.js +5 -3
  15. package/dist/input.js +19 -11
  16. package/dist/node/session-store-jsonl.js +7 -3
  17. package/dist/providers/openai-compatible.js +2 -1
  18. package/dist/providers/openai-primitives.js +2 -1
  19. package/dist/providers/schema.d.ts +7 -0
  20. package/dist/providers/schema.js +25 -0
  21. package/dist/rpc.d.ts +4 -1
  22. package/dist/rpc.js +5 -1
  23. package/dist/testing/provider-conformance.d.ts +10 -0
  24. package/dist/testing/provider-conformance.js +37 -0
  25. package/dist/trim-trailing-slashes.d.ts +8 -0
  26. package/dist/trim-trailing-slashes.js +14 -0
  27. package/docs/0.1.0-readiness.md +1 -1
  28. package/docs/acp.md +1 -0
  29. package/docs/ag-ui.md +1 -0
  30. package/docs/agent-definitions.md +1 -1
  31. package/docs/agent-loops.md +3 -0
  32. package/docs/agent-session-runtime.md +1 -0
  33. package/docs/browser-automation.md +1 -0
  34. package/docs/coding-agent-tools.md +7 -1
  35. package/docs/compaction-and-retry.md +3 -0
  36. package/docs/compaction-observational-memory.md +47 -0
  37. package/docs/database-persistence.md +1 -1
  38. package/docs/extension-authoring.md +42 -0
  39. package/docs/graft.md +125 -0
  40. package/docs/host-security.md +3 -1
  41. package/docs/index.md +15 -13
  42. package/docs/input-and-prompt-assembly.md +11 -6
  43. package/docs/instruction-injection.md +1 -1
  44. package/docs/mcp-tools.md +1 -0
  45. package/docs/migration.md +10 -0
  46. package/docs/node-jsonl-session-store.md +1 -1
  47. package/docs/obscura.md +175 -0
  48. package/docs/observability.md +21 -1
  49. package/docs/performance.md +58 -4
  50. package/docs/ponytail.md +1 -1
  51. package/docs/provider-caching.md +13 -11
  52. package/docs/provider-conformance.md +6 -0
  53. package/docs/provider-packages.md +1 -1
  54. package/docs/provider-primitives.md +15 -2
  55. package/docs/providers/ai-sdk.md +1 -1
  56. package/docs/providers/anthropic.md +1 -1
  57. package/docs/providers/azure.md +1 -0
  58. package/docs/providers/bedrock.md +1 -0
  59. package/docs/providers/kimi.md +2 -1
  60. package/docs/providers/openai.md +19 -7
  61. package/docs/providers/opencode-go.md +3 -1
  62. package/docs/providers/openrouter.md +4 -3
  63. package/docs/providers/vertex.md +1 -0
  64. package/docs/public-contracts.md +3 -2
  65. package/docs/rag.md +55 -8
  66. package/docs/release-and-install.md +63 -7
  67. package/docs/server.md +1 -0
  68. package/docs/supervisors.md +10 -2
  69. package/docs/system-prompts.md +1 -1
  70. package/docs/tools.md +1 -1
  71. package/docs/web-tools.md +2 -0
  72. package/docs/wiki.md +154 -0
  73. package/docs/workflows.md +38 -4
  74. package/docs/working-and-semantic-memory.md +20 -0
  75. package/package.json +12 -5
  76. package/docs/api-page-template.md +0 -32
  77. package/docs/release-0.2.7-evidence.md +0 -514
@@ -1,32 +0,0 @@
1
- # <API name>
2
-
3
- ## What it does
4
- <Small description of what the API does.>
5
-
6
- ## When to use it
7
- <When an app/package/extension should use this API.>
8
-
9
- ## Inputs / request
10
- <Field table or typed shape.>
11
-
12
- ## Outputs / response / events
13
- <Field table, return type, events, or side effects.>
14
-
15
- ## Request/response example
16
- ```json
17
- <minimal example payload or config>
18
- ```
19
-
20
- ## Implementation example
21
- ```ts
22
- <minimal working TypeScript example>
23
- ```
24
-
25
- ## Extension and configuration notes
26
- <How extensions/plugins/config can replace or contribute behavior.>
27
-
28
- ## Security and performance notes
29
- <Secrets, permissions, trust boundaries, resource use, latency, limits.>
30
-
31
- ## Related APIs
32
- - `<API or page>`: <relationship>
@@ -1,514 +0,0 @@
1
- # Release 0.2.7 evidence — Task 0 scope freeze
2
-
3
- Captured: `2026-08-17T01:00:28+06:00`
4
- Repository: `/home/arn/Projects/prism`
5
- Baseline commit: `099540531a35303c11a22b582a72677c1a0978fc` (`0995405`)
6
- Baseline package line: `0.2.6`
7
- Target plan: `plans/027-Release-0-2-7-Enterprise-ERP-Production-Readiness.md`
8
-
9
- This is Task 0 scope evidence, not a 0.2.7 release claim. ERP production readiness remains blocked by the 0.3.0 live-service matrix.
10
-
11
- ## 1. Scope decision
12
-
13
- Task 0 freezes nine roadmap requirements, ten threats, owners, API ownership, atomicity/recovery invariants, demand decisions, budgets, and protected evidence rules.
14
-
15
- Chosen boundaries:
16
-
17
- - Reuse core `CheckpointStore`, `LeaseStore`, `ProductionPersistenceStore`, `ToolEffectStore`, `RunLedger`, `CredentialResolver`, `AgentIdentity`, `SecretRedactor`, and persistence lifecycle contracts.
18
- - Reuse `@arnilo/prism-workflows` checkpoints/leases/coordinator rather than creating a second workflow runner.
19
- - Reuse `@arnilo/prism-policy` decision/audit records and `PolicyExportSink`; add hash-chain behavior there rather than creating a second audit ledger.
20
- - Put PostgreSQL implementations in `@arnilo/prism-enterprise-postgres`, using existing migration locking, checksums, ownership columns, transaction helpers, and `pg`.
21
- - Keep business mutation transactions caller-owned. Prism can atomically write a business row and outbox row only when both use the same caller-owned `PoolClient` transaction.
22
- - Preserve at-least-once effects with stable idempotency keys and explicit unknown outcomes. No exactly-once claim.
23
- - Keep local active maps as abort/concurrency optimizations only. Durable state, CAS revisions, leases, and fencing tokens remain correctness authorities.
24
- - Treat supervisor/subagent output as untrusted work. Only host-verified `AgentIdentity` plus current policy can authorize an action or approval.
25
- - Do not implement Vault, AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager adapters without a named deployment consumer and protected test path.
26
-
27
- Rejected: generic ERP framework, new event bus/queue, second scheduler or workflow runtime, global registry, generic state manager, cloud SDK catalog, implicit ambient credential discovery, raw private-key storage, and terminology that implies exactly-once delivery.
28
-
29
- ## 2. Baseline checks
30
-
31
- ### Environment
32
-
33
- | Check | Result |
34
- | --- | --- |
35
- | Node | `v24.19.0` |
36
- | npm | `11.17.0` |
37
- | pnpm | unavailable; repository has no `pnpm-workspace.yaml` |
38
- | `pnpm verify` | unavailable; used repository npm equivalents |
39
- | Platform | Linux |
40
- | `PRISM_TEST_POSTGRES_URL` | unset; protected PostgreSQL legs blocked, not passed |
41
- | `PRISM_TEST_NATS_URL` | unset; protected real-NATS legs remain blocked |
42
- | Live provider/canary gates | unset; protected live legs remain blocked |
43
-
44
- ### Commands
45
-
46
- | Command | Result / measured evidence |
47
- | --- | --- |
48
- | `npm run build` | pass; core and all 49 workspaces emitted under build lock |
49
- | `npm run typecheck` | pass; `10.055s` wall time, including build, workspace typechecks, and examples |
50
- | `npm run lint` | pass; zero diagnostics |
51
- | `npm run format:check` | pass; 1,070 files, no fixes |
52
- | `npm test` run A | pass; 3,716 tests, 3,683 pass, 33 named protected skips, 0 failures |
53
- | `npm test` run B | one intermittent pre-existing browser fixture failure; 3,716 tests, 3,682 pass, 33 skips, 1 failure (`downloads=0; released=false`) |
54
- | Browser fixture re-probe | pass 3/3 via `node --test packages/browser/dist/__tests__/eval-fixtures.test.js` |
55
- | `npm run test:coverage` | pass; core 91.39% lines / 84.78% branches / 91.60% functions; 42 workspace suites reported; protected packages shown separately |
56
- | `npm run security:threat-suites` | pass; 50/50 |
57
- | `node --test scripts/budget-gate.test.mjs` | pass; 10/10 |
58
- | `npm audit --audit-level=moderate` | pass; 0 vulnerabilities |
59
- | `git ls-files -z \| xargs -0 node scripts/scan-secrets.mjs` | pass; 1,659 files, 0 findings |
60
- | `node scripts/package-truth.mjs --out /tmp/phase27-package-truth.json` | pass; semantic output matches checked-in package truth |
61
-
62
- The intermittent browser result is recorded rather than converted into a green result. It is outside Task 0's ERP scope and must be resolved or explicitly quarantined before Task 10 release closeout. The `3,716` totals are the pre-freeze baseline; the new six-test Task 0 contract is run separately by `node --test scripts/phase27-freeze.test.mjs` and passes 6/6.
63
-
64
- ### Package and dependency baseline
65
-
66
- `package.json`, `scripts/package-truth.json`, and `package-lock.json` are the sources of truth.
67
-
68
- | Metric | Frozen baseline | 0.2.7 budget |
69
- | --- | ---: | ---: |
70
- | Publishable manifests | 50 | 50 |
71
- | Workspace manifests | 49 | 49 |
72
- | Provider packages | 14 | no catalog expansion |
73
- | `prism-*` family/profile packages | 9 | no new package |
74
- | Capability packages | 26 | no new package |
75
- | Packages with runtime dependency entries | 16 | no new runtime dependency names |
76
- | Runtime dependency entries across manifests | 65 | delta `+0` by default |
77
- | Internal Prism runtime dependency edges | 52 | delta `+0` by default |
78
- | Root runtime dependencies | 0 | remains 0 |
79
- | Lockfile package entries | 345 | no dependency churn without a Task 0 amendment |
80
- | Peer policy | exact `0.2.6`, atomic upgrade | exact `0.2.7` only at Task 10 |
81
-
82
- Current root exports and all workspace package exports remain unchanged during Task 0. Future public changes are additive and must update the owning package API report and docs.
83
-
84
- ## 3. Existing primitive inventory
85
-
86
- | Concern | Existing primitive and evidence | 0.2.7 use / confirmed gap | Owner |
87
- | --- | --- | --- | --- |
88
- | Durable checkpoints | Core `CheckpointStore` in `src/contracts-core/persistence.ts`; version, expected-version CAS, fencing token, bounded list/value, ownership | Saga, approval/recovery cursors, ERP records; no business outbox or saga compensation ledger | Core persistence + owning package |
89
- | Distributed leases | Core `LeaseStore` in `src/contracts-core/persistence.ts`; acquire/renew/release/get, monotonic fencing | HA claims, saga ownership, dispatch/recovery; no new lease protocol | Core persistence + SRE |
90
- | Business/session persistence | `ProductionPersistenceStore`, `SessionRecord.version`, `appendSession(expectedVersion)`, Postgres/SQLite adapters | Caller-owned transaction boundary; no outbox/inbox tables | Session/database owners |
91
- | External effect idempotency | Core `ToolEffectStore`; enterprise `prism_tool_effects` migration 002; work idempotency states `in_progress`, `completed`, `failed_retryable`, `failed_terminal`, `unknown` | Reuse unknown/reconciliation semantics; does not atomically pair arbitrary business rows with outbound intent | Work/effect owners |
92
- | Enterprise PostgreSQL | `@arnilo/prism-enterprise-postgres`; migrations 001 enterprise state, 002 tool effects, 003 router reservations; advisory lock, checksums, catalog validation, bound SQL, owner scoping | Add migration 004 messaging and migration 005 approvals only after implementation tests; no generic key/value table | Enterprise Postgres |
93
- | Workflow orchestration | `defineWorkflow`, `runWorkflow`, `resumeWorkflow`, `enqueueWorkflow`, `createWorkflowCoordinator`; checkpoint adapter over core store; lease-backed coordinator | Add only a bounded linear saga/compensation state machine; no second runner/scheduler | Workflows |
94
- | Workflow active state | `packages/workflows/src/active-runs.ts`, scheduler `active`/`activeSessions` maps | In-process abort and concurrency optimization; durable queued/running checkpoint plus lease is authority; Task 6 must test restart without these maps | Workflows/SRE |
95
- | ACP/process state | ACP `AcpSessionStore` and bounded `activeRun` ref; process recovery namespace over CheckpointStore + LeaseStore; attach-if-attested or `unknown` | Reuse recovery/fencing; inspect remaining correctness dependencies in Task 6 | ACP + coding-agent |
96
- | Policy decisions | `@arnilo/prism-policy` `PolicyEvaluator`, `PolicyDecisionStore`, outcomes `allow/deny/modify/approval`, verified actor refs, bounded redacted records | Existing approval outcome is not multi-party quorum/SoD; add an additive approval contract/store | Policy/security governance |
97
- | Audit export | `exportPolicyDecisions`, cursor pages, optional `PolicyExportSink`; no KMS/WORM/SIEM SDK | Add canonical hash-chain/signed manifest exporter with explicit host sinks and cursor CAS; do not replace existing export | Compliance/policy |
98
- | Legal hold/retention | Core `PersistenceLifecycleStore`; holds, retention, export-under-hold, quotas; hold wins over retention; resource kinds include `audit` and `connector_operation` | Reuse hold checks for audit/export/DR; no new hold engine | Data governance |
99
- | Identity/authority | Core `AgentIdentity`, `assertIdentityActive`, ownership projection, narrowing/propagation; supervisor propagates identity and AND-composes policy | Only verified host actors may approve; model/subagent output cannot become an approver | Security/governance |
100
- | Credentials | Core `CredentialResolver`, explicit resolver order, caller-supplied env object only; credentials-node encrypted file, keychain, host-KMS callbacks, OAuth/OIDC, local `vault.ts` encrypted vault | No cloud secret-manager adapter has a checked-in consumer or protected path; all four providers deferred | Platform security |
101
- | Supervisor/subagents | `createSupervisor`, child allow-list, narrowed limits/permissions, identity/effect-store propagation, durable nested approval mapping | Reuse for journey correlation only; never authorization source | Supervisor/release |
102
- | Evaluations | `defineDataset`, `defineScorer`, `scoreRun`, `runExperiment`, threshold assertions, redacted bounded reports, optional durable EvaluationStore | Add structured ERP invariant facts/scorers in existing eval package only; no model judge in authorization path | Evals/release |
103
- | Protected journeys | `scripts/e2e-enterprise-journey.test.mjs` covers identity → OPA → agent/events → approval → OpenAPI effect → artifact; phase26 coding journey and release skip manifest exist | Neither covers ERP outbox/saga/quorum/hash-chain/DR/classification as one journey; Task 9 adds the protected composition | Release owner |
104
-
105
- ## 4. Process-local registry review
106
-
107
- | Registry | Location | Classification | Task 6 rule |
108
- | --- | --- | --- | --- |
109
- | Workflow active-run map | `packages/workflows/src/active-runs.ts` | Optimization/abort registry; explicitly non-durable, bounded at 512 | Restart/status/resume/cancel must use checkpoint + lease, never this map as authority |
110
- | Workflow coordinator `active` map | `packages/workflows/src/coordinator.ts` | Local worker bookkeeping and concurrency cap | Durable queued/running checkpoint and lease own correctness |
111
- | Workflow `activeSessions` map | `packages/workflows/src/run/scheduler.ts` | Local abort fan-out only | Missing map must not lose durable state or cause duplicate side effect |
112
- | ACP `sessions` map | `packages/ag-ui/src/acp/agent/core.ts` | Live binding/controller registry; optional host `AcpSessionStore` restores state | Restore re-resolves binding; persisted `activeRun`/lifecycle owns recovery |
113
- | A2A live task map | `packages/ag-ui/src/a2a-server.ts` | Bounded live-only registry; durable event source/task resolver is optional | Live-only behavior must be documented; durable task status/replay cannot depend on map |
114
- | Managed process `sessions` map | `packages/coding-agent/src/process/sessions.ts` | Live handles/output/waiters; durable metadata is separate | Never serialize handles; recovery uses checkpoint + lease + attested backend |
115
- | Server active-run counter / RPC map | `packages/server/src/handler/core.ts`, `src/rpc.ts` | Admission/concurrency bookkeeping scoped to handler/loop | No durable status or authorization may depend on counters/maps |
116
- | Core event source stream maps | `src/agent-event-source.ts`, NATS adapter | Per-process stream/subscriber caches; durable source is database/NATS | Cursor/ownership source remains durable; stale local cache must fail closed |
117
-
118
- Other `Map` instances are bounded request-local parsers, registries, caches, or in-memory reference stores; they are not enterprise correctness authorities. Task 6 rechecks every candidate caller before changing code.
119
-
120
- ## 5. Roadmap requirement gap matrix
121
-
122
- | Requirement | Existing coverage | Exact gap | Frozen owner and intended implementation |
123
- | --- | --- | --- | --- |
124
- | Transactional outbox/inbox | `@arnilo/prism-enterprise-postgres` `erpMessaging`; caller-owned PoolClient append/record; tenant/message and tenant/consumer/message primary keys; bounded dispatcher with claim-token/version CAS | Implemented in migration 004; external delivery remains at-least-once and remote atomicity is not claimed | Enterprise Postgres + ERP app; `packages/enterprise-postgres/src/erp-messaging.ts`, migration `004_erp_messaging`, protected integration/plan tests |
125
- | Saga compensation/reconciliation | Workflow DAG checkpoints, leases, retries, suspend/resume, replay | No durable forward-step/compensation ledger, reverse compensation cursor, or unknown-outcome reconcile handler | Workflows; `packages/workflows/src/saga.ts`, tests/docs; compose over existing checkpoint/lease APIs |
126
- | Multi-party/SoD approvals | Verified identity, policy approval outcome, shared tool-decision batches, supervisor nested approval attribution | No durable request/role/quorum record, requester separation, expiry/revocation/delegation, or atomic grant consumption | Policy + enterprise Postgres; `packages/policy/src/approvals.ts`, `packages/enterprise-postgres/src/approvals.ts`, migration `005_erp_approvals` |
127
- | Tamper-evident audit export | Redacted policy ledger, cursor export, optional WORM-shaped sink, legal hold | No canonical hash chain, signed manifest, immutable acknowledgement, SIEM replay state, or independent verifier | Policy/compliance; `packages/policy/src/audit-export.ts`, verifier script; host owns WORM/SIEM/KMS |
128
- | Secret-manager adapters | Explicit `CredentialResolver`; encrypted file/keychain/KMS/OAuth/OIDC adapters; no ambient env reads | No Vault/AWS/Azure/GCP cloud source and no named consumer/protected endpoint | Platform security; all four remain deferred and absent from public exports |
129
- | HA registries/recovery | Checkpoint CAS, lease fencing, workflow coordinator, ACP/process recovery, durable event sources | Some correctness callers still have local-only live maps; no one ERP two-replica proof across all new state | Core/workflows/server/ACP/enterprise Postgres; root-cause fixes only, no new registry package |
130
- | Backup/restore/rollback evidence | Checksummed migrations; existing Postgres restore/operator docs and historical benchmark | No 0.2.7 ERP fixture backup, restore invariant manifest, migration rollback rehearsal, PITR/RPO/RTO result | Database operations; `scripts/phase27-dr.test.mjs`, `docs/disaster-recovery.md` |
131
- | Field classification/redaction | Exact-secret `SecretRedactor`, policy payload allow-list, provider/tool/artifact/telemetry redaction, legal hold | No field labels/actions shared across prompt/tool/artifact/audit/telemetry/persistence/export boundaries; unknown policy is not centrally fail-closed | Core neutral contract + existing boundary owners; `src/field-policy.ts` only, no second policy language |
132
- | ERP release journey | Existing packed enterprise/coding journeys, policy/effect/audit/eval seams | No one deterministic journey proving all ERP invariants, negative cases, failover, restore, and subagent non-authority | Release/evals; `packages/evals/src/erp-invariants.ts` plus `scripts/phase27-erp-journey.test.mjs` |
133
-
134
- ## 6. API and persistence freeze
135
-
136
- These are the only planned public/API deltas. Names are frozen before Task 1; any change requires a plan amendment and evidence update.
137
-
138
- | Surface | Owning package | Frozen additive API | Persistence/compatibility |
139
- | --- | --- | --- | --- |
140
- | Messaging | `@arnilo/prism-enterprise-postgres` | `createPostgresErpMessaging`; `ErpOutboxStore.append(client, input)`; `ErpInboxStore.record(client, input)`; bounded `ErpOutboxDispatcher.claim/acknowledge/retry/markUnknown/deadLetter/replay` | Migration 004; caller-owned transaction for append/record; existing tables untouched; at-least-once |
141
- | Saga | `@arnilo/prism-workflows` | `defineSaga`; `runSaga`; `resumeSaga`; `SagaDefinition`, `SagaStep`, `SagaRunResult` | Existing generic `CheckpointStore`/`LeaseStore`; versioned workflow namespace; no workflow SQL table |
142
- | Approval | `@arnilo/prism-policy` + enterprise Postgres | `ApprovalRequest`, `ApprovalRequirement`, `ApprovalDecision`, `ApprovalRecord`, `ApprovalStore` | `createPostgresApprovalStore`; migration 005; policy actor refs are verified identities only |
143
- | Audit export | `@arnilo/prism-policy` | `createAuditExporter`; `verifyAuditBatch`; `AuditSigner`, `AuditWormSink`, `AuditSiemSink`, `AuditCursorStore` | Existing policy records remain readable; cursor advances only after required sink acknowledgement |
144
- | Classification | core `@arnilo/prism` | `FieldPolicy`, `FieldPolicyInput`, `FieldPolicyDecision`, `applyFieldPolicy` | Optional for existing callers; protected profile supplies fail-closed policy; no persisted schema change in Task 0 |
145
- | ERP evals | `@arnilo/prism-evals` | `erpInvariantDataset`, `createErpInvariantScorers` | Structured facts only; scorer failure is a hard gate, not a weighted-average loss |
146
- | Secret managers | none until demand | No adapter/export frozen; approved adapter must implement existing `CredentialResolver.resolve(request)` | No ambient env/metadata/CLI discovery; provider-specific demand must be recorded first |
147
- | HA/DR | existing stores/scripts | No new public API planned | Existing CAS/lease/migration contracts; scripts and runbooks only |
148
-
149
- No raw callback, executable handler, credential value, private key, provider response, prompt, or unbounded payload is persisted in any new record.
150
-
151
- ## 7. Frozen invariants
152
-
153
- ### Atomicity and delivery
154
-
155
- 1. A caller transaction that rolls back leaves both business mutation and outbox intent absent.
156
- 2. A committed business mutation has its outbox intent in the same transaction; Prism does not claim remote atomicity.
157
- 3. Inbox marker and consumer-local mutation share one transaction; duplicate delivery returns the existing marker and does not repeat the mutation.
158
- 4. Delivery is at-least-once. Crash after remote send is `unknown`/retryable and repeats the stable message/idempotency key; no exactly-once assertion.
159
- 5. Claims, acknowledgements, retries, dead letters, and replay are bounded, tenant-scoped, revision/fence-checked, and never silently drop work.
160
-
161
- ### Saga and approval
162
-
163
- 6. Only durably succeeded forward steps are compensated, in reverse completion order; unknown external outcomes reconcile before retry; unresolved compensation reaches manual intervention.
164
- 7. An approval grant requires active verified actors, distinct quorum principals, requester/approver separation, current policy/action digest, and unexpired/non-revoked authority.
165
- 8. Delegation can narrow role/action/tenant/expiry only. Subagent/model text cannot satisfy a quorum or become a verified actor.
166
- 9. Grant consumption and the protected local mutation/outbox append are atomic when they share the caller transaction. Post-consumption revocation creates evidence/reconciliation; it does not rewrite history.
167
-
168
- ### Audit, secrets, and data
169
-
170
- 10. Export bytes are canonical and hash-chained; record reorder/insertion/deletion/truncation/byte/signature mutation fails independent verification.
171
- 11. Required immutable sink acknowledgement precedes cursor advancement. SIEM failure is replayable and cannot create a second logical chain entry.
172
- 12. Tenant, legal-hold, and redaction policy are applied before export hashing. Hold blocks deletion but never broadens access.
173
- 13. Secret resolution is explicit and late-bound. No new adapter reads `process.env`, metadata services, home-directory files, CLI credential state, or subprocess ambient credentials implicitly.
174
- 14. Unknown classification/policy errors fail closed at outbound, persistence, telemetry, artifact, audit, and export boundaries; denied values never appear in errors or evidence.
175
-
176
- ### HA and recovery
177
-
178
- 15. Durable state is the authority after process loss; local maps may optimize abort/lookup only.
179
- 16. One lease/fence wins a mutation. Stale owner, stale checkpoint revision, stale cursor, or stale approval revision cannot commit.
180
- 17. Cursors may replay work after crash but cannot regress or skip. Recovery of an unprovable external process is `unknown`, never fabricated success/exit.
181
- 18. A two-replica kill/failover drill proves one committed transition and tenant-scoped status/cancel/resume without the dead process registry.
182
-
183
- ## 8. Threat-to-test mapping
184
-
185
- | Threat | Frozen control | Required test/evidence |
186
- | --- | --- | --- |
187
- | `ERP-T1` mutation/intent split | Same-client transaction and rollback inspection | Task 1 Postgres transaction test; Task 9 journey |
188
- | `ERP-T2` duplicate/reordered/poison delivery | Inbox uniqueness, stable idempotency, bounded retry/dead letter, unknown state | Task 1 duplicate/tenant/poison tests; Task 9 duplicate journey |
189
- | `ERP-T3` repeated/out-of-order compensation | Durable step ledger, reverse completed steps, reconcile before retry | Task 2 kill/restart/100-step/manual-intervention tests |
190
- | `ERP-T4` forged/self/stale approval | Verified actors, quorum, SoD, expiry/revocation/delegation, CAS | Task 3 negative/concurrent approval tests; Task 9 subagent denial |
191
- | `ERP-T5` audit tamper/cross-tenant/delete | Canonical chain, signed manifest, WORM ack, hold/redaction/tenant checks | Task 4 mutation/partial sink/verifier tests; Task 9 tamper/hold cases |
192
- | `ERP-T6` secret ambient read/leak/unsafe fetch | Explicit resolver/client, endpoint policy, bounds, redaction | Task 5 contract/protected tests; repository ambient-read scan |
193
- | `ERP-T7` split brain/stale cursor/local registry loss | Lease fencing, checkpoint/cursor CAS, durable status | Task 6 two-replica kill/stale-owner/cursor tests |
194
- | `ERP-T8` unusable backup/rollback/no RPO/RTO | Restore checksums/counts, migration rehearsal, PITR evidence, measured timings | Task 7 disposable Postgres DR drill |
195
- | `ERP-T9` mislabeled/unknown field escape | Field policy before every boundary, bounded traversal, fail-closed default | Task 8 canary matrix and boundary spies |
196
- | `ERP-T10` hold/tenant/subagent authority bypass | Durable hold checks, owner-scoped keys, host verified actor, fact scorers | Task 3/4/8/9 negative journey and eval hard gates |
197
-
198
- ## 9. Operational ownership and protected gates
199
-
200
- | Surface | Owner | Evidence required |
201
- | --- | --- | --- |
202
- | Outbox/inbox/dead letters | ERP application + database operations | Queue depth/age, retry/dead-letter/replay runbook, migration and transaction evidence; Task 1 migration/transaction/claim evidence is recorded below |
203
- | Saga reconciliation | ERP application | Stuck-step query, safe retry, compensation escalation, manual-resolution audit |
204
- | Approval policy | Security/governance | Role/quorum/delegation review, expiry/revocation audit, no break-glass bypass |
205
- | Audit export/legal hold | Compliance | Signer/key rotation, chain verifier, WORM/SIEM outage/replay, retention and hold evidence |
206
- | Secret adapters | Platform security | Provider bootstrap/rotation/outage/endpoint policy; only after demand record |
207
- | HA | SRE | Lease contention, failover time, stale-owner rejection, cursor rules, no force unlock |
208
- | Backup/restore | Database operations | Disposable restore, rollback decision, PITR, RPO/RTO, checksums/counts, artifact protection |
209
- | Classification | Data governance | Taxonomy, boundary matrix, unknown-field behavior, policy review, canary results |
210
- | Journey/evals | Release owner | Structured report, hard thresholds, protected environment, 0.3.0 blocker |
211
-
212
- Missing required protected infrastructure is `blocked`, never a passing skip. Current Task 0 protected state:
213
-
214
- - PostgreSQL durable baseline: blocked because `PRISM_TEST_POSTGRES_URL` is unset; inherited benchmark evidence remains available for comparison.
215
- - Real NATS, live providers, live canaries, Docker/browser/forge journey legs: protected and not claimed by this task.
216
- - Backup/PITR baseline: recorded at Task 0 as pending measurement; Task 7 measured it — see Task 7 result (section 19) for artifact bytes, duration, and RPO/RTO instead of an invented SLO.
217
-
218
- ## 10. Frozen budgets
219
-
220
- | Surface | Frozen target |
221
- | --- | --- |
222
- | Package/dependency | 50 manifests; root remains dependency-free; zero new runtime dependency names; no new package or SDK catalog |
223
- | Outbox/inbox | 1,000-row protected backlog claim p95 `<100 ms`; indexed bounded claim; no unbounded scan; measured p95 7.827 ms at 100-row claim pages |
224
- | Saga/approval | maximum 100 saga steps / 100 approval actors per request; bounded attempts; no recursive retry loop |
225
- | Audit | maximum 1,000 records or 10 MiB resident per batch; linear hash/sign work; page-bounded retries |
226
- | Classification input fixtures | prompt 4,164 B; tool args 2,114 B; tool result 9,095 B; artifact metadata 3,692 B; audit record 4,243 B; telemetry 1,760 B; 100-record export page 10,726 B; target overhead `<10%` |
227
- | Classification traversal | bounded depth/key/byte limits; cycles fail closed; no duplicate serialization |
228
- | Lease/failover | existing workflow/recovery default lease TTL 30,000 ms; hard TTL 300,000 ms; protected failover target is lease TTL + 5 s; no hot-loop retry |
229
- | Existing Postgres comparison | historical protected max p95 28.410 ms (`routerCircuitContention`) under 50/100 ms phase ceilings; 14 query plans, all indexed/no sequential scan; this is comparison evidence, not ERP evidence |
230
- | Storage comparison | historical 0.0.23 fixture 348,800 rows / 251,977,728 bytes before cleanup; new ERP storage must record actual migration/backup size |
231
- | Backup/restore | no invented target before Task 7; measured RPO/RTO and backup size/time are hard evidence gates |
232
- | Journey/evals | deterministic bounded facts; any missing security/atomicity/DR fact fails, not averages away |
233
-
234
- ## 11. Documentation and API references reviewed
235
-
236
- Project-local references:
237
-
238
- - `roadmap.md` 0.2.7 requirements and 0.3.0 blocker.
239
- - `docs/public-contracts.md`, `docs/database-persistence.md`, `docs/enterprise-postgres-state.md`, `docs/workflows.md`, `docs/workflow-orchestration-primitives.md`.
240
- - `docs/policy-and-audit.md`, `docs/credentials-and-redaction.md`, `docs/credential-storage.md`, `docs/host-security.md`.
241
- - `docs/agent-identity.md`, `docs/supervisors.md`, `docs/evaluations.md`, `docs/release-and-install.md`.
242
- - `docs/_evidence/phase22-primitive-review.md` and `docs/_evidence/phase26-primitive-review.md` for CAS/lease/registry/freeze precedents.
243
- - `scripts/package-truth.mjs`, `scripts/package-truth.json`, `scripts/budgets.json`, `scripts/phase26-baseline.json`, `scripts/phase26-freeze-manifest.json`, `scripts/release-skip-manifest.mjs`.
244
-
245
- External references pinned for implementation tasks:
246
-
247
- - PostgreSQL transaction isolation and explicit locks: `https://www.postgresql.org/docs/current/transaction-iso.html`, `https://www.postgresql.org/docs/current/explicit-locking.html`, `https://www.postgresql.org/docs/current/sql-select.html`.
248
- - PostgreSQL backup/restore/PITR: `https://www.postgresql.org/docs/current/backup.html`, `https://www.postgresql.org/docs/current/continuous-archiving.html`.
249
- - Separation of duties guidance: `https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final` (AC-5).
250
- - Canonical JSON: `https://www.rfc-editor.org/rfc/rfc8785.html`.
251
- - Node cryptographic primitives: `https://nodejs.org/download/release/latest-v20.x/docs/api/crypto.html`.
252
- - Provider-specific secret APIs are demand-gated; no adapter is approved from documentation alone.
253
-
254
- ## 12. Task 0 result
255
-
256
- **Complete — scope frozen.** Machine-checkable freeze data is in `scripts/phase27-freeze-manifest.json`; contract test is `scripts/phase27-freeze.test.mjs`. Task 1 may start against this scope, subject to the explicit protected-evidence and known-flake notes above.
257
-
258
- No 0.2.7 implementation or public API was added by Task 0.
259
-
260
- ## 13. Task 1 result — transactional outbox/inbox and bounded dispatch
261
-
262
- **Complete — implementation and protected PostgreSQL evidence.**
263
-
264
- Implemented:
265
-
266
- - `packages/enterprise-postgres/src/ddl.ts`: migration 004 tables/indexes for `prism_erp_outbox` and `prism_erp_inbox`.
267
- - `packages/enterprise-postgres/src/migrations.ts`: checksum/catalog verification for migration `004_erp_messaging`.
268
- - `packages/enterprise-postgres/src/erp-messaging.ts`: caller-owned transactional append/record, tenant-scoped `SKIP LOCKED` claim, lease expiry to `unknown`, claim-token/version CAS acknowledgement/retry/unknown/dead-letter/replay, bounded payload/errors/batches/attempts, and verified operator requirement for manual actions.
269
- - `packages/enterprise-postgres/src/types.ts`, `enterprise.ts`, and `index.ts`: additive `PostgresErpMessaging` surface and `state.erpMessaging` composition.
270
- - `packages/enterprise-postgres/src/__tests__/erp-messaging.integration.test.ts`: rollback, idempotent/conflicting append, duplicate inbox, concurrent claims, stale fencing, retry, unknown/replay, dead-letter, lease recovery, tenant isolation, bounds, and plan checks.
271
- - Existing migration/package tests updated; migration history remains forward-only and prior checksums remain unchanged.
272
-
273
- Protected command:
274
-
275
- ```sh
276
- PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres --workspace @arnilo/prism-enterprise-postgres
277
- ```
278
-
279
- Result: `37` tests passed, `0` failed, `0` skipped in the protected package run (PostgreSQL `16-alpine`, Node `24.19.0`, Linux). ERP suite: `4` tests passed; migration suite: `5` passed. The full network-free `npm test` gate then passed with `3,722` tests, `3,689` pass, `33` protected skips, and `0` failures.
280
-
281
- Performance evidence: 10 tenants × 1,000 queued rows, 30 claim samples, batch size 100: p50 `5.999 ms`, p95 `7.827 ms`, p99 `8.066 ms`; representative `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)` used `prism_erp_outbox_claim_idx`, no sequential scan, plan time `0.087 ms`. This meets the frozen `<100 ms` p95 target for this protected environment.
282
-
283
- Compatibility/security notes:
284
-
285
- - Existing migration 001/002/003 DDL and checksums remain unchanged; migration 004 is additive.
286
- - No new package or runtime dependency was added.
287
- - Duplicate append with same tenant/message/topic/payload is idempotent; conflicting reuse fails closed.
288
- - Business mutation plus outbox append and inbox marker plus consumer mutation remain caller-owned transactions.
289
- - Remote delivery remains at-least-once; no exactly-once claim. Expired leases become `unknown` and require authorized replay.
290
- - Manual dead-letter/replay requires a non-empty audit reference and host-verified identity matching the tenant.
291
- - Post-implementation security checks: threat suites 50/50, `npm audit --audit-level=moderate` 0 vulnerabilities, secret scan 0 findings.
292
-
293
- ## 14. Task 2 result — durable saga compensation and reconciliation
294
-
295
- **Complete — existing workflow checkpoint/lease primitives only.**
296
-
297
- Implemented:
298
-
299
- - `packages/workflows/src/saga.ts`: frozen `defineSaga`/`runSaga`/`resumeSaga` surface with `SagaDefinition`, `SagaStep`, and `SagaRunResult`; ordered forward cursor, reverse completed-step compensation cursor, bounded attempts, stable tenant-scoped operation keys, unknown-outcome reconciliation, checkpoint CAS, lease heartbeat/fencing, verified manual resolution, and metadata-only transition events.
300
- - Saga state persists through the existing `WorkflowCheckpointAdapter` over core `CheckpointStore` using a private surrogate workflow checkpoint record; no saga SQL table, scheduler, queue, package, or runtime was added.
301
- - `packages/workflows/src/index.ts`: additive public exports.
302
- - `packages/workflows/src/__tests__/saga.test.ts`: reverse compensation, unknown reconciliation, stable operation keys, stale lease/fence takeover, redaction before compensation, verified manual resolution, revision mismatch, 100-step bounded execution, tenant isolation, and definition bounds.
303
- - `packages/workflows/README.md`, `docs/workflows.md`, and `docs/index.md`: saga lifecycle, reconciliation contract, compensation warning, operator/manual-resolution ownership, and API examples.
304
-
305
- Protected command:
306
-
307
- ```sh
308
- npm run build --workspace @arnilo/prism-workflows
309
- npm run typecheck --workspace @arnilo/prism-workflows
310
- npm test --workspace @arnilo/prism-workflows
311
- ```
312
-
313
- Result: build/typecheck passed; workflow package tests passed `76/76` with `0` failures. Coverage passed; `@arnilo/prism-workflows` lines `86.83%` against its `85.56%` threshold. A disposable PostgreSQL 16-alpine smoke through `createPostgresPersistence` → `createWorkflowCheckpoints` also passed run/compensate/resume; no connection value is retained.
314
-
315
- Frozen controls exercised: maximum 100 steps, default 3 / hard 10 attempts, default 30-second / hard 300-second lease TTL, tenant-scoped checkpoint/lease keys, revision/CAS/fencing checks on every transition, redacted bounded JSON state, and manual resolution requiring active verified tenant identity plus exact checkpoint version, reason, and audit reference.
316
-
317
- Compensation remains at-least-once and is not database rollback. Host handlers own remote idempotency; `unknown` outcomes never auto-replay without `reconcile`, and unresolved outcomes stop at `manual_intervention`.
318
- ## 15. Task 3 result — multi-party and separation-of-duties approvals
319
-
320
- **Complete — generic policy types in `@arnilo/prism-policy`, durable storage in enterprise PostgreSQL (migration `005_erp_approvals`).**
321
-
322
- Implemented:
323
-
324
- - `packages/policy/src/approvals.ts`: frozen `ApprovalRequest`, `ApprovalRequirement`, `ApprovalDecision`, `ApprovalRecord`, and `ApprovalStore` plus host `ApprovalAuthority` (store-level `policyRevision` pin + `resolveRoles(identity, request)`), pure `evaluateApproval` quorum evaluation, shared pure transition validation (`prepareApprovalCreate`/`prepareApprovalDecision`/`prepareApprovalRevoke`/`prepareApprovalConsume`), and the `createMemoryApprovalStore` reference adapter. Caps (frozen `approvalMaxActors` 100): 100 requirements, 100 distinct-principal quorum, 100 decisions, delegation depth 0–8, JSON byte caps.
325
- - `packages/enterprise-postgres/src/approvals.ts`: `createPostgresApprovalStore` — one row per request (`prism_erp_approvals`), `decide`/`revoke` lock the row `FOR UPDATE`, append the decision JSONB, recompute bounded quorum, and revision-check the terminal transition in one transaction; `consume` verifies tenant/action digest/expiry/policy revision/revision and joins the caller-owned transaction via the structural `client` seam (no `pg` dependency in policy).
326
- - `packages/enterprise-postgres/src/ddl.ts` + `src/migrations.ts`: `buildEnterpriseMigration005Ddl` (12 tables / 13 indexes catalog-verified; status CHECK on `pending/approved/rejected/revoked/consumed`).
327
- - SoD tests: `packages/policy/src/__tests__/approvals.test.ts` (requester separation, distinct-principal quorum + idempotent duplicates, wrong role/tenant/digest/policy revision/stale revision/expiry/rejection/revocation, bounded delegation preserving the full chain, unverified identities never become actors, caps) and `packages/enterprise-postgres/src/__tests__/approvals.integration.test.ts` (atomic consume + host action commit/rollback, concurrent final votes → one terminal transition, full lifecycle + tenant isolation).
328
- - `packages/policy/README.md`, `docs/policy-and-audit.md`, `packages/enterprise-postgres/README.md`, `docs/enterprise-postgres-state.md`, and `docs/index.md`: role/quorum table, delegation limits, revocation semantics, operator audit queries, and the explicit stance that hosts own identity verification/role source and Prism does not certify NIST compliance (AC-5 is control guidance only).
329
-
330
- Protected command:
331
-
332
- ```sh
333
- npm run build --workspace @arnilo/prism-policy
334
- npm test --workspace @arnilo/prism-policy
335
- PRISM_TEST_POSTGRES_URL="$DATABASE_URL" node --test packages/enterprise-postgres/dist/__tests__/approvals.integration.test.js
336
- PRISM_TEST_POSTGRES_URL="$DATABASE_URL" node --test packages/enterprise-postgres/dist/__tests__/*.test.js
337
- ```
338
-
339
- Result: policy package `32/32` (`0` failures, including 7 new approval tests); enterprise-postgres unit suite `10/10` without Postgres. Protected PostgreSQL 16-alpine run: `40/40` across all enterprise-postgres integration suites with `0` failures — grant consumption + host action commit/rollback together in one transaction, exactly one terminal transition under concurrent final votes (the loser fails closed with a stale revision), and lifecycle/isolation coverage. No connection value or credential retained.
340
-
341
- Full root gate: `npm run test:coverage` passed — `@arnilo/prism-policy` lines `92.55%` against its `90.78%` threshold; full `npm test` passed `3,739` tests, `3,706` pass, `33` protected skips, `0` failures; typecheck/lint/format `docs.test.ts` 140/140 and phase27 freeze contract 6/6 clean.
342
-
343
- Frozen controls exercised: verified `AgentIdentity` actors only (never model/tool/subagent claims), requester/approver separation, distinct-principal quorum, idempotent duplicate votes, stale/expired/revoked/unapproved release denial, bounded delegation chains that cannot widen role/tenant/action/expiry, policy-revision pins invalidating outstanding approvals, revision CAS on every transition, and the immutable decision provenance inside the locked request row.
344
- ## 16. Task 4 result — signed, hash-chained audit export
345
-
346
- **Complete — additive export/sink/signer/verifier contracts in `@arnilo/prism-policy`; hosts own WORM/SIEM/KMS transport.**
347
-
348
- Implemented:
349
-
350
- - `packages/policy/src/canonical.ts`: RFC 8785 (JCS) canonicalizer — sorted keys with prefix-shortest-first order, ECMAScript shortest number round-trip, `-0` collapsed, lowercase control escapes; rejects non-finite numbers, BigInt, undefined, functions, symbols, and cyclic graphs instead of coercing them.
351
- - `packages/policy/src/audit-export.ts`: frozen `createAuditExporter`/`verifyAuditBatch` with `AuditSigner` (host `sign(bytes)` + `keyId`; no raw keys), required `AuditWormSink` (ack `{batchId, digest}` must match or the cursor never moves), optional `AuditSiemSink` (failure records a bounded replayable `siemPending` status without duplicating chain entries; `retryPendingSiem` replays with host-supplied artifact bytes), CAS `AuditCursorStore` seam + `createMemoryAuditCursorStore`. One-shot page tokens with a bounded in-memory uncommitted-page replay, so failed batches reuse the same stable batch id instead of re-reading (and dropping) the page. Records are hash-chained envelopes: digest = SHA-256 of canonical bytes covering schemaVersion, tenant, sequence, prior digest, legal-hold flag, redaction provenance, and the record. The signed artifact embeds the canonical manifest document plus `{algorithm, keyId, value}` signature.
352
- - `packages/policy/src/index.ts`: additive public exports.
353
- - `packages/policy/src/__tests__/audit-export.test.ts`: 13 test cases — JCS vectors; byte-identical batches and stable retry keys; full tamper matrix (reorder, deletion, insertion, byte mutation, prior-digest mutation, signature mutation, truncation, empty artifact); WORM failure and lying-ack leave the cursor untouched; SIEM pending → replay without duplicate chain entries and digest-checked replay; per-tenant chain/cursor isolation; legal hold + redaction before hashing with `{path, reason}` provenance only; signer error and key-rotation boundary; 10,000 records streamed in 10 bounded (≤1,000-record) batches with a continuous verifiable chain; budget enforcement; expected first/last sequence and tenant validation.
354
- - `scripts/verify-audit-export.mjs`: standalone verifier CLI (`--batch --public-key --tenant [--previous-digest --first --last]`) that validates canonical bytes, record chain, manifest signature, cursor continuity, tenant, and sequence boundaries; no ledger, sink, or key beyond the public verification key.
355
- - `docs/audit-export.md`, `docs/index.md`, `docs.test.ts` apiPages, and `packages/policy/README.md`: manifest sample, verification command, sink responsibilities, key rotation (`signature.keyId`), outage/replay, legal hold, redaction, frozen caps, and non-certification language.
356
-
357
- Protected command:
358
-
359
- ```sh
360
- npm run build --workspace @arnilo/prism-policy
361
- npm test --workspace @arnilo/prism-policy
362
- npm run lint
363
- npm run format:check
364
- node scripts/verify-audit-export.mjs --batch ./acme-000001.json --public-key ./verification.pem --tenant acme
365
- ```
366
-
367
- Result: policy package `45/45` (`0` failures) — 13 new audit-export/canonical cases on top of the prior 32 — with typecheck/lint/format clean. Full `npm test` passed `3,752` tests / `3,719` pass / `33` protected skips / `0` failures; `npm run test:coverage` passed with `@arnilo/prism-policy` lines `92.66%` against its `90.78%` threshold; docs.test `140/140` (apiPages gained `docs/audit-export.md`); phase27 freeze contract green. No connection value, credential, private key, or tenant data is retained anywhere in the implementation or evidence.
368
- ## 17. Task 5 result — demanded secret-manager adapters
369
-
370
- **Complete — demand gate held: nothing was demanded, so nothing was implemented.** This is the plan's explicit scope control, not a skip.
371
-
372
- Demand result (`scripts/phase27-freeze-manifest.json` `demand` registry, unchanged from Task 0; `consumer` is null for all four):
373
-
374
- | Provider | Demand status | Consumer | Implemented |
375
- |---|---|---|---|
376
- | HashiCorp Vault | deferred | null | **not implemented—no demand** |
377
- | AWS Secrets Manager | deferred | null | **not implemented—no demand** |
378
- | Azure Key Vault | deferred | null | **not implemented—no demand** |
379
- | GCP Secret Manager | deferred | null | **not implemented—no demand** |
380
-
381
- Machine-enforced by `scripts/phase27-demand-gate.mjs` (wired into `scripts/phase27-freeze.test.mjs`, runs on every `npm test`): fails with named violations if any provider loses its deferral, any demanded-provider adapter module or `create*CredentialResolver` factory appears in `@arnilo/prism-credentials-node`, or the package's source tree gains ambient discovery.
382
-
383
- Ambient-read audit (threat `ERP-T6` — "secret ambient read/leak/unsafe fetch"; repository scan over `packages/credentials-node/src`, the adapter home package):
384
-
385
- - Metadata-service references (`169.254.169.254`, `169.254.170.2`, `100.100.100.200`, `metadata.google.internal`): **zero hits**.
386
- - Home-directory/CLI credential paths (`~/.aws`, `.azure`, `.config/gcloud`): **zero hits**.
387
- - Subprocess credential-helper CLI invocation (`aws`/`gcloud`/`az`/`vault` via `spawn`/`exec*`): **zero hits**.
388
- - `process.env` reads: **zero hits** in the package source.
389
- - Result: no adapter, placeholder, factory, or ambient read exists; invariant 13 ("secret resolution is explicit and late-bound") holds across the package.
390
-
391
- Protected evidence: no provider-protected tests exist because no provider is demanded — a skipped-Vault/AWS/Azure/GCP row would not satisfy a demanded adapter gate by design ("missing config reports skipped evidence and cannot satisfy a demanded adapter gate"), and no placeholder modules or public API names were added for undemanded providers. The frozen `secret-managers` API-ownership row ("no adapter or export until demand") is unchanged, and the demand registry is the single source that Task 5 can re-open from.
392
-
393
- Protected command:
394
-
395
- ```sh
396
- node scripts/phase27-demand-gate.mjs
397
- node --test scripts/phase27-freeze.test.mjs
398
- ```
399
-
400
- Result: demand gate exits `0` against the current tree, phase27 freeze contract green, full `npm test` green, coverage gates green. No cloud SDK, no `process.env`/metadata/CLI discovery, and no public API delta for this task.
401
- ## 18. Task 6 result — HA registries, leases, cursors, failover, split-brain
402
-
403
- **Complete — protected two-replica drill against real PostgreSQL; zero public API delta.**
404
-
405
- Implemented:
406
-
407
- - `scripts/phase27-ha-worker.mjs` — one worker process = one replica with its own pool. Modes: `start` (acquire lease, heartbeat renewals, reserve step, commit the charge side effect as an idempotent ERP-outbox append, signal the crash window, then hold until SIGKILL), `resume` (durable inspect of lease/checkpoint/outbox with the previous owner dead, bounded jittered acquisition after lease expiry, idempotent replay of the uncertain commit, finish, release), `stale` (old-fence + old-revision write must fail closed; old-token renewal must return null), `race` (two replicas contend; exactly one owner).
408
- - `scripts/phase27-ha.test.mjs` — orchestrated drill: barrier marker files, real `SIGKILL` inside the crash window (effect committed, cursor not advanced), lease-expiry wait, failover-time measurement against the frozen ceiling (`ttlMs + 5000`), foreign-tenant isolation (reads, saves, and lease takeover all reject with ownership mismatch), skip-ahead/regression rejection, and simultaneous-acquisition one-owner assertion. Skips (never passes) without `PRISM_TEST_POSTGRES_URL`; wired into the root `npm test` list after the phase27 freeze test.
409
- - `docs/operations.md` — HA/failover runbook (new docs page, docs.test apiPages + index nav): local-registry limits, lease/fence model, uncertain-commit replay rule, failover procedure, metrics (owner/fence/lease-age metadata only, no tenant payloads), and the explicit prohibition on any manual "force unlock" that bypasses fencing.
410
- - `docs/_evidence/phase27-ha-evidence.json` — recorded run: exact two-replica commands, process IDs (A, B, stale probe, both racers), injected failure, timing (`failoverMs` 4097 vs ceiling 9000), and durable final states (checkpoint version 4, cursor 3, outbox exactly 1 message).
411
-
412
- Protected drill result (recorded 2026-08-17, `postgres:16-alpine` local stand-in, `PRISM_TEST_POSTGRES_URL`):
413
-
414
- - Worker A: acquired lease (fencing token 1), created checkpoint v1 (cursor 0), reserved (v2, cursor 1), committed the charge effect into `prism_erp_outbox` (count 1), signaled, was SIGKILLed before its cursor-advance save.
415
- - Durable state with A dead: lease row present and active right after the kill; checkpoint readable (cursor 1, the uncertain commit); acquisition blocked until expiry; old-token renewal denied; stale revision+fence save rejected with `ERR_PRISM_CHECKPOINT_CONFLICT`; skip-ahead with a stale fence rejected ("Stale checkpoint fencing token").
416
- - Worker B: acquired after expiry (fencing token 2, failover 4097 ms ≤ 9000 ms ceiling), renewed (heartbeat), replayed the charge step idempotently — outbox count stayed 1 (`ON CONFLICT DO NOTHING` on the stable `messageId`) — advanced v3 (cursor 2) and v4 (cursor 3), released. Exactly one committed transition; no duplicate effect; cursor never skipped or regressed.
417
- - Race: two simultaneous acquisitions produced exactly one owner. Isolation: a foreign tenant's checkpoint read, checkpoint save, and lease takeover all failed closed with ownership-mismatch errors; the foreign tenant operated its own keys normally.
418
-
419
- Protected command:
420
-
421
- ```sh
422
- `PRISM_TEST_POSTGRES_URL` set to the protected connection string, then: `node --test scripts/phase27-ha.test.mjs`
423
- ```
424
-
425
- Scope control: no public API was added (the drill exercises the existing `LeaseStore`/`CheckpointStore`/outbox contracts); no lease package, consensus protocol, or "force unlock" exists; local in-memory registries remain optional fast paths — the workflow coordinator/saga/ACP recovery semantics were additionally proven in Task 2 (100-step durable cursor recovery) and plan 026 (cross-replica ACP process recovery with durable `activeRun` refs). No connection value, command output, or credential is retained in the evidence.
426
- ## 19. Task 7 result — backup, restore, migration rollback, PITR, and DR rehearsal
427
-
428
- **Complete — protected drill against real PostgreSQL using only standard tools; zero public API delta.**
429
-
430
- Implemented:
431
-
432
- - `scripts/phase27-dr.test.mjs` — the protected drill. Standard PostgreSQL tools only (`pg_dump -F c`, `pg_restore`, `pg_basebackup`, `psql`, `pg_ctl`), executed inside the source and PITR containers (the host has no PostgreSQL client binaries); the script orchestrates commands and verification and implements no backup format, scheduler, or encryption. Guards: source/target databases must differ, target host must be loopback, target database name must not match production patterns, `--confirm-target prism_dr_restore` is mandatory, target/rollback databases and source schemas must not pre-exist (fail closed on dirty state), artifact dir needs ≥ 512 MB headroom, and source/target/PITR passwords plus a seeded secret canary must never appear in the manifest, logs, or console.
433
- - Seed leg: representative multi-tenant 0.2.7 state through the real store APIs — conversation session entries, workflow/saga/ACP/conversation checkpoints, active saga leases, legal holds, tenant quotas, policy decisions (append-only audit ledger), evaluations, work idempotency, tool effects (completed + pending), model-router budget reservations, ERP outbox (2 messages per tenant, caller-owned transactions), ERP inbox, and approvals (one approved, one pending per tenant).
434
- - Backup/restore leg: `pg_dump -F c` → `createdb` the confirmed target → `pg_restore --no-owner --no-privileges` → per-table row counts and content digests (md5 of ordered rows) equal the source for all 14 seeded tables; per-tenant outbox rows survive.
435
- - Migration leg: raw DDL builders for migrations 001–003 with the module-computed registry checksums build the 0.2.6-era schema; legacy rows seeded through the real stores; `applyEnterpriseMigrations` applies 004/005 — old rows preserved, new tables initialized empty, full 5-migration history recorded. Rollback rehearsal: the pre-upgrade backup restored into a fresh database reproduces the pre-upgrade rows exactly and excludes the 0.2.7 tables; documented loss window is writes between the pre-upgrade backup and the rollback restore (restore is the last resort; forward repair is preferred in production).
436
- - PITR leg: WAL-archived cluster (`wal_level=replica`, `archive_mode=on`, `archive_command='cp %p /wal_archive/%f'`), `pg_basebackup` taken before two marker writes, WAL switched and archived after each, recovered instance started with `recovery.signal`, a bounded `restore_command`, and `recovery_target_time` between the two writes with `recovery_target_action=pause`; the earlier marker is present and the later marker is absent.
437
- - `docs/disaster-recovery.md` — DR runbook (new docs page, docs.test apiPages + index nav): what it does, guards, inputs (source/target/PITR + confirmation token), outputs, guarded commands, the rollback decision tree, measured RPO/RTO, legal-hold verification, quarterly re-drill template, and explicit non-claims (managed backup/encryption/cross-region replication stay operator-owned).
438
- - `docs/_evidence/phase27-dr-evidence.json` — the recorded run: seed counts, backup artifact bytes/SHA-256/duration, restore duration, per-table count/digest match booleans, migration numbers, PITR target/markers/recovery duration, redacted URLs, redaction assertions, and the `currentBackupRestore: "measured: …"` status.
439
-
440
- Protected drill result (recorded 2026-08-17, disposable `postgres:16-alpine` containers):
441
-
442
- - Backup: 108,291 bytes in 122 ms; SHA-256 `bd47292f…`; archive lists the entire database (234 archive entries incl. system objects).
443
- - Restore into the confirmed disposable target: 382 ms; all 14 seeded table counts and content digests equal the source; per-tenant outbox (2+2 messages) preserved; legal-hold rows and quota records preserved with identical digests.
444
- - Migration: 0.2.6-era schema (3 migrations) with 1 legacy policy decision, 1 completed work item, 1 tool effect → upgrade applies migrations 004/005 via the real runner → 5-migration history, legacy rows preserved, new tables empty → rollback from the pre-upgrade backup restores exactly 1 policy row and zero 0.2.7 tables.
445
- - PITR: base backup taken before markers m1/m2 (sub-second apart), recovery target between them, recovered instance paused with m1 present and m2 absent; recovery 1.2 s; RPO ≈ 0 s / RTO ≈ 1 s measured in this disposable environment (not claimed as production guarantees).
446
- - Redaction: all three connection URLs stored with masked passwords; the source/target/PITR passwords and the seeded `SUPER-SECRET-CANARY` never appear in the evidence, logs, or console.
447
-
448
- Protected command (no connection value retained; the freeze gate forbids `PRISM_*` env name paired with a connection string in evidence):
449
-
450
- ```sh
451
- # ensure the source and PITR containers mount a shared dir at /dr
452
- node scripts/phase27-dr.test.mjs \
453
- --source "$PRISM_TEST_POSTGRES_URL" \
454
- --target "$PRISM_DR_TARGET_URL" \
455
- --confirm-target prism_dr_restore # PRISM_PITR_URL must be set for the PITR leg
456
- ```
457
-
458
- Scope control: zero public API delta (standard tools + existing runners only); no custom backup format, scheduler, encryption, or restore orchestration service; no exactly-once claim anywhere in the backup/restore path; managed backup, encryption, cross-region replication, and retention scheduling are explicitly operator-owned and not claimed.
459
- ## 20. Task 8 result — field-level classification and fail-closed redaction at data boundaries
460
-
461
- **Complete — dependency-free contract in `@arnilo/prism` + seams at egress, audit, and telemetry; ERP-T9 matrix green; frozen overhead cap measured.**
462
-
463
- Implemented:
464
-
465
- - `src/field-policy.ts` — `applyFieldPolicy(value, policy, options)`: walks JSON-like values (plain objects/arrays/primitives; `Date`/`RegExp`/buffers pass through; `Map`/`Set` normalize; functions/bigints/symbols/class instances/cycles throw `FieldPolicyError` — fail closed, never stringified). Decisions: `allow` keeps, `deny` replaces with `[DENIED]`, `redact` masks string leaves with `[REDACTED]` preserving container shape, `tokenize` replaces string leaves with a deterministic `tok_<hash>` (stable per path+value across runs, safe for audit chains). Sparse copy: untouched subtrees share the input reference; the input is never mutated. Bounds (frozen): depth 32, keys 10,000, string budget 1,000,000 chars, optional wall-clock budget (`maxPolicyMs`). Policy exceptions, invalid decisions, cycles, unsupported values, and budget breaches throw with the path and error class only — values are never echoed. `createProtectedFieldPolicy()` is the fail-closed default: unknown labels deny on outbound/persisted destinations (`prompt`/`tool`/`artifact`/`audit`/`telemetry`/`export`/`persistence`), inbound unknowns pass, `secret`/`financial` deny, `personal` redacts, `token` tokenizes, `public` passes; labels come exclusively from the boundary owner's `labelFor` hints — no automatic sensitive-data discovery, no global registry, no decorators, no second policy language. `createAuditFieldRedactor(policy, {labelFor, tenantId, purpose})` adapts the contract to the audit-export `redact` hook so transformation precedes canonical hashing and only `{path, reason}` provenance survives. `ALLOW_FIELD_POLICY`, `FieldPolicyError`, `FIELD_POLICY_LIMITS` exported for composition.
466
- - Seams: `redactMessage` / `redactProviderRequest` / `redactAgentEvent` / `redactSessionEntry` / `redactRunLedgerRecord` take optional `(fieldPolicy, destination, labelFor)` — existing hardcoded secret redaction runs first (defense in depth), then the policy pass; without a policy the functions are the identity fast path (existing callers unchanged). `createOpenTelemetryInstrumentation({ fieldPolicy })` filters/masks exported span attributes and events — `allow` keeps, `redact` masks, `deny` drops, `tokenize` hashes, and a policy error drops the attribute without ever echoing the value.
467
- - ERP-T9 matrix `src/__tests__/field-policy.test.ts` (25/25): canaries (secret/personal/financial/tenant-owned) across all seven destinations are allowed/redacted/tokenized/denied as configured and the canary string appears nowhere in transformed output or provenance; unknown labels, policy throws, invalid decisions, timeout (`maxPolicyMs`), excessive depth/keys/bytes, cycles, and unsupported values all fail closed; tenant mismatch denies; audit transform-before-hash deterministic + provenance shape `{path, reason}` only; legal hold does not broaden export; compat callers without policy are untouched; sparse-copy allocation asserts.
468
- - OTel seam tests (2 new in `packages/observability-opentelemetry`, 14/14): denied attribute dropped, redacted attribute masked, policy error drops the attribute with no value echo.
469
- - `docs/data-classification.md` (new docs page, docs.test apiPages + `docs/index.md` nav, all 9 required headings): taxonomy, boundary matrix, defaults, limits, legal hold, tenant semantics, migration guidance, and failure behavior; `docs/host-security.md` gained the Task 8 paragraph and related link.
470
-
471
- Frozen overhead cap (`classificationMaxOverheadPercent` = 10) — measured against the pre-existing boundary work (the secret-redaction walk boundaries already ran before classification existed) on the frozen representative payload sizes, measured interleaved A/B (each iteration runs both phases back to back so drift/load affect both equally; fastest of three runs decides) — `applyFieldPolicy` peak 99.8% / range 95.8%–99.8% of that baseline (all ≤ 110%): prompt 4,164 B → 99.0%, toolArguments 2,114 B → 95.8%, toolResult 9,095 B → 97.1%, artifactMetadata 3,692 B → 99.0%, auditRecord 4,243 B → 99.8%, telemetry 1,760 B → 97.7%, exportPage100 10,726 B → 98.0%. Raw ratio vs native `JSON.stringify` is ≈1.0–1.3× on the frozen fixtures — a JS policy gateway cannot beat a native serializer, so the frozen cap is defined and enforced against the walking work the boundary already performed (matching the plan's "reuse existing redactors" and "avoids duplicate serialization"). Numbers recorded in `scripts/phase27-freeze-manifest.json` (`measuredClassification`).
472
-
473
- Protected command: none required — the contract and seams are verified entirely by `npm test` + `npm run test:coverage` on the local tree (no live infrastructure); the ERP journey (Task 9) exercises the seams end to end.
474
-
475
- ## 21. Task 9 result — deterministic ERP evals and the protected end-to-end release journey
476
-
477
- The frozen additive evals API shipped in `@arnilo/prism-evals`: `erpInvariantDataset` (frozen, versioned `Dataset` of the eight ERP invariants) and `createErpInvariantScorers()` (eight deterministic scorers, each a hard 0/1 gate). Scorers consume **structured journey facts only** — JSON carried in `result.text` — never model prose, secrets, or classified payloads. A single missing/false fact fails its scorer and the whole gate; no weighted average can hide an atomicity or security failure. `erpInvariantDataset` metadata is frozen (`frozen: true`, `release: "0.2.7"`, `schemaVersion: 1`, `scorerCount: 8`).
478
-
479
- The protected runner `scripts/phase27-erp-journey.test.mjs` exercises the full ERP pipeline against real PostgreSQL with process-level failover, in one reproducible journey: verified identity + policy decision + model-budget reservation/commit; SoD quorum approval (two distinct verified approvers grant; requester self-approval, subagent (narrowed delegation) approval, and revoked-approval consume all fail closed); atomic approval-consume + outbox-append in the same caller transaction; transactional outbox/inbox with duplicate delivery → exactly one local mutation (idempotent inbox); saga failure → compensation of the completed step (definite error) and unknown-outcome → `manual_intervention` → audited manual resolution reconciling to terminal; signed hash-chained audit export with legal-hold flag and field-policy redaction, independent `verifyAuditBatch` verification, and tamper detection (flipped byte fails); legal-hold `putLegalHold` + `exportUnderHold`; field-level classification canary (`secret` field denied at the prompt boundary); cross-tenant append claiming foreign ownership denied; two-replica fenced failover reusing `scripts/phase27-ha-worker.mjs` (SIGKILL active worker → peer resumes within lease TTL+5s, idempotent outbox replay, stale fence/revision write rejected, cursor never regresses); and a logical pg-client backup/restore of the ERP tables into a throwaway schema (`CREATE TABLE LIKE INCLUDING ALL` + `INSERT SELECT`) with SHA-256 digest equality. The journey fails (never skips) when `docs/_evidence/phase27-dr-evidence.json` is missing or stale (DR drill must run first).
480
-
481
- Evidence: `docs/_evidence/phase27-erp-journey.json` — schemaVersion 1, release 0.2.7, 4 labelled local substitutes (in-memory WORM/SIEM sinks, in-memory saga stores, logical pg-client backup; comprehensive PITR is in the DR drill evidence), stage timings, the eight fact blocks, scorer results (all 8 score 1), `factDigest`, `backupDigest`/`restoreDigest` (equal), `drEvidenceFile`, `haEvidenceFile`, gates (`allInvariantsPass`, `drEvidenceFresh`, `restoreEquality`), and the explicit `blocker: "passing this protected journey does NOT satisfy the 0.3.0 live-service matrix"`. Redaction guard asserts the evidence JSON contains neither the Postgres connection string nor secret-like strings. Numbers recorded in `scripts/phase27-freeze-manifest.json` (`measuredErpJourney`): durationMs 4815, scorers 8, allPassed true, restoreDigestMatch true, drEvidenceFresh true.
482
-
483
- Protected command:
484
-
485
- ```sh
486
- # Protected run (requires a disposable PostgreSQL instance; skips otherwise — never a passing skip):
487
- PRISM_TEST_POSTGRES_URL="<disposable postgres url>" node --test scripts/phase27-erp-journey.test.mjs
488
- ```
489
-
490
- Passing this protected journey **does not** satisfy the 0.3.0 live-service matrix; the live-service matrix remains an intentional external dependency recorded separately.
491
-
492
- ## 22. Task 10 result — documentation, migrations, release metadata, and measurable exit gates
493
-
494
- Closeout. The 50-package graph is synchronized at 0.2.7: `node scripts/release.mjs bump --from 0.2.6 --to 0.2.7` bumped all 50 manifests (root + 49 workspaces), updated every `@arnilo/prism` peer/dependency range, and regenerated `package-lock.json`; `node scripts/release.mjs check --version 0.2.7 --allow-dirty` validates every manifest version, peer range, and lockfile entry (the only failing check is the missing `v0.2.7` git tag, which is an operator action at publish time, not a release-blocker for the working tree). `scripts/package-truth.json` regenerated identically at 50 publishable / 49 workspace, peer spec `0.2.7`. Zero accidental runtime dependency additions across the release.
495
-
496
- Migrations install and verify on empty and upgraded databases: `packages/enterprise-postgres` registers exactly five migrations (`001_enterprise_state`, `002_tool_effects`, `003_router_reservations`, `004_erp_messaging`, `005_erp_approvals`) with SHA-256 checksums validated at module load by `assertEnterpriseMigrationHistory` (index-position name/version/checksum match). The Task 7 DR drill rehearsed the 0.2.6 → 0.2.7 upgrade (004+005 applied) and a rollback rehearsal (v26 backup restored: 0.2.7 tables absent, legacy rows preserved); production rollback is documented as roll-forward repair only — no down migrations exist.
497
-
498
- Documentation is complete and linked exactly once: `docs/index.md` links `audit-export.md`, `operations.md`, `disaster-recovery.md`, `data-classification.md`, and `evaluations.md` (ERP invariants section) exactly once each; the `docs.test.ts` contract (140/140) enforces the nine required headings per page and the nav-link invariant. `docs/release-0.2.7-evidence.md` links every Task 0–10 result and the 0.3.0 blocker.
499
-
500
- Roadmap `0.2.7 — Enterprise ERP production readiness` has every checkbox checked from linked, reproducible passing evidence (Tasks 0–9 complete; Task 10 is this closeout). The acceptance sentence is preserved verbatim: "ERP production ready" remains blocked until the 0.3.0 live-service matrix is recorded. The milestone is not marked "shipped" as ERP production-ready; the nine ERP capability checkboxes are complete and the release gates pass, but the 0.3.0 blocker is explicit in the roadmap, the evidence doc, and both machine manifests.
501
-
502
- Release-evidence manifest (`scripts/release-evidence.json`, regenerated for 0.2.7): 59 surfaces, 0 blocked, 19 protected (all naming their required env, never echoing values), every skip explained. Final evidence manifest (`scripts/phase27-release-evidence.json`): commit, tool versions (node v24.19.0 / npm 11.17.0), test totals (core npm test pass / security:threat-suites 50 pass / docs 140 / phase27-freeze 8), coverage (core 91.09% lines / policy 92.66% / evals 91.75%), timings (HA failover 4100 ms vs 9000 ms ceiling; DR backup 122 ms / restore 382 ms / recovery 1163 ms / RPO 0 s / RTO 1 s; ERP journey 4815 ms / 8 scorers), budgets (tarball re-baselined Task 8 / 50-package graph / classification overhead peak 99.8%), evidence-file references, protected-surface inventory, ERP journey results (all 8 invariants pass, restoreDigestMatch, drEvidenceFresh), known limitations, the secret scan (4456 files / 0 findings), and the explicit 0.3.0 blocker + pending operator sign-off.
503
-
504
- Secret/canary scan: `scripts/scan-secrets.mjs` over source, evidence, scripts, docs, plans, and generated dist artifacts — 4456 files, 0 findings (private keys, AWS keys, GitHub/npm/Slack tokens, OpenAI keys). No connection-string values leak into any evidence or plan doc (the freeze `environmentNamesOnly` policy records env-var names only).
505
-
506
- Export review: every additive 0.2.7 export has a named owner, bounded inputs, explicit failure semantics, and documentation; no placeholder adapter, dead API, stale generated file, or duplicated primitive remains. The four secret-manager providers stay deferred behind the demand gate (no adapter module, no `create*CredentialResolver` factory, no ambient discovery — verified by `scripts/phase27-demand-gate.mjs`).
507
-
508
- Protected command (closeout contract, stdlib-only, no infrastructure):
509
-
510
- ```sh
511
- node --test scripts/phase27-release.test.mjs
512
- ```
513
-
514
- The release is not "ERP production ready": the 0.3.0 live-service matrix (real OIDC IdP + JWKS rotation, real OPA bundle pinning, real MCP OAuth AS DCR + refresh/revoke, real S3-compatible store incl. KMS, real NATS JetStream) remains an intentional external dependency recorded separately. Passing the 0.2.7 exit gates unblocks the 0.2.7 release cut; it does not satisfy the 0.3.0 live-service matrix.