proof-harness 0.1.0-next.1 → 0.1.0-next.10

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 (42) hide show
  1. package/COMPATIBILITY.md +402 -1
  2. package/PROOF_SDK_CONTRACT.md +106 -81
  3. package/README.md +67 -4
  4. package/cli/config.mjs +38 -14
  5. package/cli/engines/parse_proof_schema.mjs +43 -82
  6. package/cli/engines/proof_control_sensitivity.mjs +98 -0
  7. package/cli/engines/proof_coverage.mjs +29 -22
  8. package/cli/engines/proof_drift.mjs +119 -96
  9. package/cli/engines/proof_input_scope.mjs +177 -0
  10. package/cli/engines/proof_mutation_check.mjs +361 -108
  11. package/cli/engines/proof_mutation_inventory.mjs +16 -21
  12. package/cli/engines/proof_plant_verification.mjs +88 -0
  13. package/cli/engines/proof_schema_classification.mjs +75 -0
  14. package/cli/engines/proof_schema_provider.mjs +192 -0
  15. package/cli/engines/proof_verify.mjs +56 -37
  16. package/cli/engines/scan_proof_capabilities.mjs +404 -202
  17. package/cli/proof-harness.mjs +3 -5
  18. package/dist/{action-types-C3OmHRyH.d.ts → action-types-Cw-dmSKj.d.ts} +11 -1
  19. package/dist/{fixture-B-dmnG5G.d.ts → fixture-DwDOegDK.d.ts} +9 -1
  20. package/dist/{mission-types-Dr5rBHYO.d.ts → mission-types-CYXrtv5m.d.ts} +3 -3
  21. package/dist/node.d.ts +63 -3
  22. package/dist/node.js +430 -48
  23. package/dist/node.js.map +1 -1
  24. package/dist/playwright.d.ts +39 -8
  25. package/dist/playwright.js +873 -227
  26. package/dist/playwright.js.map +1 -1
  27. package/dist/server.d.ts +26 -2
  28. package/dist/server.js +103 -33
  29. package/dist/server.js.map +1 -1
  30. package/dist/shared.d.ts +3 -168
  31. package/dist/shared.js +7 -31
  32. package/dist/shared.js.map +1 -1
  33. package/dist/{trace-types-DkIzq-l9.d.ts → trace-types-BIW53YPH.d.ts} +6 -22
  34. package/package.json +5 -3
  35. package/proof-consumer-fixtures/action-invocation/auth-refusal.json +8 -0
  36. package/proof-consumer-fixtures/capabilities/auth-admin.json +15 -0
  37. package/proof-consumer-fixtures/control-sensitivity/catalog.json +14 -0
  38. package/proof-consumer-fixtures/malformed/contradictory-status.json +45 -0
  39. package/proof-consumer-fixtures/mutations/primary-rpc/failed.json +31 -0
  40. package/proof-consumer-fixtures/schema-provider/catalog.json +13 -0
  41. package/cli/engines/proof_module_check.mjs +0 -498
  42. package/cli/engines/scan_module_meta.mjs +0 -352
package/COMPATIBILITY.md CHANGED
@@ -11,7 +11,7 @@ Consumers must pin an exact prerelease version, for example:
11
11
  ```json
12
12
  {
13
13
  "devDependencies": {
14
- "proof-harness": "0.1.0-next.1"
14
+ "proof-harness": "0.1.0-next.9"
15
15
  }
16
16
  }
17
17
  ```
@@ -24,3 +24,404 @@ tighten validation or packaging contracts even when its base version remains
24
24
  The trace, mission, and health protocol versions are independent from the npm
25
25
  package version. Consumers should reject unsupported artifact or protocol
26
26
  versions rather than guessing how to interpret them.
