@awak-app/simy-cli 0.5.11 → 0.5.13

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awak-app/simy-cli",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@ import {
13
13
  } from "./durable-local-task-steps-contract.js";
14
14
  import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
15
15
 
16
- export const CLI_VERSION = "0.5.11";
16
+ export const CLI_VERSION = "0.5.13";
17
17
  export const CLI_API_CONTRACT_VERSION = 10;
18
18
 
19
19
  export function withCliContract(
@@ -33,15 +33,31 @@ external-message authority and never weakens repository-required gates.
33
33
 
34
34
  ## Freeze the delivery charter
35
35
 
36
+ Start with the beneficiary's experience, not the proposed solution. Read-only
37
+ discovery may supply facts, but before solution design or coding freeze a compact
38
+ experience contract: who is in what situation, what they are trying to achieve,
39
+ the complete usable outcome, what they should understand and do next at each
40
+ observable moment, recovery and forbidden experiences, and how each moment will
41
+ be observed. Give each moment a stable UX/acceptance ID and carry those same IDs
42
+ through the shared To-Be, implementation mapping, and results matrix. Do not use
43
+ a list of components, endpoints, screens, or tasks as a substitute for the
44
+ end-to-end experience.
45
+
36
46
  Before material action, record:
37
47
 
38
- - objective and user-visible success conditions;
48
+ - objective, beneficiary, and the frozen experience contract with user-visible
49
+ success conditions;
39
50
  - authoritative acceptance sources that are independent of the candidate
40
51
  implementation, plus architecture boundaries and dependency direction;
41
52
  - repositories, latest base branches, dedicated worktrees, environments, and
42
53
  the exact user journey;
43
54
  - required evidence, applicable change boundaries, risk tier, and the few
44
55
  critical invariants that must block integration;
56
+ - for every production-relevant experience ID, outcome and guardrail measures
57
+ with signal, independent source, observation window, threshold, decision and
58
+ owner; if no production outcome is in scope, record that once with a reason;
59
+ - when release is in scope, staged exposure and deterministic promote, hold,
60
+ abort and rollback criteria, rollback mechanism, kill switch and owner;
45
61
  - requested and effective delivery levels, the risk-based safety ceiling, and
46
62
  the chat confirmation receipt;
47
63
  - exclusions and authority boundaries, especially commit, push, Draft PR
@@ -58,32 +74,38 @@ changes the requested result.
58
74
 
59
75
  Before implementation, read [shared system specifications](references/shared-system-specifications.md).
60
76
  Find the team's canonical specification for this system; create it once if absent.
61
- Record purpose, beneficiary, current pain, intended user experience and observable
62
- success. Reference the current As-Is revision and update only affected To-Be views.
77
+ Reconcile the frozen experience contract with the current As-Is, then update only
78
+ the affected To-Be views. Preserve the purpose, beneficiary, current pain,
79
+ intended user experience, observable success, and stable UX/acceptance IDs.
63
80
  Maintain the twelve-view inventory, stable IDs and cross-view impact links in the
64
81
  shared source, not twelve new task-local copies. Unaffected views retain their
65
82
  version; unknown As-Is facts remain unknown. Review changes through the team's
66
83
  normal version control and preserve concurrent work.
67
84
 
68
- Use independent agents for design review (including consistency across diagram
69
- kinds), implementation-to-To-Be review, and final PR/evidence review at **every**
70
- level. Reviewers inspect the full affected path and unchanged linked views. Fix
71
- findings and obtain independent re-review. A changed requirement or baseline
85
+ Freeze and follow the risk-proportionate review plan from the selected delivery
86
+ level. Level 3 may consolidate design, implementation-to-To-Be, affected-view and
87
+ final evidence review into one independent final-candidate review. Inspect changed
88
+ views and their dependency links; retain unrelated views by exact revision. Fix
89
+ findings and independently re-review the affected behavior. A changed bound input
72
90
  invalidates affected approval; do not rewrite To-Be to disguise a failed result.
73
91
 
74
- PR creation and post-creation verification include the SQM loop described in the
75
- reference. Announce this requirement when starting Delivery Loop. Running SQM
76
- still follows its explicit opt-in/authority rules; if opt-in or access is absent,
77
- retain the unmet SQM gate and continue independent work. Ordinary PR work outside
78
- Delivery Loop does not activate this workflow. Record known incident coverage and
79
- unknown coverage separately; a green proof with no applicable rules does not prove
80
- that historical failures cannot recur.
92
+ When SQM is explicitly opted in, apply the identity-aware SQM plan described in
93
+ the reference. Announce its applicability when starting Delivery Loop. Reuse a
94
+ verifiable proof only for exactly unchanged bound inputs; rerun for changed identity
95
+ or newly applicable PR-only checks. If required access is absent, retain the unmet
96
+ gate and continue independent work. Ordinary PR work outside Delivery Loop does
97
+ not activate SQM. Record known and unknown incident coverage separately; zero
98
+ applicable rules do not prove that historical failures cannot recur.
81
99
 
82
100
  ## Maintain the acceptance checklist outside the conversation
83
101
 
84
102
  Before implementation, read [acceptance evidence](references/acceptance-evidence.md)
85
103
  and persist a criterion-by-criterion checklist. Bind the criteria and both
86
104
  verification layers to independent acceptance sources in a frozen charter.
105
+ Use the experience contract as the user-facing slice of these same criteria, not
106
+ as a parallel checklist. Every observable moment from the real trigger through a
107
+ usable outcome and applicable recovery path must map to at least one criterion;
108
+ working components do not prove that the combined experience is complete.
87
109
  Use a separate current-results matrix for observations, status, evidence,
88
110
  unmet reasons, and next actions. Render a readable table alongside the JSON;
89
111
  checklists held only in chat or the model's memory are insufficient.
@@ -100,6 +122,15 @@ values, ownership, authorization, and duplication. HTTP 500/network failures are
100
122
  failure evidence; absence of a 500 or presence of a 200 does not prove persistence.
101
123
  Record justified non-applicability before execution, not after a failed test.
102
124
 
125
+ After building the exact candidate in the production-like local environment,
126
+ execute the complete experience and regenerate the readable table. It must let a
127
+ reviewer compare, for every stable ID, the persona/context and expected visible
128
+ outcome, the actual user observation, the independent technical postcondition or
129
+ integration result, evidence, status, and the next repair. Inspect the table as a
130
+ whole to catch gaps between individually working parts. Any missing, pending,
131
+ failed, stale, or unverifiable applicable experience row blocks
132
+ `user_like_local_verified` and returns the loop to the smallest affected repair.
133
+
103
134
  Run the packaged acceptance validator before promoting a relevant verification
104
135
  gate and before issuing the completion receipt. A missing, pending, failed, stale,
105
136
  or unverifiable applicable row prevents promotion. Matrix completeness is one
@@ -114,7 +145,7 @@ For persistence, require reload/session return and stored values/ownership. A
114
145
  working control, success message or HTTP 200 alone is insufficient.
115
146
 
116
147
  The bundled minimum checker extends the acceptance matrix with structured
117
- observations. Compare the original request to the AC-N list independently so
148
+ observations. Compare the original request to the stable criterion-ID list independently so
118
149
  omitted requirements cannot disappear before validation. A missing, failed or
119
150
  stale floor check prevents verified completion at any level. The CLI now enforces
120
151
  this before implementation/PR promotion and on recheck; the standalone command
@@ -265,10 +296,11 @@ Run focused regression tests first, then formatting, lint, type checking, unit
265
296
  tests, and the actual affected runtime. Apply only the relevant lanes from
266
297
  [the change-boundary matrix](references/change-boundary-matrix.md).
267
298
 
268
- After local verification at every level, have an independent agent compare the
269
- implementation and observed user outcomes to the frozen To-Be and original request.
270
- Include cross-view consistency, omissions and applicable historical regressions.
271
- Fix findings narrowly and independently re-review the affected behavior.
299
+ After local verification, execute the frozen review plan for the effective level
300
+ and governing repository. The independent reviewer compares the implementation
301
+ and observed outcomes to the frozen To-Be and original request, including affected
302
+ cross-view links, omissions and applicable historical regressions. Fix findings
303
+ narrowly and independently re-review the affected behavior.
272
304
 
273
305
  Give the independent verifier the acceptance sources and raw evidence, not just
274
306
  the implementer's summary. Use a separate session/agent for every review; another model can help but model count is not quality evidence.
@@ -283,11 +315,14 @@ production configuration, and CI or security-policy changes.
283
315
 
284
316
  ### 4. Coordinate through Draft PR and integration state
285
317
 
286
- Before creating a PR, pass the independent To-Be review and pre-PR SQM loop.
287
318
  After authority for the required remote changes exists, record reproduction,
288
319
  tests, evidence, companion revisions, skipped checks, and remaining gates in the
289
- Draft PR. After creation, run a fresh SQM loop bound to the actual PR head/base
290
- and have an independent agent review the PR diff, proofs and incident coverage.
320
+ Draft PR so the exact remote candidate can be inspected. Before Ready or merge,
321
+ pass the reviews and explicitly opted-in SQM gates required by the frozen plan.
322
+ Rerun a review or SQM gate only when its bound head,
323
+ base, diff, specification, policy, evidence, environment or SQM bundle changed,
324
+ or when a PR-only check became applicable. Otherwise retain the verified receipt
325
+ and record its exact identity instead of duplicating ceremony.
291
326
  Before Ready or merge, combine the latest base with required companion
292
327
  and parallel changes, rerun the affected path, and confirm current CI and
293
328
  mergeability. Without combined-state proof, report `pr_only_verified`, not
@@ -301,6 +336,12 @@ the charter permits, then have Codex operate the user's Chrome through the
301
336
  browser extension as a normal user. Do not replace this gate with direct API
302
337
  calls or a fixed selector-only test.
303
338
 
339
+ Immediately after the journey, refresh and inspect the experience/acceptance table
340
+ for the same candidate revision, environment, fixture, and input. Compare the
341
+ entire trigger-to-usable-outcome path, including what the user understands and can
342
+ do next, with the frozen experience contract. Do not promote the local journey
343
+ because its individual controls, requests, or services worked in isolation.
344
+
304
345
  Read [Codex Chrome user verification](references/chrome-user-verification.md)
305
346
  before running this phase.
306
347
 
@@ -311,6 +352,13 @@ remote merge result. Wait until the target environment reports the exact merge
311
352
  revision or a verifiably derived image digest. Confirm health and runtime
312
353
  identity before live verification.
313
354
 
355
+ For a production-impacting change, release through the frozen stages. At each
356
+ stage observe the named outcome and guardrail signals for the frozen window, then
357
+ apply the deterministic decision: promote only when all promotion thresholds pass;
358
+ hold when evidence is incomplete; abort or rollback when a halt threshold fires.
359
+ Verify the rollback mechanism and kill switch before exposure. Record the owner
360
+ and a receipt for every decision. A release plan is not evidence that a stage ran.
361
+
314
362
  ### 7. Verify the deployed user journey
315
363
 
316
364
  Repeat the relevant Chrome journey against the deployed environment. Include
@@ -323,6 +371,10 @@ history or claim `done`. Save the exact revision, environment, input, Run ID,
323
371
  timestamps, visible state, network evidence, and logs, then add a boundary
324
372
  regression before re-entering implementation.
325
373
 
374
+ After the frozen observation window, evaluate the production outcome and guardrail
375
+ measures for the same experience IDs. A functioning deployment is not successful
376
+ delivery when the intended outcome misses its threshold or a guardrail is breached.
377
+
326
378
  ### Feed repaired delivery failures back into SQM
327
379
 
328
380
  When an observed failure was repaired, prepare one bounded `delivery.json` record
@@ -343,10 +395,11 @@ and validation ordering still require Delivery Loop execution controls.
343
395
 
344
396
  Remove scoped fixtures and prove the residual count is zero. Deliver a compact
345
397
  completion receipt containing shared-spec baseline/To-Be revisions and status,
346
- cross-view and implementation review results, pre/post-PR SQM proofs and historical
347
- coverage gaps, repository revisions, PR and CI, independent
398
+ the frozen review plan and identity-bound review results, applicable SQM proofs
399
+ and their reuse/rerun decision, historical coverage gaps, repository revisions, PR and CI, independent
348
400
  review, container/runtime results, local Chrome evidence, merge and deployment
349
- identity, deployed Chrome evidence, artifacts, cleanup, skipped gates, and
401
+ identity, rollout-stage and outcome/guardrail results, deployed Chrome evidence,
402
+ artifacts, cleanup, skipped gates, and
350
403
  residual risk. Convert recurring findings into the smallest maintainable rule,
351
404
  test, static check, or source-of-truth update selected by the five-response
352
405
  comparison. Bound debt cleanup by scope, change size, and completion criteria;
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## Before implementation
4
4
 
5
+ Freeze the user experience before proposing the solution. Read-only discovery may
6
+ inform it, but the first authored success record states the beneficiary and
7
+ context, their goal, the complete usable outcome, what they should see,
8
+ understand, and be able to do next at each observable moment, applicable recovery
9
+ and forbidden experiences, and how those moments will be observed. Assign stable
10
+ UX/acceptance IDs. A screen, component, endpoint, task, or stored row is an
11
+ implementation detail unless it is tied to this experience.
12
+
5
13
  The controller externalizes every acceptance condition with a stable ID, observable
6
14
  outcome, source independent of the candidate, and required checks. For each
7
15
  verification gate, freeze its applicable criteria in `charter.json` and record the
@@ -9,6 +17,20 @@ SHA-256 of the exact file bytes in the controller-owned snapshot. Reconcile the
9
17
  gate charters against the overall delivery charter so no requirement disappears.
10
18
  Do not choose a convenient subset after observing failures.
11
19
 
20
+ Use these criteria as the structured form of the experience contract rather than
21
+ creating a second UX checklist. Cover the full path from its real trigger to its
22
+ usable outcome and the applicable recovery path. When several parts must work
23
+ together for the beneficiary to succeed, include an end-to-end criterion in
24
+ addition to narrower diagnostic criteria. Independent coverage review compares
25
+ the experience contract and original request to the frozen rows before coding.
26
+
27
+ For production-relevant experience IDs, extend the same charter with outcome and
28
+ guardrail measures. Each measure names the signal, independent source,
29
+ observation window, threshold, resulting decision and owner. A rollout policy
30
+ references those measure IDs for promotion and halt decisions. These fields are
31
+ part of the same acceptance source and readable results view, not a second
32
+ success checklist.
33
+
12
34
  Each criterion has two layers:
13
35
 
14
36
  - `user`: a goal-driven user journey with visible interactions and evidence of the
@@ -49,6 +71,20 @@ After every evaluated action (test, repair, review or changed inputs):
49
71
  All rows, including pending/failed ones, stay visible. A green matrix cannot advance
50
72
  an unrelated gate or prove merge/deployment. Validate again for the final environment.
51
73
 
74
+ During staged release, append the stage, exposure, observed window, measure
75
+ results and deterministic `promote`, `hold`, `abort` or `rollback` decision to
76
+ the current results and receipt. Planned thresholds are not observations. A
77
+ healthy deployment cannot replace a missed outcome or breached guardrail.
78
+
79
+ After the exact candidate is built in a production-like local environment, run
80
+ the complete experience and regenerate `checklist.md` for that same revision,
81
+ environment, fixture, and input. Read the user and system rows together as a
82
+ single experience table: expected outcome, actual user observation, independent
83
+ postcondition/integration result, evidence, status, and next repair. Missing,
84
+ pending, failed, stale, or unverifiable applicable rows prevent
85
+ `user_like_local_verified`. Individually working components cannot override a
86
+ failed or absent end-to-end row.
87
+
52
88
  ## File contract
53
89
 
54
90
  Charter example (illustrative values must be replaced with actual requirements):
@@ -59,7 +95,7 @@ Charter example (illustrative values must be replaced with actual requirements):
59
95
  "run_id": "save-run-42",
60
96
  "criteria": [{
61
97
  "id": "SAVE-1",
62
- "outcome": "The owner can recover the saved note after returning",
98
+ "outcome": "An owner returning to the task sees the saved note, understands it was retained, and can continue editing",
63
99
  "acceptance_source": "Approved issue acceptance criteria, revision 3",
64
100
  "layers": {
65
101
  "user": {"applicability": "required", "method": "Save in Chrome, reload, sign in again and compare the note"},
@@ -37,11 +37,11 @@ messages.
37
37
 
38
38
  | Level | Name | Intended work | Verification and review policy |
39
39
  | --- | --- | --- | --- |
40
- | 1 | Maximum assurance | Authentication, payments, destructive or irreversible changes, production security/configuration, sensitive data, and risky migrations | Exercise all affected and adjacent invariants, failure/recovery and rollback paths; require the broadest applicable independent review and repository gates. |
41
- | 2 | Protected | Persistence, permissions, shared contracts, external side effects, cross-repository changes, and medium-risk migrations | Verify both evidence layers for every affected outcome, shared boundaries, retry/idempotency and recovery; require independent review of the change and affected integrations. |
42
- | 3 | Balanced (default) | Normal features and bug fixes with bounded, reversible impact | Run focused regressions, applicable static checks, the real affected runtime and main user journey; require independent design/To-Be/PR review, scoped to the affected path. |
43
- | 4 | Fast | Small, isolated, reversible UI or implementation changes | Run the direct regression and real affected path; add broader checks only for touched shared boundaries; keep mandatory review and evidence gates. |
44
- | 5 | Fastest | Copy, documentation, comments, metadata, or similarly trivial changes with no runtime/data/security effect | Verify the changed artifact, final diff, packaging/link integrity where applicable, and every repository-mandated check; do not claim untested runtime behavior. |
40
+ | 1 | Maximum assurance | Authentication, payments, destructive or irreversible changes, production security/configuration, sensitive data, and risky migrations | Exercise all affected and adjacent invariants, failure/recovery and rollback paths. Use separate independent design, implementation-boundary, and final PR/evidence reviews plus every repository gate. |
41
+ | 2 | Protected | Persistence, permissions, shared contracts, external side effects, cross-repository changes, and medium-risk migrations | Verify both evidence layers, shared boundaries, retry/idempotency, staged rollout and recovery. Require independent boundary/design and final candidate/integration review; combine them only when the reviewed inputs are exactly identical. |
42
+ | 3 | Balanced (default) | Normal features and bug fixes with bounded, reversible impact | Run focused regressions, applicable static checks, the real affected runtime and main user journey. One independent final-candidate review may cover design, implementation, affected views and evidence; repeat only when its bound identity changes. |
43
+ | 4 | Fast | Small, isolated, reversible UI or implementation changes | Run the direct regression and real affected path, adding shared-boundary checks only when touched. Use a scoped independent diff/actual-path review when repository policy or material ambiguity requires it. |
44
+ | 5 | Fastest | Copy, documentation, comments, metadata, or similarly trivial changes with no runtime/data/security effect | Verify the artifact, final diff, packaging/link integrity and repository-mandated checks. Independent review is needed only when governing policy requires it or material ambiguity remains. |
45
45
 
46
46
  Level 3 is the recommendation when no task-specific signal favors another level.
47
47
  Faster levels reduce work only by declaring genuinely unaffected lanes not
@@ -69,8 +69,9 @@ always choose a lower numbered, safer level.
69
69
  Every level preserves:
70
70
 
71
71
  - frozen success criteria and independent acceptance sources;
72
- - shared system As-Is/To-Be, cross-view consistency and independent agent reviews;
73
- - pre/post-PR SQM gates under explicit opt-in, with historical coverage reported;
72
+ - shared system As-Is/To-Be and consistency of affected cross-view links;
73
+ - the frozen risk-proportionate review plan and every governing repository review;
74
+ - SQM only under explicit opt-in, with identity-bound proof and historical coverage reported;
74
75
  - honest state transitions and current revision/context binding;
75
76
  - required user/system evidence for applicable outcomes;
76
77
  - protection of user work, secrets, data integrity, and tenant boundaries;
@@ -78,3 +79,16 @@ Every level preserves:
78
79
  - repository-required tests, reviews, CI, and approval/authority boundaries.
79
80
 
80
81
  All levels retain the [minimum functional floor](minimum-completion.md): requirement mapping, actual operation/result, and independent postcondition/integration observations. Persistence requires reload and stored-state checks. Non-runtime work verifies its real consumer/artifact; no imaginary DB or API checks are added. The minimum checker has no level-based bypass.
82
+
83
+ ## Reuse instead of duplicate ceremony
84
+
85
+ Freeze the review and SQM plan in the charter. Reuse a passing review or SQM
86
+ proof only when its repository, head, base, working-tree/diff, specification,
87
+ policy, knowledge bundle, evidence and relevant environment identities are exactly
88
+ unchanged and the proof remains verifiable. A PR URL alone is not a changed input.
89
+ Rerun the affected review or SQM phase when any bound identity changes or when a
90
+ PR-only scanner, policy or integration check first becomes applicable.
91
+
92
+ Do not reopen or re-attest every shared view. Inspect the changed views and their
93
+ declared dependency links; retain unchanged views by exact revision. Do not turn a
94
+ failed, unavailable or applicable gate into `not_applicable` to save time.
@@ -9,19 +9,25 @@ in an executor result or assurance-level selection disables the gate.
9
9
  ## Collect real observations
10
10
 
11
11
  Before implementation, reconcile the original request, acceptance criteria and
12
- planned operations. Preserve every requested outcome; a missing requirement must
12
+ planned operations against the frozen experience contract. Preserve every
13
+ observable moment from the real trigger to a usable result, including applicable
14
+ recovery and forbidden experiences; a missing requirement must
13
15
  be added through the controller's charter process, not omitted from the matrix.
14
16
  The independent auditor compares original-request coverage and checks the oracle
15
17
  against raw evidence. Machine mapping checks cannot discover an omitted idea in
16
18
  natural language or authenticate what an evidence collector observed.
17
19
 
18
- For each AC-N record implementation_refs (repository-relative file paths), then:
20
+ For each stable criterion ID record implementation_refs (repository-relative file
21
+ paths). New UX-first charters preserve the IDs supplied by the experience
22
+ contract; legacy charters without explicit IDs retain deterministic `AC-N` IDs.
23
+ Then:
19
24
 
20
25
  - user/operation: operate the actual UI or CLI/consumer. Expected is true; actual
21
26
  is whether the intended operation really executed, not just whether a click
22
27
  command succeeded. Include screenshots/traces where relevant.
23
28
  - user/result: observe the requested visible outcome, including reload and session
24
- return for a saving feature. Compare expected and actual values.
29
+ return for a saving feature. Compare expected and actual values, user
30
+ understanding, and the next action the experience makes available.
25
31
  - system/postcondition: independently inspect the final state; for persistence,
26
32
  include the saved values and ownership, not a toast or HTTP status.
27
33
  - system/integration: inspect the necessary combined path through APIs, storage,
@@ -33,6 +39,8 @@ For document-only work these mean opening the actual consumer and checking conte
33
39
  links and packaged bytes. No DB/API is required if none participates. Both generic
34
40
  layers remain required; there is no post-failure waiver. Tests, permissions or
35
41
  runtime unavailable means pending/blocked. Never fabricate expected/actual equality.
42
+ Passing narrower rows for controls, APIs, services, or artifacts cannot substitute
43
+ for the end-to-end row when the requested experience depends on their combination.
36
44
 
37
45
  ## Executable contract
38
46
 
@@ -59,7 +67,8 @@ identities in the evidence; the independent reviewer must validate their current
59
67
  identity. Do not claim that this single-checkout gate automatically re-reads a
60
68
  companion service's Git HEAD or production environment.
61
69
 
62
- Every matrix row adds `implementation_refs: ["src/example.js"]`. Each required
70
+ Every matrix row uses the same stable criterion ID and adds
71
+ `implementation_refs: ["src/example.js"]`. Each required
63
72
  user/system cell's **first** evidence file is JSON with this shape (user example):
64
73
 
65
74
  ```json
@@ -128,10 +137,10 @@ external provider calls are introduced.
128
137
 
129
138
  ## Shared specifications and independent review
130
139
 
131
- The floor's AC-N rows trace back to stable requirement IDs in the team's shared
140
+ The floor's stable criterion rows trace back to requirement IDs in the team's shared
132
141
  As-Is/To-Be specification. Follow [shared system specifications](shared-system-specifications.md)
133
- for purpose, user experience, cross-view consistency, independent reviews and
134
- pre/post-PR SQM. These are controller workflow obligations, not fields magically
142
+ for purpose, user experience, cross-view consistency, risk-proportionate independent
143
+ reviews and explicitly opted-in, identity-aware SQM. These are controller workflow obligations, not fields magically
135
144
  validated by the minimum checker. The CLI gate verifies minimum structured evidence;
136
145
  it does not launch every design review or SQM phase on its own. Standalone hosts
137
146
  remain responsible for executing this skill and cannot claim an enforced host hook.
@@ -15,22 +15,37 @@ An old design or green PR is not proof of current production behavior.
15
15
 
16
16
  At the start of each run:
17
17
 
18
- 1. Read the existing index and current linked views. Reconcile relevant As-Is with
18
+ 1. Freeze the experience contract from the original request before proposing a
19
+ solution: beneficiary/context, goal, complete usable outcome, observable
20
+ moments with what the person sees, understands, and can do next, recovery and
21
+ forbidden experiences, and the observation method. Read-only discovery may
22
+ inform this record. Give every moment a stable UX/acceptance ID.
23
+ 2. Read the existing index and current linked views. Reconcile relevant As-Is with
19
24
  code and actual behavior; label unknowns, cite evidence, and correct documented
20
25
  drift without claiming that the drift was part of the requested feature.
21
- 2. State why the change matters: beneficiary, current difficulty, what they can
22
- do after the change, observable success, and non-goals. Preserve the user's
23
- original request as an independent acceptance source.
24
- 3. Prepare a proposed To-Be on a branch/change proposal linked from the shared
26
+ 3. Reconcile why the change matters against that contract: current difficulty,
27
+ observable success, and non-goals. Preserve the user's original request as an
28
+ independent acceptance source; do not replace their outcome with a component
29
+ or implementation inventory.
30
+ 4. Prepare a proposed To-Be on a branch/change proposal linked from the shared
25
31
  system index. Record its baseline revision, affected stable IDs, before/after
26
32
  behavior, dependencies and acceptance oracles. Reuse unchanged views by reference;
27
33
  do not copy the whole specification into every run or generate empty diagrams.
28
- 4. Link the requirement to workflow, screen/control, state/sequence, API/event,
34
+ 5. Link each experience ID to workflow, screen/control, state/sequence, API/event,
29
35
  data/relationship, permission and runtime boundaries as applicable. Map these
30
- stable system IDs to the run's AC-N rows and implementation/evidence locations.
31
- 5. Obtain independent design review before implementation. Supply the original
32
- request, shared baseline, To-Be and linked unchanged views. Missing evidence or
33
- contradictory views remains pending/failed, never silently accepted.
36
+ stable system IDs to the run's criterion rows and implementation/evidence locations.
37
+ 6. Freeze the delivery-level review plan. At levels that require a separate
38
+ design review, obtain it before implementation; at level 3 the independent
39
+ final-candidate review may cover design, implementation and evidence together.
40
+ Supply the original request, shared baseline, To-Be and affected links. Missing
41
+ evidence or contradictory views remains pending/failed, never silently accepted.
42
+
43
+ After the exact local candidate is built, update the existing acceptance matrix
44
+ and its readable table for those same experience IDs. Compare expected experience,
45
+ actual user observation, independent system result, evidence, status, and next
46
+ repair across the whole journey. This is a view of the canonical acceptance data,
47
+ not another specification. A gap between working parts blocks local user-journey
48
+ promotion even if every part passed a narrower test.
34
49
 
35
50
  The twelve-view index must account for every category: present (with a canonical
36
51
  link), unchanged (with a revision), not applicable (with a reason), or unknown
@@ -55,13 +70,15 @@ interface views; do not invent application screens, APIs, tables or infrastructu
55
70
 
56
71
  ## Review evidence and neutral agents
57
72
 
58
- All reviews use independent agents/sessions, including design, cross-view
59
- consistency, implementation-versus-To-Be, verification changes and PR review.
60
- An agent must not approve work it authored. A reviewer can re-review fixes to its
61
- findings if it did not implement the fixes. Supply raw sources, not desired answers;
62
- require inspected references, discrepancies, uncertainty and a reasoned verdict.
63
- Use the repository-required model (gpt-5.5 / xhigh where mandated and available).
64
- If independent review is unavailable, report that gate as blocked.
73
+ Required reviews use independent agents/sessions; an agent must not approve work
74
+ it authored. Follow the risk-proportionate plan in `delivery-levels.md` instead of
75
+ creating design, cross-view, implementation and PR reviews by default. A reviewer
76
+ may cover several review kinds in one receipt when the candidate inputs are the
77
+ same, and can re-review fixes it did not implement. Supply raw sources, not desired
78
+ answers; require inspected references, discrepancies, uncertainty and a reasoned
79
+ verdict. Use the repository-required model (gpt-5.5 / xhigh where mandated and
80
+ available). If a required independent review is unavailable, report that gate as
81
+ blocked.
65
82
 
66
83
  Persist a compact review receipt: reviewer/session identity, review kind, original
67
84
  request and shared baseline/To-Be references, candidate revisions, inspected views
@@ -109,17 +126,22 @@ Use concrete regressions for relevant uncovered incidents and have an independen
109
126
  reviewer assess coverage; keep missing incident history or untested boundaries
110
127
  visible. No finite test promises absence of all future bugs.
111
128
 
112
- Pre-PR: implementation-to-To-Be review and relevant regressions pass, then run SQM;
113
- repair findings within the bounded loop, re-review changed work, and rerun against
114
- the final candidate before creation. On unavailable/failed proof, withhold creation
115
- and report the exact blocker while continuing independent authorized preparation.
116
-
117
- Post-PR: fetch the actual PR head/base and check out/verify that combination, run
118
- both SQM phases again into new paths, inspect proof and current CI, and obtain
119
- independent PR/evidence review. The pre-PR proof cannot stand in for this run.
120
- If it fails, retain the PR as draft/not-ready with unmet gates recorded, repair and
121
- rerun affected independent reviews and SQM. A changed head/base/spec or relevant
122
- bundle invalidates affected approvals and proofs; preserve old receipts as history.
129
+ Draft creation: after authority and local regressions, publish a Draft so the exact
130
+ remote candidate can be inspected. Draft existence is not gate approval.
131
+
132
+ Before Ready or merge, pass the frozen review plan. When the explicitly opted-in
133
+ plan requires SQM, run it against the final candidate; repair findings within the
134
+ bounded loop and rerun affected gates. On an unavailable/failed required proof,
135
+ retain Draft/not-ready and report the exact blocker while continuing independent
136
+ authorized preparation.
137
+
138
+ Post-PR: fetch and verify the actual PR head/base and current CI. Reuse the pre-PR
139
+ SQM proof only when repository, head, base, working-tree/diff, specification,
140
+ policy, knowledge bundle, evidence and relevant environment identities are exactly
141
+ unchanged and the signed proof verifies. Rerun into a new path when any identity
142
+ changed or a PR-only scanner/policy becomes applicable. Apply the same rule to
143
+ independent review. If a required gate fails, retain the PR as draft/not-ready,
144
+ repair and rerun affected gates. Preserve superseded receipts as history.
123
145
  Promote Ready only with current required gates, including applicable combined-state
124
146
  verification. Merge and deployment still require their own authority and evidence.
125
147
 
@@ -9,6 +9,8 @@ Persist one controller-owned snapshot with at least:
9
9
 
10
10
  - `run_id`, `state`, monotonic `revision`, and terminal disposition;
11
11
  - immutable explicit conditions and their stable digest;
12
+ - frozen experience-contract identities, their mapping to acceptance rows, and
13
+ the current readable local-experience table path and digest;
12
14
  - acceptance-source identities and digests, architecture boundaries, applicable
13
15
  rule decisions, risk tier, requested/effective delivery levels, the safety
14
16
  ceiling reason, and chat-confirmation receipt;
@@ -20,7 +22,8 @@ Persist one controller-owned snapshot with at least:
20
22
  - frozen acceptance charter digest, current matrix/table paths and digests,
21
23
  superseded matrix snapshots, and the acceptance validator result for each gate;
22
24
  - verification-boundary changes, deferred-debt references, and applicable
23
- outcome and guardrail measures;
25
+ outcome and guardrail measures, rollout policy, current rollout stage and
26
+ latest deterministic release decision;
24
27
  - attempt number, retry/time/cost budgets, and failure fingerprints;
25
28
  - append-only receipt IDs, evidence references, timestamps, and cleanup state.
26
29
 
@@ -44,6 +47,8 @@ Use the smallest applicable receipts rather than free-form completion claims:
44
47
  - `ChromeUserJourneyReceipt`
45
48
  - `MergeReceipt`
46
49
  - `DeploymentIdentityReceipt`
50
+ - `RolloutStageReceipt`
51
+ - `OutcomeEvaluationReceipt`
47
52
  - `HealthReceipt`
48
53
  - `LiveEvidenceReceipt`
49
54
  - `CleanupReceipt`
@@ -106,6 +111,8 @@ Evidence is revision-bound:
106
111
  | Merge SHA changes | Deployment identity and live evidence |
107
112
  | Deployed revision changes | Health and live evidence |
108
113
  | Browser profile, tenant, or fixture changes | Affected user-journey evidence |
114
+ | Outcome/guardrail signal, source, window, threshold, decision, or owner changes | Affected release and outcome evidence |
115
+ | Rollout stage, exposure, recovery control, or kill switch changes | Affected rollout and later live evidence |
109
116
 
110
117
  Do not discard historical receipts; mark them superseded for the current gate.
111
118
 
@@ -144,11 +151,13 @@ done =
144
151
  && deployed_exact_revision
145
152
  && health_verified
146
153
  && live_verified
154
+ && production_outcome_evaluated
147
155
  && cleanup_verified
148
156
  && completion_receipt_created
149
157
  ```
150
158
 
151
- Remove only genuinely non-applicable terms and record why. If merge or deploy
159
+ Remove only genuinely non-applicable terms and record why; production outcome
160
+ evaluation is non-applicable when no production-impacting release is in scope. If merge or deploy
152
161
  is outside current authority, stop at the highest proven pre-authority state.
153
162
 
154
163
  Acceptance evidence must cover every applicable criterion for the target gate,
@@ -156,5 +165,8 @@ with current context and the required user/system layers. After each evaluated
156
165
  action, refresh the JSON matrix and its readable table; append a receipt for the
157
166
  update. Keep the frozen charter distinct from mutable results. A charter patch
158
167
  supersedes the old digest only after the controller verifies its authority.
168
+ For `user_like_local_verified`, the current table must cover the frozen experience
169
+ contract from real trigger to usable outcome for the exact local candidate. A
170
+ collection of narrower passing rows does not waive a missing end-to-end row.
159
171
 
160
172
  The CLI records minimum evidence in observed_evidence.minimum_completion, independently of executor status. The independent audit is bound to its matrix digest. Legacy v2 histories remain readable, but missing minimum evidence cannot be reused as passing evidence on resume/recheck. Missing or unsupported charters require a refresh; they do not disable verification.
@@ -25,7 +25,15 @@ function requireValue(condition, message) {
25
25
  }
26
26
 
27
27
  function validateCharter(charter) {
28
- requireValue(object(charter) && charter.schema_version === 1 && text(charter.run_id), "Invalid charter header");
28
+ requireValue(object(charter) && [1, 2].includes(charter.schema_version) && text(charter.run_id), "Invalid charter header");
29
+ if (charter.schema_version === 2) {
30
+ requireValue(object(charter.experience), "Experience contract required");
31
+ for (const field of ["beneficiary", "situation", "goal", "usable_outcome", "recovery"])
32
+ requireValue(text(charter.experience[field]), `Experience contract needs ${field}`);
33
+ requireValue(Array.isArray(charter.experience.forbidden_experiences) &&
34
+ charter.experience.forbidden_experiences.length > 0 &&
35
+ charter.experience.forbidden_experiences.every(text), "Experience contract needs forbidden experiences");
36
+ }
29
37
  requireValue(Array.isArray(charter.criteria) && charter.criteria.length > 0, "Charter requires criteria");
30
38
  const ids = new Set();
31
39
  for (const criterion of charter.criteria) {
@@ -88,15 +96,24 @@ export async function readEvidenceArtifact(root, artifact) {
88
96
  const tableText = (value) => String(value ?? "").replaceAll("|", "\\|").replace(/[\r\n]+/g, " ");
89
97
 
90
98
  export function renderChecklist(report) {
99
+ const experienceContext = report.experience
100
+ ? `${report.experience.beneficiary} / ${report.experience.situation}`
101
+ : "Legacy charter: persona and situation not recorded";
91
102
  const header = [
92
- "| Criterion / outcome | Layer / method | Status | Observation | Evidence | Unmet reason / next action |",
93
- "| --- | --- | --- | --- | --- | --- |",
103
+ "| Experience criterion / expected outcome | Persona / situation | Layer / method | Status | Actual observation | Evidence | Unmet reason / next repair |",
104
+ "| --- | --- | --- | --- | --- | --- | --- |",
94
105
  ];
95
106
  const rows = report.checks.map((row) => "| " + [
96
- `${row.id}: ${row.outcome}`, `${row.layer}: ${row.method}`, row.status,
107
+ `${row.id}: ${row.outcome}`, experienceContext,
108
+ `${row.layer}: ${row.method}`, row.status,
97
109
  row.observation, row.evidence.join(", "), [row.reason, row.next_action].filter(Boolean).join(" / "),
98
110
  ].map(tableText).join(" | ") + " |");
111
+ const context = report.context;
112
+ const candidate = context
113
+ ? `Candidate context: stage=${tableText(context.stage)}; environment=${tableText(context.environment)}; fixture=${tableText(context.fixture_id)}; input_sha256=${tableText(context.input_sha256)}; source_revisions=${tableText(JSON.stringify(context.source_revisions))}`
114
+ : "Candidate context: unavailable";
99
115
  return [...header, ...rows, "", `Validator: ${report.status}`,
116
+ candidate,
100
117
  ...report.errors.map((error) => `- ${tableText(error)}`)].join("\n") + "\n";
101
118
  }
102
119
 
@@ -111,7 +128,8 @@ export async function checkAcceptance({ charterBytes, matrix, expectedCharterSha
111
128
  requireValue(contextDigest(matrix.context) === expectedContextSha256, "Current context digest mismatch");
112
129
  requireValue(Array.isArray(matrix.results), "Matrix requires results");
113
130
  const root = await realpath(evidenceRoot);
114
- const report = { status: "evidence_incomplete", checks: [], errors: [] };
131
+ const report = { status: "evidence_incomplete", experience: charter.experience ?? null,
132
+ context: matrix.context, checks: [], errors: [] };
115
133
  const results = new Map();
116
134
  const expectedIds = new Set(charter.criteria.map((criterion) => criterion.id));
117
135
  for (const row of matrix.results) {
@@ -4,6 +4,7 @@ import { lstat, readFile, realpath } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { isDeepStrictEqual } from "node:util";
7
+ import { normalizeProductionContract } from "./production-contract.mjs";
7
8
  import {
8
9
  checkAcceptance,
9
10
  contextDigest,
@@ -35,13 +36,23 @@ export function minimumCharter(charter) {
35
36
  charter.completion_contract.minimum_scope || charter.acceptance_criteria,
36
37
  non_goals: charter.completion_contract.non_goals || [],
37
38
  };
39
+ const ids = criterionIds(
40
+ charter.completion_contract.criterion_ids,
41
+ charter.acceptance_criteria.length,
42
+ );
43
+ const experience = minimumExperience(
44
+ charter.completion_contract.experience,
45
+ charter.completion_contract.user_outcome || charter.requirement,
46
+ );
38
47
  return {
39
- schema_version: 1,
48
+ schema_version: 2,
40
49
  minimum_completion_version: MINIMUM_COMPLETION_VERSION,
41
50
  run_id: charter.id,
42
51
  source,
52
+ experience,
53
+ ...normalizeProductionContract(charter.completion_contract, ids),
43
54
  criteria: charter.acceptance_criteria.map((outcome, index) => ({
44
- id: `AC-${index + 1}`,
55
+ id: ids[index],
45
56
  outcome,
46
57
  acceptance_source: `request:${contextDigest(source)}`,
47
58
  layers: {
@@ -60,6 +71,49 @@ export function minimumCharter(charter) {
60
71
  };
61
72
  }
62
73
 
74
+ function criterionIds(value, count) {
75
+ if (value === undefined || value === null)
76
+ return Array.from({ length: count }, (_, index) => `AC-${index + 1}`);
77
+ if (!Array.isArray(value)) throw new Error("Invalid acceptance criterion IDs");
78
+ const ids = value.map((id) => (text(id) ? id.trim() : ""));
79
+ if (
80
+ ids.length !== count ||
81
+ new Set(ids).size !== ids.length ||
82
+ ids.some(
83
+ (id) => !/^[A-Za-z][A-Za-z0-9]*(?:[-_.][A-Za-z0-9]+)*$/.test(id),
84
+ )
85
+ )
86
+ throw new Error("Invalid acceptance criterion IDs");
87
+ return ids;
88
+ }
89
+
90
+ function minimumExperience(value, userOutcome) {
91
+ const source = object(value) ? value : {};
92
+ return {
93
+ beneficiary: text(source.beneficiary)
94
+ ? source.beneficiary.trim()
95
+ : "The requester",
96
+ situation: text(source.situation)
97
+ ? source.situation.trim()
98
+ : "Using the requested changed behavior",
99
+ goal: text(source.goal) ? source.goal.trim() : userOutcome,
100
+ usable_outcome: text(source.usable_outcome)
101
+ ? source.usable_outcome.trim()
102
+ : userOutcome,
103
+ recovery: text(source.recovery)
104
+ ? source.recovery.trim()
105
+ : "Unmet acceptance rows remain visible with the next repair.",
106
+ forbidden_experiences:
107
+ Array.isArray(source.forbidden_experiences) &&
108
+ source.forbidden_experiences.length > 0 &&
109
+ source.forbidden_experiences.every(text)
110
+ ? source.forbidden_experiences.map((item) => item.trim())
111
+ : [
112
+ "Reporting completion from components or commands without the end-to-end outcome",
113
+ ],
114
+ };
115
+ }
116
+
63
117
  export function minimumContext(charter, headSha) {
64
118
  const frozen = minimumCharter(charter);
65
119
  return {
@@ -0,0 +1,158 @@
1
+ // Shared by the CLI and the independently installed skill. Keep this module
2
+ // self-contained so validation never depends on the repository checkout.
3
+ const cleanString = value => typeof value === "string" ? value.trim() : "";
4
+ const record = value => value && typeof value === "object" && !Array.isArray(value) ? value : {};
5
+ const uniqueStrings = value => [...new Set(Array.isArray(value) ? value.map(cleanString).filter(Boolean) : [])];
6
+ const criterionIdPattern = /^[A-Za-z][A-Za-z0-9]*(?:[-_.][A-Za-z0-9]+)*$/;
7
+
8
+ export function normalizeProductionContract(source, ids) {
9
+ const outcome_contract = normalizeOutcomeContract(source.outcome_contract, ids);
10
+ return { outcome_contract, rollout_policy: normalizeRolloutPolicy(source.rollout_policy, outcome_contract) };
11
+ }
12
+
13
+ function normalizeOutcomeContract(value, criterionIds) {
14
+ const source = record(value);
15
+ const criteria = new Set(criterionIds);
16
+ const outcomeMeasures = normalizeMeasures(
17
+ source.outcome_measures,
18
+ criteria,
19
+ new Set(["promote", "hold"]),
20
+ "outcome",
21
+ );
22
+ const guardrailMeasures = normalizeMeasures(
23
+ source.guardrail_measures,
24
+ criteria,
25
+ new Set(["hold", "abort", "rollback"]),
26
+ "guardrail",
27
+ );
28
+ const allIds = [
29
+ ...outcomeMeasures.map((measure) => measure.id),
30
+ ...guardrailMeasures.map((measure) => measure.id),
31
+ ];
32
+ if (new Set(allIds).size !== allIds.length) {
33
+ throw new Error("Duplicate outcome or guardrail measure IDs");
34
+ }
35
+ return {
36
+ outcome_measures: outcomeMeasures,
37
+ guardrail_measures: guardrailMeasures,
38
+ };
39
+ }
40
+
41
+ function normalizeMeasures(value, criterionIds, allowedDecisions, kind) {
42
+ if (value === undefined || value === null) return [];
43
+ if (!Array.isArray(value)) throw new Error(`Invalid ${kind} measures`);
44
+ const measures = value.map((item) => {
45
+ const source = record(item);
46
+ const measure = {
47
+ id: cleanString(source.id),
48
+ criterion_id: cleanString(source.criterion_id),
49
+ signal: cleanString(source.signal),
50
+ source: cleanString(source.source),
51
+ window: cleanString(source.window),
52
+ threshold: cleanString(source.threshold),
53
+ decision: cleanString(source.decision).toLowerCase(),
54
+ owner: cleanString(source.owner),
55
+ };
56
+ if (
57
+ !criterionIdPattern.test(measure.id) ||
58
+ !criterionIds.has(measure.criterion_id) ||
59
+ !measure.signal ||
60
+ !measure.source ||
61
+ !measure.window ||
62
+ !measure.threshold ||
63
+ !allowedDecisions.has(measure.decision) ||
64
+ !measure.owner
65
+ ) {
66
+ throw new Error(`Invalid ${kind} measure`);
67
+ }
68
+ return measure;
69
+ });
70
+ if (new Set(measures.map((measure) => measure.id)).size !== measures.length) {
71
+ throw new Error(`Duplicate ${kind} measure IDs`);
72
+ }
73
+ return measures;
74
+ }
75
+
76
+ function normalizeRolloutPolicy(value, outcomeContract) {
77
+ const source = record(value);
78
+ const required = source.required === true;
79
+ if (!required) {
80
+ return {
81
+ required: false,
82
+ reason: cleanString(source.reason) || "No production rollout is in scope",
83
+ stages: [],
84
+ promotion_requires: [],
85
+ halt_on: [],
86
+ rollback: null,
87
+ kill_switch: null,
88
+ };
89
+ }
90
+ const stages = normalizeRolloutStages(source.stages);
91
+ const outcomeIds = new Set(
92
+ outcomeContract.outcome_measures.map((measure) => measure.id),
93
+ );
94
+ const guardrailIds = new Set(
95
+ outcomeContract.guardrail_measures.map((measure) => measure.id),
96
+ );
97
+ const promotionRequires = uniqueStrings(source.promotion_requires);
98
+ const haltOn = uniqueStrings(source.halt_on);
99
+ const rollback = normalizeRecoveryControl(source.rollback, "rollback");
100
+ const killSwitch = normalizeRecoveryControl(source.kill_switch, "kill switch");
101
+ if (
102
+ stages.length < 2 ||
103
+ outcomeIds.size === 0 ||
104
+ promotionRequires.length !== outcomeIds.size ||
105
+ promotionRequires.some((id) => !outcomeIds.has(id)) ||
106
+ guardrailIds.size === 0 ||
107
+ haltOn.length !== guardrailIds.size ||
108
+ haltOn.some((id) => !guardrailIds.has(id))
109
+ ) {
110
+ throw new Error("Invalid progressive rollout policy");
111
+ }
112
+ return {
113
+ required: true,
114
+ reason: cleanString(source.reason) || "Production impact requires progressive exposure",
115
+ stages,
116
+ promotion_requires: promotionRequires,
117
+ halt_on: haltOn,
118
+ rollback,
119
+ kill_switch: killSwitch,
120
+ };
121
+ }
122
+
123
+ function normalizeRolloutStages(value) {
124
+ if (!Array.isArray(value)) throw new Error("Invalid rollout stages");
125
+ const stages = value.map((item) => {
126
+ const source = record(item);
127
+ const stage = {
128
+ id: cleanString(source.id),
129
+ exposure: cleanString(source.exposure),
130
+ observation_window: cleanString(source.observation_window),
131
+ };
132
+ if (
133
+ !criterionIdPattern.test(stage.id) ||
134
+ !stage.exposure ||
135
+ !stage.observation_window
136
+ ) {
137
+ throw new Error("Invalid rollout stage");
138
+ }
139
+ return stage;
140
+ });
141
+ if (new Set(stages.map((stage) => stage.id)).size !== stages.length) {
142
+ throw new Error("Duplicate rollout stage IDs");
143
+ }
144
+ return stages;
145
+ }
146
+
147
+ function normalizeRecoveryControl(value, label) {
148
+ const source = record(value);
149
+ const control = {
150
+ mechanism: cleanString(source.mechanism),
151
+ owner: cleanString(source.owner),
152
+ };
153
+ if (!control.mechanism || !control.owner) {
154
+ throw new Error(`Invalid ${label} control`);
155
+ }
156
+ return control;
157
+ }
158
+
@@ -1,6 +1,8 @@
1
+ import { normalizeProductionContract } from "../codex-plugins/simy/skills/agentic-delivery-loop/scripts/production-contract.mjs";
1
2
  import { cleanString, stringArray } from "./shared.js";
2
3
 
3
4
  export const COMPLETION_CONTRACT_VERSION = 2;
5
+ const criterionIdPattern = /^[A-Za-z][A-Za-z0-9]*(?:[-_.][A-Za-z0-9]+)*$/;
4
6
 
5
7
  export function buildCompletionContract({
6
8
  inherited,
@@ -12,11 +14,18 @@ export function buildCompletionContract({
12
14
  const source = record(inherited);
13
15
  const minimumScope = stringArray(source.minimum_scope);
14
16
  const proofRequired = stringArray(source.proof_required);
17
+ const criteria = stringArray(acceptanceCriteria);
18
+ const userOutcome = cleanString(source.user_outcome) || cleanString(requirement);
19
+ const ids = criterionIds(source.criterion_ids, criteria.length);
20
+ const productionContract = normalizeProductionContract(source, ids);
15
21
  return {
16
22
  version: COMPLETION_CONTRACT_VERSION,
17
- user_outcome: cleanString(source.user_outcome) || cleanString(requirement),
23
+ user_outcome: userOutcome,
18
24
  minimum_scope:
19
- minimumScope.length > 0 ? minimumScope : stringArray(acceptanceCriteria),
25
+ minimumScope.length > 0 ? minimumScope : criteria,
26
+ criterion_ids: ids,
27
+ experience: normalizeExperience(source.experience, userOutcome),
28
+ ...productionContract,
20
29
  non_goals: stringArray(nonGoals),
21
30
  proof_required: uniqueStrings([
22
31
  "criterion_evidence",
@@ -29,12 +38,53 @@ export function buildCompletionContract({
29
38
  }
30
39
 
31
40
  export function acceptanceCriterionRows(charter) {
32
- return stringArray(charter?.acceptance_criteria).map((statement, index) => ({
33
- id: `AC-${index + 1}`,
41
+ const statements = stringArray(charter?.acceptance_criteria);
42
+ const ids = criterionIds(
43
+ record(charter?.completion_contract).criterion_ids,
44
+ statements.length,
45
+ );
46
+ return statements.map((statement, index) => ({
47
+ id: ids[index],
34
48
  statement,
35
49
  }));
36
50
  }
37
51
 
52
+ function criterionIds(value, count) {
53
+ if (value === undefined || value === null) {
54
+ return Array.from({ length: count }, (_, index) => `AC-${index + 1}`);
55
+ }
56
+ if (!Array.isArray(value)) throw new Error("Invalid acceptance criterion IDs");
57
+ const ids = value.map(cleanString);
58
+ if (
59
+ ids.length !== count ||
60
+ new Set(ids).size !== ids.length ||
61
+ ids.some((id) => !criterionIdPattern.test(id))
62
+ ) {
63
+ throw new Error("Invalid acceptance criterion IDs");
64
+ }
65
+ return ids;
66
+ }
67
+
68
+ function normalizeExperience(value, userOutcome) {
69
+ const source = record(value);
70
+ return {
71
+ beneficiary: cleanString(source.beneficiary) || "The requester",
72
+ situation:
73
+ cleanString(source.situation) || "Using the requested changed behavior",
74
+ goal: cleanString(source.goal) || userOutcome,
75
+ usable_outcome: cleanString(source.usable_outcome) || userOutcome,
76
+ recovery:
77
+ cleanString(source.recovery) ||
78
+ "Unmet acceptance rows remain visible with the next repair.",
79
+ forbidden_experiences:
80
+ stringArray(source.forbidden_experiences).length > 0
81
+ ? stringArray(source.forbidden_experiences)
82
+ : [
83
+ "Reporting completion from components or commands without the end-to-end outcome",
84
+ ],
85
+ };
86
+ }
87
+
38
88
  export function normalizeUncertaintyVerification(value) {
39
89
  const source = record(value);
40
90
  return {
@@ -65,6 +65,7 @@ export function createCodingLoopSnapshot({ runId, request, metadata = {} }) {
65
65
  const inheritedArtifacts = stringArray(charterContext.artifacts_required);
66
66
  const inheritedAssumptions = stringArray(charterContext.assumptions);
67
67
  const inheritedCompletionContract = record(charterContext.completion_contract);
68
+ const inheritedExperienceContract = record(charterContext.experience_contract);
68
69
  const finalAcceptanceCriteria =
69
70
  acceptanceCriteria.length > 0
70
71
  ? acceptanceCriteria
@@ -157,7 +158,17 @@ export function createCodingLoopSnapshot({ runId, request, metadata = {} }) {
157
158
  visual_review_required: browserEvidenceRequired,
158
159
  },
159
160
  completion_contract: buildCompletionContract({
160
- inherited: inheritedCompletionContract,
161
+ inherited: {
162
+ ...inheritedCompletionContract,
163
+ ...(Object.keys(inheritedExperienceContract).length > 0
164
+ ? {
165
+ experience: inheritedExperienceContract,
166
+ criterion_ids:
167
+ inheritedExperienceContract.criterion_ids ??
168
+ inheritedCompletionContract.criterion_ids,
169
+ }
170
+ : {}),
171
+ },
161
172
  requirement: cleanString(charterContext.user_goal) || cleanString(request.requirement),
162
173
  acceptanceCriteria: finalAcceptanceCriteria,
163
174
  nonGoals: finalNonGoals,
@@ -27,7 +27,8 @@ export function buildIndependentAuditInstruction(charter, attempt) {
27
27
  [
28
28
  `Requirement: ${charter.requirement}`,
29
29
  `Original request: ${charter.thread_state?.original_request || charter.requirement}`,
30
- `Acceptance criteria:\n${bullets(charter.acceptance_criteria)}`,
30
+ `Acceptance criteria:\n${bullets(acceptanceCriterionRows(charter).map(row => `${row.id}: ${row.statement}`))}`,
31
+ `Frozen experience (controller-owned): ${JSON.stringify(charter.completion_contract?.experience ?? null)}`,
31
32
  `User-visible outcome: ${charter.completion_contract?.user_outcome || charter.requirement}`,
32
33
  `Minimum scope:\n${bullets(charter.completion_contract?.minimum_scope || charter.acceptance_criteria)}`,
33
34
  `Risk level: ${charter.risk.level}`,
@@ -62,8 +63,11 @@ export function buildIndependentAuditInstruction(charter, attempt) {
62
63
  "Inspect the final diff and related code directly from the repository.",
63
64
  "Look for unmet acceptance criteria, regressions, security issues, data and concurrency risks, missing tests, and unrelated changes.",
64
65
  "Treat executor claims as untrusted until supported by repository evidence.",
65
- "Compare the original request to the complete AC-N list and implementation mapping. Report forgotten features, dead controls, unsaved results and broken integration. Open the raw minimum evidence, verify its oracle and actual runtime observations; matching expected/actual JSON and hashes alone do not prove truth. Check reload/session return and persisted values/ownership when saving is required.",
66
- "For every AC-N criterion, return a criterion_results entry only after directly checking its cited proof and observed outcome.",
66
+ "Compare the original request and frozen experience to the complete stable criterion-ID list and implementation mapping. Report forgotten features, dead controls, unsaved results and broken integration. Open the raw minimum evidence, verify its oracle and actual runtime observations; matching expected/actual JSON and hashes alone do not prove truth. Check reload/session return and persisted values/ownership when saving is required.",
67
+ "For every stable criterion ID, return a criterion_results entry only after directly checking its cited proof and observed outcome.",
68
+ "For production-relevant criteria, verify that each outcome and guardrail measure names its signal, independent source, observation window, threshold, decision, owner, and criterion ID.",
69
+ "When rollout_policy.required=true, verify that staged exposure and deterministic promote, hold, abort, rollback, and kill-switch controls are frozen before release. Do not treat a rollout plan as deployed evidence.",
70
+ "Apply the frozen delivery-level review plan: reuse a prior review only when its exact head, base, specification, policy, evidence, and environment identities are unchanged; otherwise review the affected boundary again.",
67
71
  charter.evidence_policy?.browser_required
68
72
  ? "Open and inspect the actual local screenshots or videos. Return visual_review.inspected=true only after checking their visible content against the acceptance criteria."
69
73
  : "Do not require visual evidence when the Charter does not require browser evidence.",
@@ -75,9 +79,9 @@ export function buildIndependentAuditInstruction(charter, attempt) {
75
79
  [
76
80
  "Finish with exactly one line beginning SIMY_AUDIT_JSON: followed by one JSON object.",
77
81
  "Required keys: passed, summary, findings, criterion_results, visual_review, coverage_review.",
78
- "coverage_review must contain requirements_complete (boolean), unmapped_requirements (array), and observation explaining the comparison of the original request, AC-N list, implementation and raw user/system proof. Use false and list gaps when anything is missing or unverified.",
82
+ "coverage_review must contain requirements_complete (boolean), unmapped_requirements (array), and observation explaining the comparison of the original request, frozen experience, stable criterion-ID list, implementation and raw user/system proof. Use false and list gaps when anything is missing or unverified.",
79
83
  "Each finding candidate must contain passed, code, severity, target, explanation, and repairability.",
80
- "criterion_results must contain one entry per AC-N with criterion_id, passed, verification_method, evidence_refs, and observed_result.",
84
+ "criterion_results must contain one entry per stable criterion ID with criterion_id, passed, verification_method, evidence_refs, and observed_result.",
81
85
  "visual_review must contain inspected, artifacts, and acceptance_observations.",
82
86
  "Only passed=false entries are findings. Omit passing checks, and never report passed=true evidence as a finding.",
83
87
  "Use passed=false when a blocker or major finding remains, or when evidence is unavailable.",
@@ -37,6 +37,15 @@ export function buildCodingInstruction(
37
37
  "completion_contract",
38
38
  [
39
39
  `User-visible outcome: ${charter.completion_contract?.user_outcome || charter.thread_state.user_goal}`,
40
+ `Beneficiary: ${charter.completion_contract?.experience?.beneficiary || "not recorded"}`,
41
+ `Situation: ${charter.completion_contract?.experience?.situation || "not recorded"}`,
42
+ `Experience goal: ${charter.completion_contract?.experience?.goal || "not recorded"}`,
43
+ `Usable outcome: ${charter.completion_contract?.experience?.usable_outcome || "not recorded"}`,
44
+ `Recovery experience: ${charter.completion_contract?.experience?.recovery || "not recorded"}`,
45
+ `Forbidden experiences:\n${bullets(charter.completion_contract?.experience?.forbidden_experiences || [])}`,
46
+ `Outcome measures: ${JSON.stringify(charter.completion_contract?.outcome_contract?.outcome_measures || [])}`,
47
+ `Guardrail measures: ${JSON.stringify(charter.completion_contract?.outcome_contract?.guardrail_measures || [])}`,
48
+ `Progressive rollout policy: ${JSON.stringify(charter.completion_contract?.rollout_policy || { required: false })}`,
40
49
  `Minimum scope:\n${bullets(charter.completion_contract?.minimum_scope || charter.acceptance_criteria)}`,
41
50
  `Non-goals:\n${bullets(charter.completion_contract?.non_goals || charter.thread_state.non_goals)}`,
42
51
  `Proof required:\n${bullets(charter.completion_contract?.proof_required || [])}`,
@@ -52,10 +61,10 @@ export function buildCodingInstruction(
52
61
  ),
53
62
  ),
54
63
  section("minimum_completion_evidence", [
55
- "All delivery levels require this floor. Before implementation reconcile every original requirement with the AC-N list; report omissions instead of silently dropping them.",
64
+ "All delivery levels require this floor. Before implementation reconcile every original requirement and frozen experience with the stable criterion-ID list; report omissions instead of silently dropping them.",
56
65
  "Save a matrix.json under the local evidence root (ui_evidence_root or repository .artifacts). Return delivery_evidence_path in SIMY_RESULT_JSON. A passed flag or tests_run string is not proof.",
57
66
  "Use check-minimum-completion.mjs from the installed Delivery Loop skill. Its reference minimum-completion.md defines the matrix and structured observation format.",
58
- "For each AC-N include real implementation_refs and required user/system evidence. Exercise the actual operation and result, then inspect postcondition and integration independently. For saving, reload/return in a new session and verify stored values and ownership. Document-only work uses the real command/consumer and artifact integrity, not imaginary API/DB checks.",
67
+ "For each stable criterion ID include real implementation_refs and required user/system evidence. Exercise the actual operation and result, then inspect postcondition and integration independently. For saving, reload/return in a new session and verify stored values and ownership. Document-only work uses the real command/consumer and artifact integrity, not imaginary API/DB checks.",
59
68
  "First evidence artifact per layer must be JSON: {criterion_id, context_sha256, checks}. User checks: operation (expected=true, actual=observed boolean) and result; system checks: postcondition and integration. Each check includes subject, expected and actual. Derive expected values from the original requirement, not from the candidate output. Include screenshots/logs as additional evidence.",
60
69
  "Freeze and bind observations to the current committed revision and charter before verification. Do not rehash old observations to make changed code pass. Use pending/failed for unavailable or mismatching evidence.",
61
70
  `Controller input JSON: ${JSON.stringify({ id: charter.id, requirement: charter.requirement, repository: charter.repository, acceptance_criteria: charter.acceptance_criteria, completion_contract: charter.completion_contract, thread_state: { original_request: charter.thread_state.original_request } })}`,
@@ -145,7 +154,7 @@ export function buildCodingInstruction(
145
154
  "Required keys: outcome_kind, summary, branch_name, commit_sha, commit_message_headline, pr_url, pr_number, pr_title, pr_base_branch, tests_run, tests_passed, ui_evidence_path, unrelated_changes_detected, secret_scan_passed, changed_files, residual_risks, problem_solving, uncertainty_verification, criterion_evidence, visual_review, delivery_evidence_path.",
146
155
  problemSolvingResultContract(),
147
156
  "uncertainty_verification must contain top_uncertainty, first_verification, evidence_checked, observed_result, resolved, and fallback. Only set resolved=true when concrete observed evidence resolves it.",
148
- "criterion_evidence must contain one entry for every AC-N with criterion_id, passed, verification_method, evidence_refs, and observed_result. Only set passed=true when the evidence directly proves that criterion.",
157
+ "criterion_evidence must contain one entry for every stable criterion ID with criterion_id, passed, verification_method, evidence_refs, and observed_result. Only set passed=true when the evidence directly proves that criterion.",
149
158
  "For UI or browser-extension work, visual_review must contain inspected=true, the local screenshot or video paths in artifacts, and concrete acceptance_observations from viewing the actual content. Capturing a file without viewing it does not count.",
150
159
  "Use null for unavailable scalar values and [] for unavailable arrays. Do not claim evidence that was not observed.",
151
160
  ].join("\n"),
@@ -69,7 +69,7 @@ export async function collectMinimumCompletion({
69
69
  return {
70
70
  status: "evidence_incomplete",
71
71
  errors: [
72
- "Minimum completion evidence missing, invalid or stale; verify the actual operation, result, postcondition and integration for every AC-N.",
72
+ "Minimum completion evidence missing, invalid or stale; verify the actual operation, result, postcondition and integration for every stable criterion ID.",
73
73
  ],
74
74
  };
75
75
  }