27
+
28
+ ## Prerelease migration notes
29
+
30
+ ### 0.1.0-next.10 — explicit primary mutation evidence (release candidate)
31
+
32
+ Prepared for release; not yet published. After publication, pin exactly
33
+ `proof-harness@0.1.0-next.10` and rerun a fresh baseline before mutation checks.
34
+ Trace, mission, and health protocol versions remain unchanged.
35
+
36
+ This fixes #26: next.9 accidentally required SDK-helper provenance for primary
37
+ mutation assertions, although primary inventory accepts explicit consumer
38
+ assertions. A mapped, decisive failed `role: "primary"` assertion recorded by
39
+ `recordAssertion` can again detect a mutation without `emittedBy`. An explicit
40
+ selector restriction on `emittedBy` still applies. Control sensitivity continues
41
+ to require recognized SDK-helper provenance; controls never detect primary
42
+ mutations. Existing freshness, plant, failed-run, and restoration checks remain.
43
+
44
+ No new RPC helper or provenance field is required. Do not supply or forge
45
+ `emittedBy`: public `recordAssertion` deliberately strips it. Consumer proof
46
+ specs and protected catalogs own the meaning of explicit primary assertions.
47
+ For an RPC EXECUTE-denial proof, successful unauthorized execution should emit
48
+ the mapped failed primary assertion. Unrelated SQL, transport, or setup errors
49
+ must remain incomplete or emit no decisive failure; they are not evidence of
50
+ the mapped breach. Tightening the consumer denial probe to accept only the
51
+ expected SQLSTATE (for example, `42501`) remains consumer-owned work.
52
+
53
+ ### 0.1.0-next.9 — anonymous actions and control sensitivity
54
+
55
+ Published release. Pin exactly
56
+ `proof-harness@0.1.0-next.9` and regenerate capabilities and baseline traces.
57
+ Trace/mission/health versions remain unchanged. Authentication refusal and
58
+ control-sensitivity output contracts use protocol/schema version 1.
59
+
60
+ #### Anonymous self-service actions (#23)
61
+
62
+ `assert.authorization` accepts `actor: "anonymous"` for **action probes only**.
63
+ It sends the protected POST invoker request in a fresh API context, without
64
+ login cookies or Authorization, and disables redirect following. It does not
65
+ clear or reuse the authenticated page session. Use the Playwright project's
66
+ `use.baseURL`, an explicit `action.baseURL`, or an already navigated page URL.
67
+
68
+ ```ts
69
+ await assert.authorization({
70
+ actor: "anonymous", page,
71
+ action: {
72
+ module: "user-management", name: "selfDeleteAccount", inputParams: {},
73
+ expectedAuthRedirect: "/sign-in",
74
+ },
75
+ });
76
+ ```
77
+
78
+ The consumer must update its **guarded invoker**, after request validation and
79
+ registered-handler lookup. In the catch around actual handler execution:
80
+
81
+ ```ts
82
+ import { authenticationRedirectResponse } from "proof-harness/server";
83
+ // Existing proofGuard, body validation, registry lookup, and handler call stay.
84
+ const refusal = authenticationRedirectResponse(error, request, {
85
+ module: body.module, action: body.action, allowedRedirects: ["/sign-in"],
86
+ });
87
+ if (refusal) return refusal;
88
+ // Keep the existing failure response for all other exceptions.
89
+ ```
90
+
91
+ Choose the exact authentication destination from the consumer's own auth
92
+ middleware. The adapter recognizes Next.js redirect errors only, requires no
93
+ Cookie/Authorization request headers, and matches an exact allowlisted relative
94
+ URL. It returns HTTP 401 with `{ protocolVersion: 1,
95
+ type: "proof_action_auth_refusal", module, action,
96
+ reason: "authentication_required", redirect }`. The client binds that envelope
97
+ to the invoked action and the assertion checks `expectedAuthRedirect`.
98
+ A generic 401/500, `action_threw`, invalid-form rejection, unexpected redirect,
99
+ malformed result, or missing registry entry is incomplete evidence. Anonymous
100
+ success is a failed authorization assertion. No seeded denied role is needed.
101
+
102
+ Use `assert.actionSucceeds` with a disposable authenticated actor for the
103
+ allowed-path control; account-deletion fixtures remain consumer-owned. The
104
+ adapter's destination list and placement are protected trust inputs: it must
105
+ only normalize authentication redirects from an executed registered action,
106
+ not invoker setup errors or unrelated redirects. This is not automatic proof
107
+ of an arbitrary application's authentication semantics.
108
+
109
+ Workspace scope discovery now follows local caller-input bindings and accesses,
110
+ including destructuring aliases and direct parse/safeParse results. It no
111
+ longer counts comments, SQL/output keys, or generated/result workspace IDs.
112
+ Unknown user-input dataflow used as workspace scope and dynamic input keys
113
+ remain unassessed. Internal `_BOT.ts` actions retain their existing exemption
114
+ from caller-boundary coverage. This is bounded local analysis, not intermodule
115
+ schema inference. In particular `createTeamWorkspace` no longer inherits a
116
+ cross-tenant requirement merely because it returns a new workspace ID. Declare
117
+ any additional product invariants and add their proofs in the consumer.
118
+
119
+ #### Separate positive-control sensitivity (#24)
120
+
121
+ Move the old `TENANT-deny-all` entry out of the primary mutation catalog into
122
+ a separate protected module named by `controlSensitivityCatalog` in
123
+ `proof.config.mjs`. Keep its existing subject, apply SQL and spec; add:
124
+
125
+ ```js
126
+ claims: [],
127
+ controls: [{
128
+ kind: "tenant_isolation", target: "workspace_members", operation: "select",
129
+ emittedBy: "assert.tenantIsolation",
130
+ }],
131
+ expectedFailureCode: "tenant_isolation_control",
132
+ ```
133
+
134
+ Run `proof-harness controls --inventory`, then `proof-harness controls` (or
135
+ `--only TENANT-deny-all`). Both require fresh passing baseline evidence for
136
+ every mapped package-origin **control** in the selected spec. The initial
137
+ contract supports tenant-isolation SELECT controls emitted by
138
+ `assert.tenantIsolation` or `assert.authorization`, with the exact failure code
139
+ `tenant_isolation_control`. Other sensitivity categories remain unsupported.
140
+
141
+ The runner reuses mutation planting/readback, isolated execution, restoration,
142
+ and recovery checks. A successful test needs a non-timeout failed proof, a
143
+ failed mapped control assertion of the same helper origin, and a step error
144
+ beginning with that exact failure code. Setup errors, incomplete/skipped
145
+ assertions, another spec, primary-only failures, or a missing failed control do
146
+ not count. Unexpected owner-control database errors are setup failures rather
147
+ than control rejection; explicit permission denial or zero visible owner rows
148
+ can establish the intended anti-vacuity failure.
149
+
150
+ Results live separately in `artifacts.controlSensitivity` (default
151
+ `.proof/control-sensitivity`), with `mode: "control-sensitivity"`,
152
+ `controlRejected`/`controlsTurnedRed`, and `detected: false`. They remain planted
153
+ traces and cannot satisfy baseline claims or primary mutation coverage. Use
154
+ `proof-harness controls --recover` for an interrupted run against the same
155
+ local stack; the shared recovery journal blocks either runner until restored.
156
+ Primary `mutate` still rejects `claims: []` with no resolved primary baseline.
157
+ A failed control can no longer count as a failed primary claim of the same
158
+ kind/target/operation. Wire both commands into consumer CI if both properties
159
+ are required.
160
+
161
+ ### 0.1.0-next.8
162
+
163
+ Published release `0.1.0-next.8`. Consumers pin exactly
164
+ `proof-harness@0.1.0-next.8` when adopting these features.
165
+
166
+ #### Direct RLS clients and Auth administration (#18)
167
+
168
+ The scanner recognizes immutable local bindings returned by
169
+ `createSupabaseRLSClient`, including named-import aliases and awaited calls.
170
+ Their table writes are assessed as RLS writes, never recorded as service-role
171
+ writes. Binding identity is local to its scope: shadowed identifiers,
172
+ reassignment, escaped clients/builders, and unresolved privilege still fail.
173
+ This remains convention-based discovery of the named consumer factories, not
174
+ verification of their implementation or arbitrary wrapper/dataflow analysis.
175
+
176
+ Direct `serviceClient.auth.admin.deleteUser(...)` is recorded in the additive
177
+ capability field `serviceRoleAuthOperations: ["deleteUser"]`, separate from
178
+ `serviceRoleMutations`. This does not pretend the Auth API is a SQL DELETE
179
+ (and does not infer whether its optional soft-delete argument is true).
180
+ Other administrative methods, dynamic method names, detached calls, and
181
+ unresolved administrative privilege remain unassessed.
182
+
183
+ Regenerate capabilities. Changes to Auth operations participate in drift under
184
+ `service_role_mutations_changed`. Coverage requires a package-origin denial
185
+ assertion and an allowed-path control for a user-facing Auth-admin action,
186
+ even when it has no workspace input or voluntary invariant declaration.
187
+ Existing explicit action-gap policy handling remains unchanged; a recognized
188
+ operation alone is not passing authorization evidence. Trace, mission and
189
+ health protocol versions are unchanged; capability schema remains additive v1.
190
+
191
+ #### Catalog providers and rollback INSERT controls (#19, #20)
192
+
193
+ These changes are not in the published `0.1.0-next.7`. They are opt-in; the
194
+ default SQL parser and REST INSERT/DELETE control remain available. No trace,
195
+ mission, or health protocol version changes. Pin exactly `0.1.0-next.8` only
196
+ after it is released.
197
+
198
+ #### Catalog assessment
199
+
200
+ Set `schemaProvider: "scripts/proof/catalog-provider.mjs"` in the protected
201
+ `proof.config.mjs`. `parse`, the parse step of `build`, and both sides of
202
+ `drift` use it instead of the conservative SQL parser. ALTER and DROP are then
203
+ evaluated by PostgreSQL, not inferred from historical CREATE statements.
204
+
205
+ The module must export a default async function receiving:
206
+
207
+ - `protocolVersion: 1`, `rootDir` for the tree being assessed;
208
+ - `migrations`: ordered `{ path, sql, sha256 }` entries from the configured
209
+ migration directory (top-level `.sql` files), and their combined `inputHash`;
210
+ - `withLocalPostgres(connectionUrl, callback)` and
211
+ `readPostgresCatalog(client, schemas = ["public"])` from `proof-harness/node`.
212
+ These are injected so the archived base needs no installed dependencies to
213
+ import these helpers. Keep the provider's own imports dependency-free.
214
+
215
+ The consumer provider must create an empty disposable database, apply its
216
+ versioned platform bootstrap and every supplied migration in order, read the
217
+ catalog, and drop the database in `finally`. Fail on migration or cleanup
218
+ errors. Return `{ protocolVersion: 1, assessed: true, issues: [], tables }` only
219
+ after successful evaluation and cleanup; `tables` is the helper's result.
220
+ The connection helper restricts connections to loopback hosts. The consumer
221
+ owns the local database URL, creation privileges, platform bootstrap, migration
222
+ ordering, schema selection, and any required role setup. Never use the live
223
+ head database as the base catalog, clone an application-populated database,
224
+ or catch a failed migration and return partial facts.
225
+
226
+ Catalog table facts contain `schema`, `name`, `columns` (names), `rls_enabled`,
227
+ `rls_forced`, and `policies`. Each policy contains `name`, `command`, `roles`,
228
+ `mode`, `using`, and `check`. Classification is package-owned. Drift compares
229
+ column names, RLS state including FORCE, and full policy facts; this does not
230
+ add drift coverage for column types, defaults, functions, triggers, or grants.
231
+ Malformed envelopes/facts and changed migration inputs fail assessment and
232
+ replace stale successful output with an unassessed artifact.
233
+
234
+ The schema artifact adds `assessment` with provider protocol/path/hash,
235
+ migration paths/hashes, combined input hash, and repository source hash. This
236
+ is source identity, not a signature. The provider, its platform bootstrap,
237
+ configuration, and installed harness remain protected trust inputs.
238
+
239
+ For generated migrations, retain `driftPrepare` to rebuild them in each tree.
240
+ **Adoption requires a preparatory base commit:** register the provider, its
241
+ platform bootstrap, and config in the consumer before the comparison that
242
+ upgrades the package. Both base and head must declare a provider. Drift rejects
243
+ switching assessment modes across the comparison and never substitutes head
244
+ configuration for a base without the provider. Existing next.7 ignores the new
245
+ keys; registering them does not repair its conservative parser. A consumer
246
+ already blocked on next.7 needs to land this trusted setup separately before
247
+ the candidate's first successful drift run.
248
+
249
+ #### Append-only INSERT controls
250
+
251
+ Configure the protected consumer config explicitly, using your local ports:
252
+
253
+ ```js
254
+ insertControl: {
255
+ mode: "rollback",
256
+ databaseUrlEnv: "PROOF_DATABASE_URL",
257
+ role: "service_role",
258
+ supabaseUrl: "http://127.0.0.1:54321",
259
+ }
260
+ ```
261
+
262
+ Supply the corresponding local PostgreSQL URL through that environment
263
+ variable; never commit its password. The configured REST origin must match
264
+ `NEXT_PUBLIC_SUPABASE_URL`. The database URL must refer to the same stack,
265
+ schema, and migrations as REST; the harness validates the configured origin
266
+ but cannot discover which database a REST server actually serves.
267
+
268
+ The denied INSERT still executes through the authenticated Supabase client.
269
+ Only after an explicit `42501` denial and a service read confirming no write
270
+ does the harness validate the same payload on one PostgreSQL connection:
271
+ BEGIN, SET LOCAL ROLE, INSERT, force deferred constraints, ROLLBACK. The role
272
+ must have the intended INSERT permission. Missing columns keep their database
273
+ defaults; the harness supplies no tenancy values. An invalid payload, failed
274
+ role change, database error, or unacknowledged rollback fails as incomplete
275
+ evidence. The harness records the control assertion only after success and a
276
+ REST reread confirming no matching row remains. There is no consumer callback
277
+ that can stamp a passing control.
278
+
279
+ No DELETE privilege is needed for this control. Transactional trigger writes
280
+ also roll back, but sequence advancement and external side effects do not;
281
+ use only a disposable local stack. This control checks database validity under
282
+ the configured role, not REST middleware or an authenticated user's JWT
283
+ context. Templates whose privileged insert depends on that context must supply
284
+ a suitable database design/control strategy rather than treat this mode as
285
+ equivalent. Product authorization errors must still use SQLSTATE `42501`;
286
+ generic exceptions such as `P0001` are not accepted as authorization evidence.
287
+
288
+ For maintainers, `PROOF_DATABASE_URL=... node tests/postgres-integration.mjs`
289
+ after `pnpm build` exercises a separate temporary database against PostgreSQL,
290
+ including immediate and deferred failures and catalog ALTER/DROP behavior.
291
+
292
+ ### 0.1.0-next.7 — audit hardening
293
+
294
+ Published release `0.1.0-next.7` tightens acceptance behavior. Trace
295
+ <a id="unreleased-audit-hardening"></a>
296
+
297
+ ### 0.1.0-next.7
298
+
299
+ Published September 7, 2026. This release tightens acceptance behavior. Trace
300
+ schema remains 2, with additive source/execution identity fields. Version 1
301
+ and unversioned trace shapes remain explicitly readable, but CLI acceptance
302
+ requires current provenance. Required consumer changes:
303
+
304
+ - Regenerate traces. `verify`, `coverage`, and mutation inventory share a
305
+ decoder: corrupt JSON, duplicate proof IDs, unknown vocabulary, and
306
+ contradictory verdicts fail. Only passing baseline evidence can satisfy
307
+ claims; mutation evidence is separate. Current spec contents must match
308
+ `specHash`; in Git worktrees both `commit` and `sourceHash` must match.
309
+ `sourceHash` includes tracked and untracked nonignored inputs and excludes
310
+ configured generated artifacts. Run generators before proofs. Non-Git
311
+ exports cannot establish whole-source freshness and only check spec hashes.
312
+ - Use a dedicated trace directory. The writer honors `artifacts.traces` and
313
+ the runner's `--traces-dir` override. IDs must use letters, digits, dots,
314
+ underscores, or hyphens, starting with a letter or digit. Duplicate IDs
315
+ cannot overwrite another proof; a later Playwright retry of the same test
316
+ may replace its earlier attempt. Standalone repeated runs need fresh output.
317
+ Mission aggregates now live at `<traces>/missions/<missionId>.json` to
318
+ prevent collisions with proof IDs. Cleanup refuses unknown JSON before
319
+ deleting any trace and rejects repository/ancestor directories, including
320
+ symlink aliases. Move legacy or corrupt files aside before rerunning.
321
+ - UPDATE denial probes require stable `identityColumns` (default `["id"]`),
322
+ present and non-null in every target row, excluded from the update payload.
323
+ The attempted update must change a value. Rereads use original identities,
324
+ even when a write changes a filter column. SQL errors other than `42501`
325
+ are incomplete evidence. INSERT probes require a payload absent before the
326
+ probe and valid for a service-role control insertion; that control is
327
+ cleaned up. HTTP expectations must contain a status or a nonempty matcher
328
+ list. Write requests are never automatically retried after transport errors.
329
+ - Capability discovery uses TypeScript syntax analysis for import aliases and
330
+ multiple actions. Unknown factory/client indirection, escaped service query
331
+ builders, dynamic table names, and ambiguous metadata block assessment.
332
+ Discovery still assumes the configured template action layout.
333
+ - The SQL parser is deliberately conservative, not a final PostgreSQL catalog
334
+ evaluator. Unsupported ALTER/DROP or dynamic SQL makes schema assessment
335
+ fail. RLS must be explicitly enabled for classification; public ALL/write
336
+ policies are never exempted as public-read. Policy expressions and mode
337
+ participate in drift. Resolve unsupported migrations with a catalog-backed
338
+ integration before using this parser as a gate; do not edit shipped SQL or
339
+ mark an unassessed artifact assessed to obtain a pass.
340
+ - Drift always regenerates both trees using the installed harness and each
341
+ tree's consumer configuration. It includes untracked actions, SQL, and
342
+ lockfiles. For ignored/generated migrations, configure `driftPrepare` as a
343
+ command argv array (for example `["node", "scripts/aggregate_migrations.mjs",
344
+ "--fresh"]`). That consumer-owned command runs in each tree before parsing;
345
+ failure prevents assessment. It must work in the archived tree without an
346
+ implicit dependency install. The old `driftSources` optimization is unused.
347
+ - Mutations require fresh mapped baseline claims and write isolated traces.
348
+ SQL applies run as one transaction; recovery is attempted even after a
349
+ failed apply. Policy snapshots preserve expressions, roles, command, and
350
+ permissive/restrictive mode. Generic privilege mutations support direct,
351
+ owner-issued grants without grant options; unsupported inherited/PUBLIC or
352
+ ownership cases fail before planting. Move old mutation outputs aside on
353
+ first use: nonempty output directories require a harness ownership marker.
354
+ An interrupted run leaves `mutation-recovery.json` beside the mutation output
355
+ directory. Run `proof-harness mutate --recover` against the same local
356
+ database to restore and verify the journaled subject. A timeout, signal, or
357
+ incomplete failed claim never counts as detection. Recovery covers the
358
+ catalog's declared subject and cleanup, not arbitrary undeclared side effects.
359
+ - Partial fixture setup is cleaned up, and cleanup errors now fail the proof.
360
+
361
+ These checks validate observed evidence. Protect the installed harness,
362
+ configuration, mission, policies, mutation catalog, and CI acceptance job from
363
+ executor edits. Trace metadata is not a signature or a sandbox: it cannot
364
+ establish honesty against an executor allowed to replace the gate or fabricate
365
+ its inputs. Independent product acceptance and a trusted runner remain needed.
366
+
367
+ ### 0.1.0-next.6
368
+
369
+ **Removed: the module-descriptor system.** It served the consumer's planner
370
+ (what a module is for, what a buyer must decide, where answers land) and never
371
+ fed verification, so it is now consumer-owned. Gone from the package:
372
+
373
+ - CLI commands `modules` and `modules-check` (and the `modules` step inside
374
+ `build`); the `.proof/module-policy.json` accept-list introduced in
375
+ `next.5` went with the checker.
376
+ - `proof-harness/shared` exports `ModuleMeta`, `ModuleDecision`,
377
+ `ModuleInput`, `ModuleDefault`, `ModuleSeam`, `ScannedModule`,
378
+ `ModulesArtifact`, `MODULE_DESCRIPTOR_SCHEMA_VERSION`, `MODULE_KINDS`,
379
+ `APPLIED_BY`, `ModuleKind`, `AppliedBy`, `isModuleKind`, `isAppliedBy`.
380
+ - The `proof.config.mjs` keys `artifacts.modules`, `policies.module`,
381
+ `roots.modules`, `roots.sharedModules`, and `moduleKinds` (now ignored if
382
+ supplied).
383
+
384
+ Migration: consumers using the system vendor it from
385
+ `proof-harness@0.1.0-next.5` (Apache-2.0) — `cli/engines/scan_module_meta.mjs`,
386
+ `cli/engines/proof_module_check.mjs`, and `src/shared/module-types.ts` plus the
387
+ two vocabularies above — and point their `module.meta.ts` type imports at the
388
+ vendored copy. Consumers that never ran `proof:modules` are unaffected. Trace,
389
+ mission, and health protocol versions are unchanged.
390
+
391
+ ### 0.1.0-next.5
392
+
393
+ No artifact schema or protocol changes. Three behavioral changes:
394
+
395
+ - **`seed.workspace` no longer writes `type`.** The insert carries only
396
+ `name` plus caller-supplied values — the harness never invents a column
397
+ value it wasn't given. A schema whose `workspaces` table has required
398
+ columns without defaults (the conventional template's `type` included)
399
+ must supply them via `seed.workspace(name, { columns: { … } })` or the new
400
+ `workspaceColumns` option on `assert.tenantIsolation`, or add a database
401
+ default.
402
+ - **`createProofServiceClient` refuses non-local targets.** Any
403
+ `NEXT_PUBLIC_SUPABASE_URL` whose parsed hostname is not
404
+ `localhost`/`127.0.0.1`/`[::1]` aborts as `unsafe_database` before a
405
+ client is constructed, because the suite deletes the users and workspaces
406
+ it seeds. CI setups reaching Supabase through a docker-network hostname
407
+ call `allowProofServiceHosts(["<host>"])` in code; there is no environment
408
+ override.
409
+ - **`modules-check` gains an accept-list.** `.proof/module-policy.json`
410
+ (`schemaVersion: 1`, `acceptedUndescribed: [{ module, reason }]`) lets an
411
+ adopting consumer record undescribed required-root modules as visible debt
412
+ instead of turning the check off. Entries that stop applying fail as
413
+ `module_policy_stale`. Absent file = no acceptances; existing consumers
414
+ are unaffected.
415
+
416
+ ### 0.1.0-next.4
417
+
418
+ No entry-point, artifact schema, or protocol changes. The `mutate` command
419
+ tightens validation: each mutation's subject is now read back after apply and
420
+ again after its proof, so a defect that never took effect fails as
421
+ `NOT PLANTED` and one that was externally undone mid-run fails as
422
+ `UN-PLANTED` — both distinct from a missed proof. A catalog mutation whose
423
+ `apply` intentionally leaves its subject unchanged (it plants a side object
424
+ that `cleanup` removes) must now declare `applyDoesNotChangeSubject: true`;
425
+ runs with such undeclared mutations fail until the flag is added. The shared
426
+ dev server is also started before the first defect is planted rather than
427
+ inside the first mutation's window.
@@ -9,11 +9,8 @@ instead; this document is for anyone _consuming_ the template.
9
9
 
10
10
  > **"Manifest" in this document always means the _mission_ manifest**
11
11
  > (`.proof/current-mission.json`) — the file that pins what one PR must prove.
12
- > It is unrelated to a **module descriptor** (`module.meta.ts` →
13
- > `.proof/modules.json`), which describes what a module is for and what a buyer
14
- > must decide before using it. See §3.1 below.
15
12
 
16
- - **Version:** 1 (`schemaVersion` in every manifest and artifact)
13
+ - **Versions:** mission manifests 1; proof traces 2; proof health protocol 1.
17
14
  - **Stability:** The shapes in this document are the contract. Additive
18
15
  changes (new optional fields, new vocabulary entries) do not bump the
19
16
  version; breaking changes bump `schemaVersion` and are documented here.
@@ -25,6 +22,17 @@ instead; this document is for anyone _consuming_ the template.
25
22
 
26
23
  ---
27
24
 
25
+ ## Current acceptance semantics
26
+
27
+ The `0.1.0-next.7` audit hardening in [COMPATIBILITY.md](COMPATIBILITY.md#010-next7)
28
+ supersedes older template-specific command examples below. Trace consumers
29
+ must use `decodeTrace`/`decodeTraceBundle` for structural validation and
30
+ `readTraceDirectory` for CLI-equivalent freshness checks. Evidence counts only
31
+ when the proof and step passed, the assertion has a passing consistent verdict,
32
+ and the trace is a baseline rather than a planted mutation. Helper-origin
33
+ metadata is a convention enforced by the writer, not cryptographic attestation.
34
+ The consumer controls and protects the runner and its trust inputs.
35
+
28
36
  ## 1. The loop
29
37
 
30
38
  ```
@@ -50,7 +58,7 @@ instead; this document is for anyone _consuming_ the template.
50
58
  │ │ fails cheaply BEFORE the database proofs run │
51
59
  │ ├─ proof:verify │
52
60
  │ ├─ runs every *.proof.ts spec │
53
- │ ├─ aggregates .proof/traces/<missionId>.json │
61
+ │ ├─ aggregates .proof/traces/missions/<missionId>.json │
54
62
  │ └─ validates manifest requirements against artifacts │
55
63
  │ ├─ proof:coverage --strict │
56
64
  │ └─ proof:inventory → requires mutation or acceptance │
@@ -534,7 +542,6 @@ Everything the template produces that the consumer consumes.
534
542
  | Output | Location | Emitted by | Cadence |
535
543
  | --------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------- | -------------------------------------- |
536
544
  | Capability map | `.proof/capabilities.json` (gitignored) | `pnpm proof:scan` (via `proof:build` in setup/CI) | Every setup + CI run |
537
- | Module descriptors | `.proof/modules.json` (gitignored) | `pnpm proof:modules` (via `proof:build` in setup/CI) | Every setup + CI run |
538
545
  | Schema + RLS classification | `.proof/schema.json` (gitignored) | `pnpm proof:parse` (via `proof:build` in setup/CI) | Every setup + CI run |
539
546
  | Proof action allowlist | `app/__shared/proof-runtime/action-registry.generated.ts` | `pnpm proof:registry` | When action requirements/probes change |
540
547
  | PR verification status | GitHub check `Proof` | `pnpm proof:check` in `.github/workflows/ci.yml` | Every PR push |
@@ -714,82 +721,16 @@ from another commit or whose `specFile` no longer exists with `stale_trace`.
714
721
 
715
722
  ---
716
723
 
717
- ### 3.1 Module descriptors — what exists, and what must be decided
718
-
719
- `.proof/modules.json` answers a different question from everything else here.
720
- The trace artifacts say _"this claim was proved"_; this one says _"this is what
721
- exists, and this is what a buyer has to settle before it can be used"_. It is
722
- what a planning agent reads instead of the template's source, which is the point:
723
- without it the planner needs the codebase in its context, and that grows with
724
- every module we write.
724
+ ### 3.1 Module descriptors — moved to the consumer
725
725
 
726
- Regenerate it with `pnpm proof:modules`. Unlike `schema.json` it needs **no
727
- Docker, database, or migration aggregate** — it reads the filesystem and
728
- `capabilities.json`, and takes about a second.
729
-
730
- Each entry has a declared half and a derived half:
731
-
732
- ```jsonc
733
- {
734
- "schemaVersion": 1,
735
- "modules": [
736
- {
737
- // Declared in app/__core/<module>/module.meta.ts
738
- "id": "user-auth",
739
- "kind": "core", // core | extension | business | generated
740
- "purpose": "Signs people in and proves who they are — …",
741
- "env": [],
742
- "decisions": [
743
- {
744
- "id": "mfa_enforcement", // snake_case, unique across all modules
745
- "ask": "Must people use a second factor to sign in?",
746
- "because": "Stricter is safer but adds a step for everyone.",
747
- "options": [{ "value": "optional", "means": "…" }],
748
- "applied_by": "config", // config | content | code
749
- "applies": ["app/__core/user-auth/src/config.ts#MFA_POLICY"],
750
- },
751
- ],
752
- "inputs": [
753
- /* values the buyer supplies rather than picks */
754
- ],
755
- "defaults": [
756
- /* what we chose for them, each with a seam */
757
- ],
758
-
759
- // Derived from source — never written by hand
760
- "path": "app/__core/user-auth",
761
- "entities": [], // tables its own migrations create
762
- "requires": ["config", "user-management"],
763
- "uses_shared": ["ui", "db-supabase"],
764
- "actions": [{ "name": "signin", "verb": null, "object": null }],
765
- "proven": { "specs": 1, "mutations": 0 },
766
- },
767
- ],
768
- // Tables no descriptor claims — today, the ones owned by __shared modules.
769
- "unowned_tables": [
770
- {
771
- "table": "audit_logs",
772
- "module": "audit-logging",
773
- "path": "app/__shared/audit-logging",
774
- },
775
- ],
776
- }
777
- ```
778
-
779
- **Two audiences, one file.** A planner reads `purpose`, `decisions`, `inputs`
780
- and `applied_by` — business language, no paths, roughly 80 tokens per decision.
781
- The agent that _applies_ an answer additionally reads `applies`, which names
782
- every place the answer lands, including files in other modules.
783
-
784
- **Every claim is checked.** `pnpm proof:modules:check` fails when a module has
785
- no descriptor, when an id does not match its directory, when a decision has
786
- fewer than two options or a duplicate id, when a declared environment variable
787
- is undocumented or an undeclared one is read, and — the one that matters most in
788
- practice — **when a seam no longer exists**. A moved file fails CI instead of
789
- quietly sending the next agent to the wrong place.
790
-
791
- Circular dependencies in the derived graph are **reported, not failed**, while a
792
- known inversion is being resolved.
726
+ Until `0.1.0-next.5` the package also shipped a module-descriptor system
727
+ (`module.meta.ts` → `.proof/modules.json`, plus the `modules` and
728
+ `modules-check` commands). It served the consumer's *planner* — what a module
729
+ is for, what a buyer must decide, where answers land — and never fed
730
+ verification, so as of `0.1.0-next.6` it is consumer-owned and no longer part
731
+ of this package. The last release carrying it is `proof-harness@0.1.0-next.5`
732
+ (Apache-2.0), whose implementation is the reference for consumers vendoring
733
+ the system into their own repository.
793
734
 
794
735
  ## 4. Failure contract
795
736
 
@@ -941,6 +882,33 @@ operation }` assertion has neither a matching mutation nor a valid entry in
941
882
  `.proof/mutation-policy.json`, pinned to the current spec hash and a passing
942
883
  positive control. A changed/deleted claim, changed spec, missing control, or
943
884
  newly added mutation makes the acceptance stale and fails inventory.
885
+ - **A proof run cannot target a hosted database.** `createProofServiceClient`
886
+ parses `NEXT_PUBLIC_SUPABASE_URL` and hard-aborts as `unsafe_database` on
887
+ any hostname that is not local — matched on the parsed hostname, never by
888
+ substring, so `localhost.example.com` does not pass. The suite deletes the
889
+ auth users and workspaces it seeds; against a hosted project that failure
890
+ is silent and unrecoverable. There is deliberately no environment-variable
891
+ override: a genuinely disposable non-local host (a docker-network CI
892
+ hostname) is allowed in code via `allowProofServiceHosts`, where a reviewer
893
+ sees it. The mutation runner applies the same refusal to its psql target.
894
+ - **The harness never invents a column value it wasn't given.** Anywhere the
895
+ package writes to a consumer-owned table, the values are caller-supplied or
896
+ the column is omitted — tenancy shape is a consumer decision.
897
+ `seed.workspace` writes only `name`; a schema that requires more states it
898
+ via `seed.workspace(name, { columns })` or `workspaceColumns` on
899
+ `assert.tenantIsolation`.
900
+ - **A mutation that is not demonstrably live cannot judge a proof.** The
901
+ mutation runner never (re)applies migrations; it starts (or adopts) the
902
+ shared dev server before the first defect is planted, then reads each
903
+ mutation's subject back after apply and again after the proof. An apply that
904
+ left the subject unchanged fails as NOT PLANTED (a `REVOKE` no-op, or a
905
+ privilege still reachable via `PUBLIC` or an inherited role); a subject that
906
+ reverted mid-run — for example consumer tooling re-applying a wholesale
907
+ `REVOKE ALL; GRANT …` migration — fails as UN-PLANTED. Neither is reported
908
+ as a missed proof, because the proof never saw the defect. A mutation whose
909
+ apply intentionally leaves its subject untouched (it plants a side object
910
+ that `cleanup` removes) must declare `applyDoesNotChangeSubject: true` in
911
+ the catalog to opt out of the two subject comparisons explicitly.
944
912
 
945
913
  `pnpm proof:mutate` is the regression test for these guarantees: it re-opens known
946
914
  vulnerabilities in the local database one at a time and fails if the corresponding
@@ -1253,3 +1221,60 @@ a trace version bump.
1253
1221
  `.proof/coverage-policy.json` carries its own `schemaVersion` (currently `1`).
1254
1222
  It is authored, not derived, and is read only by `pnpm proof:coverage`; consumers
1255
1223
  do not need to parse it.
1224
+
1225
+
1226
+ ## Catalog provider and rollback controls (0.1.0-next.8)
1227
+
1228
+ The protected consumer config may opt into `schemaProvider` protocol 1 and
1229
+ `insertControl.mode: "rollback"`. See [the setup and migration contract](COMPATIBILITY.md)
1230
+ for the exact provider envelope, base-commit requirements, and control configuration.
1231
+ `proof-harness/node` exports `withLocalPostgres`, `readPostgresCatalog`,
1232
+ `rollbackInsertControl`, and the `PostgresCatalogTable`/`PostgresCatalogPolicy` types.
1233
+ Providers own database materialization; the harness validates catalog facts,
1234
+ classifies policies, binds source identity, and compares both trees. A rollback
1235
+ INSERT control is package-executed and can only record passing control evidence
1236
+ after its insert, deferred checks, rollback, and absence reread succeed. The
1237
+ original authenticated request remains the primary authorization assertion.
1238
+
1239
+
1240
+ ## Direct Auth administration (0.1.0-next.8)
1241
+
1242
+ Capability schema v1 adds optional `serviceRoleAuthOperations`, currently a
1243
+ closed list containing only `"deleteUser"`. It describes direct service-client
1244
+ Auth API calls separately from `serviceRoleMutations` (SQL table writes).
1245
+ Such user-facing actions require denial and allowed-control evidence in
1246
+ coverage even without a workspace input. Changes use the existing
1247
+ `service_role_mutations_changed` drift facet. See `COMPATIBILITY.md` for
1248
+ supported factory/binding shapes and required consumer regeneration.
1249
+
1250
+
1251
+ ## Anonymous invocations and control sensitivity (0.1.0-next.9)
1252
+
1253
+ The versioned anonymous refusal envelope is `ActionAuthenticationRefusal`
1254
+ (protocol 1), validated by `isActionAuthenticationRefusal` from the shared
1255
+ entry point. The server exports `authenticationRedirectResponse`; Playwright
1256
+ exports `actAsUser.invokeAnonymousAction` and accepts `actor: "anonymous"` in
1257
+ `assert.authorization` action probes. Only an action-bound, expected
1258
+ authentication redirect can establish a passing anonymous denial; generic
1259
+ exceptions are not evidence. See `COMPATIBILITY.md` for the required protected
1260
+ invoker integration and allowed authenticated control.
1261
+
1262
+ `proof-harness controls` consumes `controlSensitivityCatalog` independently
1263
+ from primary mutation inventory and writes `mode: "control-sensitivity"`
1264
+ results under `artifacts.controlSensitivity`. Its mapped failed controls never
1265
+ count as primary mutation detection. The initial contract is limited to
1266
+ tenant-isolation SELECT anti-vacuity checks with `tenant_isolation_control`;
1267
+ see `COMPATIBILITY.md` for exact selectors, fresh baseline requirements and
1268
+ recovery. Consumer claims and product fixtures remain consumer-owned.
1269
+
1270
+ ## Explicit primary mutation evidence (0.1.0-next.10)
1271
+
1272
+ Primary inventory and mutation detection accept explicit consumer assertions
1273
+ without `emittedBy`. Detection still requires a mapped, decisive failed primary
1274
+ assertion in a failed step and trace, plus the runner's baseline, plant, and
1275
+ failed-execution checks. Any selector requiring a specific `emittedBy` must
1276
+ still match it. A missing helper origin is not helper provenance: consumer
1277
+ specs and protected catalogs remain responsible for these assertions' meaning.
1278
+ Control sensitivity requires recognized SDK-helper provenance and cannot count
1279
+ as primary detection. Skipped, incomplete, and setup-only failures are not
1280
+ mapped decisive primary evidence. No trace protocol change is introduced.