@awak-app/simy-cli 0.5.8 → 0.5.11
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/README.md +64 -0
- package/package.json +7 -3
- package/src/agent.js +70 -9
- package/src/auto-update.js +312 -35
- package/src/browser.js +24 -7
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/SKILL.md +227 -18
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/references/acceptance-evidence.md +131 -0
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/references/change-boundary-matrix.md +20 -1
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/references/cross-repository-control.md +76 -0
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/references/delivery-levels.md +80 -0
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/references/minimum-completion.md +137 -0
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/references/shared-system-specifications.md +138 -0
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/references/state-and-receipts.md +18 -0
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/scripts/check-acceptance.mjs +193 -0
- package/src/codex-plugins/simy/skills/agentic-delivery-loop/scripts/check-minimum-completion.mjs +221 -0
- package/src/console/app.js +21 -2
- package/src/console/commands.js +3 -0
- package/src/desktop-host.js +220 -0
- package/src/desktop-process-lock.js +231 -0
- package/src/desktop-status.js +263 -0
- package/src/direct-executor.js +6 -0
- package/src/index.js +37 -11
- package/src/install-update.js +494 -0
- package/src/orchestrator/audit.js +17 -2
- package/src/orchestrator/completion-contract.js +1 -1
- package/src/orchestrator/evidence.js +9 -1
- package/src/orchestrator/independent-audit.js +13 -1
- package/src/orchestrator/instruction.js +13 -1
- package/src/orchestrator/loop.js +34 -4
- package/src/orchestrator/minimum-completion.js +108 -0
- package/src/orchestrator/result.js +7 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Minimum functional completion
|
|
2
|
+
|
|
3
|
+
Every level must prove that the requested work exists, actual operations reach
|
|
4
|
+
the intended result, and necessary persistence/integration works. Speed changes
|
|
5
|
+
adjacent verification breadth, never this floor. The CLI requires this matrix for
|
|
6
|
+
all coding-loop attempts, including legacy v2 resumes/rechecks. No opt-out field
|
|
7
|
+
in an executor result or assurance-level selection disables the gate.
|
|
8
|
+
|
|
9
|
+
## Collect real observations
|
|
10
|
+
|
|
11
|
+
Before implementation, reconcile the original request, acceptance criteria and
|
|
12
|
+
planned operations. Preserve every requested outcome; a missing requirement must
|
|
13
|
+
be added through the controller's charter process, not omitted from the matrix.
|
|
14
|
+
The independent auditor compares original-request coverage and checks the oracle
|
|
15
|
+
against raw evidence. Machine mapping checks cannot discover an omitted idea in
|
|
16
|
+
natural language or authenticate what an evidence collector observed.
|
|
17
|
+
|
|
18
|
+
For each AC-N record implementation_refs (repository-relative file paths), then:
|
|
19
|
+
|
|
20
|
+
- user/operation: operate the actual UI or CLI/consumer. Expected is true; actual
|
|
21
|
+
is whether the intended operation really executed, not just whether a click
|
|
22
|
+
command succeeded. Include screenshots/traces where relevant.
|
|
23
|
+
- user/result: observe the requested visible outcome, including reload and session
|
|
24
|
+
return for a saving feature. Compare expected and actual values.
|
|
25
|
+
- system/postcondition: independently inspect the final state; for persistence,
|
|
26
|
+
include the saved values and ownership, not a toast or HTTP status.
|
|
27
|
+
- system/integration: inspect the necessary combined path through APIs, storage,
|
|
28
|
+
workers or consumers. Job acceptance is not job completion. Use the same input
|
|
29
|
+
and execution context across layers; cite trace/runtime identities in subject
|
|
30
|
+
and additional evidence.
|
|
31
|
+
|
|
32
|
+
For document-only work these mean opening the actual consumer and checking content,
|
|
33
|
+
links and packaged bytes. No DB/API is required if none participates. Both generic
|
|
34
|
+
layers remain required; there is no post-failure waiver. Tests, permissions or
|
|
35
|
+
runtime unavailable means pending/blocked. Never fabricate expected/actual equality.
|
|
36
|
+
|
|
37
|
+
## Executable contract
|
|
38
|
+
|
|
39
|
+
`check-minimum-completion.mjs` and `check-acceptance.mjs` ship together under the
|
|
40
|
+
installed skill's scripts directory. The CLI instruction supplies controller input
|
|
41
|
+
JSON; save it as charter.json for the standalone command. It contains id,
|
|
42
|
+
requirement, repository, acceptance_criteria, completion_contract (version 2), and
|
|
43
|
+
thread_state.original_request. For standalone use the controller freezes the same
|
|
44
|
+
fields independently of implementation. The matrix otherwise follows
|
|
45
|
+
[acceptance evidence](acceptance-evidence.md).
|
|
46
|
+
|
|
47
|
+
To derive the frozen specification/context, import `minimumCharter(charter)` and
|
|
48
|
+
`minimumContext(charter, fullHeadSha)` from the minimum script; import `sha256`
|
|
49
|
+
and `contextDigest` from check-acceptance.mjs. The charter digest is
|
|
50
|
+
`sha256(Buffer.from(JSON.stringify(minimumCharter(charter))))`.
|
|
51
|
+
The context digest is `contextDigest(minimumContext(charter, fullHeadSha))`.
|
|
52
|
+
The CLI derives both itself; it never takes expected digests from executor output.
|
|
53
|
+
|
|
54
|
+
The current CLI context is one local checkout, its full HEAD, controller charter
|
|
55
|
+
ID and request digest. A clean checkout is required and checked again after
|
|
56
|
+
collection. This is not automatic multi-repository deployment identity discovery.
|
|
57
|
+
For companion services, verify the combined path and record the pinned service
|
|
58
|
+
identities in the evidence; the independent reviewer must validate their current
|
|
59
|
+
identity. Do not claim that this single-checkout gate automatically re-reads a
|
|
60
|
+
companion service's Git HEAD or production environment.
|
|
61
|
+
|
|
62
|
+
Every matrix row adds `implementation_refs: ["src/example.js"]`. Each required
|
|
63
|
+
user/system cell's **first** evidence file is JSON with this shape (user example):
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"criterion_id": "AC-1",
|
|
68
|
+
"context_sha256": "<current context digest>",
|
|
69
|
+
"checks": {
|
|
70
|
+
"operation": {"subject": "Save clicked and request observed for fixture run", "expected": true, "actual": true},
|
|
71
|
+
"result": {"subject": "Note after reload and session return", "expected": "requested text", "actual": "requested text"}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The system file uses `postcondition` and `integration` instead. Each has `subject`,
|
|
77
|
+
non-null `expected` and actual observed `actual`. Values may be objects/arrays to
|
|
78
|
+
compare saved values and ownership together. The checker compares values exactly;
|
|
79
|
+
missing fields or mismatches fail regardless of a cell's passed flag. Additional
|
|
80
|
+
cell evidence may contain screenshots or logs; all artifact hashes are validated.
|
|
81
|
+
Keep raw evidence local and use synthetic data, never credentials/personal data.
|
|
82
|
+
|
|
83
|
+
Store matrix and evidence under ui_evidence_root, or under repository `.artifacts`
|
|
84
|
+
when no root was specified. All artifact paths are relative to the matrix directory.
|
|
85
|
+
Return `delivery_evidence_path` in SIMY_RESULT_JSON, absolute under that root or
|
|
86
|
+
relative to it. Source mappings must resolve to files inside the current repository.
|
|
87
|
+
Evidence must be bounded regular files; path escapes/symlinks are rejected.
|
|
88
|
+
|
|
89
|
+
Standalone invocation, with controller-owned trusted digests:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
node <installed-skill>/scripts/check-minimum-completion.mjs charter.json matrix.json context.json <trusted-charter-sha256> <trusted-context-sha256>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Exit 0 is evidence_complete; 1 is evidence_incomplete; 2 means invalid/unavailable
|
|
96
|
+
inputs. JSON includes a readable checklist and floor failures. Check the top-level
|
|
97
|
+
status, not only individual base-matrix rows. The standalone checker validates
|
|
98
|
+
mapping structure; only the CLI additionally resolves mappings in its checkout.
|
|
99
|
+
Neither file hashes nor a passed report authenticate the collector or prove an
|
|
100
|
+
oracle matches the original requirement. Independent coverage/oracle review is
|
|
101
|
+
required by the CLI and must inspect the raw evidence. The standalone command
|
|
102
|
+
cannot stop Codex/Claude's final chat response and does not install global hooks.
|
|
103
|
+
|
|
104
|
+
## State, review and recovery
|
|
105
|
+
|
|
106
|
+
The CLI's implementation gate includes the minimum report before independent
|
|
107
|
+
review. The review returns coverage_review with requirements_complete,
|
|
108
|
+
unmapped_requirements and a concrete observation. The controller binds that
|
|
109
|
+
review to the matrix digest, then recollects evidence. Changing the matrix after
|
|
110
|
+
review requires a new review; changing HEAD or dirtying the tree invalidates the
|
|
111
|
+
proof. A concurrent call using the same run ID is rejected, including cloned snapshots.
|
|
112
|
+
A repeated start rechecks evidence without executing the implementation again;
|
|
113
|
+
explicit resume remains the repair path. Cross-process
|
|
114
|
+
execution remains subject to existing runner/host ownership controls.
|
|
115
|
+
|
|
116
|
+
PR recheck clears prior completion flags and reruns the floor. Missing evidence
|
|
117
|
+
never promotes an old successful audit. The old history is retained; do not rewrite
|
|
118
|
+
it as verified under the new floor. Fix the named criterion, collect fresh evidence
|
|
119
|
+
and resume within the retry budget. Reconcile ambiguous external side effects
|
|
120
|
+
before retrying. The minimum pass is a prerequisite for existing CI/review/authority
|
|
121
|
+
gates, not merge, deployment or a complete production delivery.
|
|
122
|
+
|
|
123
|
+
Rollback: restoring the older CLI removes this additional enforcement. Retain the
|
|
124
|
+
versioned evidence/history and report that limitation; never relabel old evidence
|
|
125
|
+
as verified by this floor. Monitor minimum-completion findings and subsequent
|
|
126
|
+
reviewer-discovered gaps through existing local run records; no new telemetry or
|
|
127
|
+
external provider calls are introduced.
|
|
128
|
+
|
|
129
|
+
## Shared specifications and independent review
|
|
130
|
+
|
|
131
|
+
The floor's AC-N rows trace back to stable requirement IDs in the team's shared
|
|
132
|
+
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
|
|
135
|
+
validated by the minimum checker. The CLI gate verifies minimum structured evidence;
|
|
136
|
+
it does not launch every design review or SQM phase on its own. Standalone hosts
|
|
137
|
+
remain responsible for executing this skill and cannot claim an enforced host hook.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Shared system specifications and delivery review
|
|
2
|
+
|
|
3
|
+
Read before planning a Delivery Loop change. The specification belongs to the
|
|
4
|
+
system and its team, not to a single run. Use existing team paths and conventions;
|
|
5
|
+
if none exist, start `docs/systems/<system-id>/` with an index and the applicable
|
|
6
|
+
views below. Repository paths here are a convention, not a demand to move existing
|
|
7
|
+
documents or to create a new service. Keep one canonical source for each concept.
|
|
8
|
+
|
|
9
|
+
## Shared baseline and proposed change
|
|
10
|
+
|
|
11
|
+
The system index records its owner/maintainers, repository/document links, stable
|
|
12
|
+
system ID, As-Is source revision(s) and environment, verified date, and gaps.
|
|
13
|
+
Distinguish source-code behavior, locally observed behavior, and deployed behavior.
|
|
14
|
+
An old design or green PR is not proof of current production behavior.
|
|
15
|
+
|
|
16
|
+
At the start of each run:
|
|
17
|
+
|
|
18
|
+
1. Read the existing index and current linked views. Reconcile relevant As-Is with
|
|
19
|
+
code and actual behavior; label unknowns, cite evidence, and correct documented
|
|
20
|
+
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
|
|
25
|
+
system index. Record its baseline revision, affected stable IDs, before/after
|
|
26
|
+
behavior, dependencies and acceptance oracles. Reuse unchanged views by reference;
|
|
27
|
+
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,
|
|
29
|
+
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.
|
|
34
|
+
|
|
35
|
+
The twelve-view index must account for every category: present (with a canonical
|
|
36
|
+
link), unchanged (with a revision), not applicable (with a reason), or unknown
|
|
37
|
+
(with an owner/next action). Only create/update a diagram where it describes a
|
|
38
|
+
real system concept. For CLI/skill systems, chat/terminal interactions can be the
|
|
39
|
+
interface views; do not invent application screens, APIs, tables or infrastructure.
|
|
40
|
+
|
|
41
|
+
| View | Primary content | Consistency checks against other views |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| art-01-screen-inventory-final | Screens or user interaction surfaces and stable IDs | Every referenced screen/control in workflows, transitions and wireframes exists |
|
|
44
|
+
| art-02-screen-transitions | Navigation, entry/exit, failure/recovery | Reachable inventory entries, correct permission guards and state conditions |
|
|
45
|
+
| art-03-wireframes | Controls, content, empty/loading/error/success states | Each action has a requirement, transition and downstream sequence; no dead control |
|
|
46
|
+
| art-04-functional-requirements | Purpose, beneficiary, outcomes, invariants, acceptance | Every request maps to implementation and evidence through the other affected views |
|
|
47
|
+
| art-05-state-machines | Legal states, guards, terminal outcomes, retry/cancel | Sequences/events trigger legal transitions; UI messages match actual final state |
|
|
48
|
+
| art-06-permissions | Actor/role, action, resource, authority boundary | UI guards, API checks, ownership and execution roles agree |
|
|
49
|
+
| art-07-user-workflows | User goal, steps, completion and recovery experience | Requirements covered end to end through screens, APIs, persistence and runtime |
|
|
50
|
+
| art-08-data-dictionary | Fields, meaning, type, requiredness, lifecycle and owner | API/event payloads, ER cardinality and state invariants use the same definitions |
|
|
51
|
+
| art-09-sequences | Calls/events through components, errors, asynchronous completion | API names/payloads, states, authorization and deployed component boundaries agree |
|
|
52
|
+
| art-10-api-design | Existing APIs, commands or file contracts, errors and compatibility | Sequence inputs/outputs, data meanings and permission checks agree |
|
|
53
|
+
| art-11-er-diagram | Entities, relationships, ownership and cardinality | Dictionary and API reads/writes agree; label logical artifact models as non-DB |
|
|
54
|
+
| art-12-infrastructure | Repositories, execution environments, services and trust boundaries | Every sequence participant has an actual host/owner; revision/rollout dependencies align |
|
|
55
|
+
|
|
56
|
+
## Review evidence and neutral agents
|
|
57
|
+
|
|
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.
|
|
65
|
+
|
|
66
|
+
Persist a compact review receipt: reviewer/session identity, review kind, original
|
|
67
|
+
request and shared baseline/To-Be references, candidate revisions, inspected views
|
|
68
|
+
and evidence, findings, result, and re-review links. An independent reviewer checks:
|
|
69
|
+
|
|
70
|
+
- coverage of the original request, user benefit and acceptance oracles;
|
|
71
|
+
- consistency for every affected cross-view link, including unchanged neighbors;
|
|
72
|
+
- behavior after actual operation, reload/session return and final persistence or
|
|
73
|
+
integration outcome where applicable;
|
|
74
|
+
- implementation and tests against the proposed To-Be, not just against each other;
|
|
75
|
+
- missing/extra functionality, stale evidence and unresolved historical regressions.
|
|
76
|
+
|
|
77
|
+
A diagram syntax/link checker helps, but cannot replace semantic cross-view review.
|
|
78
|
+
Do not declare neutral review merely by renaming the implementer's role.
|
|
79
|
+
|
|
80
|
+
## SQM before and after PR creation
|
|
81
|
+
|
|
82
|
+
Use the installed SQM loop skill. Respect explicit SQM opt-in, existing session and
|
|
83
|
+
service authority. Explain unavailable gates; do not install global hooks, weaken
|
|
84
|
+
rules, force an upload or switch organizations to obtain a pass.
|
|
85
|
+
|
|
86
|
+
For each phase use fresh, separate local paths outside the diff (for example an
|
|
87
|
+
ignored `.artifacts/delivery/<run>/sqm/pre-pr/` or `post-pr/` directory):
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
simy sqm min-check --base <actual-pr-base> --output <phase>/min.json
|
|
91
|
+
simy sqm check --base <actual-pr-base> --min-result <phase>/min.json --proof <phase>/proof.json --signed-evidence <phase>/proof.json.signed.json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use supported no-upload options when required by the authorized sharing scope.
|
|
95
|
+
Read the proof and verify its repository, head, base, working-tree/diff identity,
|
|
96
|
+
knowledge bundle version/digest and `outcome.status == passed`; verify the bound
|
|
97
|
+
signature using SQM tooling. Exit zero alone is insufficient. Do not edit or commit
|
|
98
|
+
between min-check and check. Pin the commit before the pre-PR check; writing proof
|
|
99
|
+
into the committed diff would invalidate its identity.
|
|
100
|
+
|
|
101
|
+
Before the pre-PR phase, inventory relevant known incidents from the team's shared
|
|
102
|
+
incident records, SQM knowledge and repository regression history. Record incident
|
|
103
|
+
or source ID, failure boundary, applicable/unaffected/unknown classification,
|
|
104
|
+
regression command and result, SQM rule match/coverage, and residual gaps. Link this
|
|
105
|
+
ledger from the shared specification so the next task can reuse and improve it.
|
|
106
|
+
Separate confirmed incidents from synthetic risk scenarios; never invent history.
|
|
107
|
+
A zero-match/no-applicable-rules proof does not establish historical coverage.
|
|
108
|
+
Use concrete regressions for relevant uncovered incidents and have an independent
|
|
109
|
+
reviewer assess coverage; keep missing incident history or untested boundaries
|
|
110
|
+
visible. No finite test promises absence of all future bugs.
|
|
111
|
+
|
|
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.
|
|
123
|
+
Promote Ready only with current required gates, including applicable combined-state
|
|
124
|
+
verification. Merge and deployment still require their own authority and evidence.
|
|
125
|
+
|
|
126
|
+
## Team maintenance after verification
|
|
127
|
+
|
|
128
|
+
Merge reviewed specification changes with the corresponding implementation through
|
|
129
|
+
the normal team review process. Record proposed, implemented locally, merged and
|
|
130
|
+
verified-in-environment status separately. Advance As-Is only for the revision and
|
|
131
|
+
environment actually verified; a merged To-Be is not automatically production As-Is.
|
|
132
|
+
Preserve the prior baseline and decisions in version history. If another change
|
|
133
|
+
updates a shared view, reconcile its semantics and re-review affected links even
|
|
134
|
+
when Git reports no textual conflict. Do not overwrite concurrent edits.
|
|
135
|
+
|
|
136
|
+
Each run stores pointers, deltas and evidence in its receipt, not another competing
|
|
137
|
+
master specification. Record unresolved drift with an owner, source and next action.
|
|
138
|
+
Never lower the intended outcome merely to make an implementation match a diagram.
|
package/src/codex-plugins/simy/skills/agentic-delivery-loop/references/state-and-receipts.md
CHANGED
|
@@ -9,11 +9,18 @@ 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
|
+
- acceptance-source identities and digests, architecture boundaries, applicable
|
|
13
|
+
rule decisions, risk tier, requested/effective delivery levels, the safety
|
|
14
|
+
ceiling reason, and chat-confirmation receipt;
|
|
12
15
|
- repository, base SHA, head SHA, companion SHAs, merge SHA, image digests, and
|
|
13
16
|
deployed revision;
|
|
14
17
|
- target environments, user journey, user/tenant fixture identities, and
|
|
15
18
|
required artifacts;
|
|
16
19
|
- applicable gates and their current status;
|
|
20
|
+
- frozen acceptance charter digest, current matrix/table paths and digests,
|
|
21
|
+
superseded matrix snapshots, and the acceptance validator result for each gate;
|
|
22
|
+
- verification-boundary changes, deferred-debt references, and applicable
|
|
23
|
+
outcome and guardrail measures;
|
|
17
24
|
- attempt number, retry/time/cost budgets, and failure fingerprints;
|
|
18
25
|
- append-only receipt IDs, evidence references, timestamps, and cleanup state.
|
|
19
26
|
|
|
@@ -29,6 +36,7 @@ Use the smallest applicable receipts rather than free-form completion claims:
|
|
|
29
36
|
- `FailureReproductionReceipt`
|
|
30
37
|
- `ImplementationReceipt`
|
|
31
38
|
- `LocalVerificationReceipt`
|
|
39
|
+
- `AcceptanceEvidenceReceipt` (matrix completeness, not overall completion)
|
|
32
40
|
- `IndependentReviewReceipt`
|
|
33
41
|
- `PRCIReceipt`
|
|
34
42
|
- `IntegrationVerificationReceipt`
|
|
@@ -91,6 +99,7 @@ Evidence is revision-bound:
|
|
|
91
99
|
| Change | Invalidate |
|
|
92
100
|
|---|---|
|
|
93
101
|
| Charter or explicit success conditions | All affected downstream gates |
|
|
102
|
+
| Acceptance source, test oracle, assertion, or CI/security gate changes | The affected gate and all dependent downstream evidence |
|
|
94
103
|
| Head SHA or generated source changes | Local, CI, integration, container, and live evidence |
|
|
95
104
|
| Base SHA or companion revision changes | Integration and later evidence |
|
|
96
105
|
| Container image digest changes | Container user journey and later runtime evidence |
|
|
@@ -125,6 +134,8 @@ For the full delivery mode:
|
|
|
125
134
|
```text
|
|
126
135
|
done =
|
|
127
136
|
reproduction_verified
|
|
137
|
+
&& acceptance_evidence_complete
|
|
138
|
+
&& minimum_functional_evidence_verified
|
|
128
139
|
&& locally_verified
|
|
129
140
|
&& pr_ci_green
|
|
130
141
|
&& integration_verified
|
|
@@ -140,3 +151,10 @@ done =
|
|
|
140
151
|
Remove only genuinely non-applicable terms and record why. If merge or deploy
|
|
141
152
|
is outside current authority, stop at the highest proven pre-authority state.
|
|
142
153
|
|
|
154
|
+
Acceptance evidence must cover every applicable criterion for the target gate,
|
|
155
|
+
with current context and the required user/system layers. After each evaluated
|
|
156
|
+
action, refresh the JSON matrix and its readable table; append a receipt for the
|
|
157
|
+
update. Keep the frozen charter distinct from mutable results. A charter patch
|
|
158
|
+
supersedes the old digest only after the controller verifies its authority.
|
|
159
|
+
|
|
160
|
+
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.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Offline record validation only. The controller owns acceptance and provenance.
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { constants } from "node:fs";
|
|
6
|
+
import { lstat, open, readFile, realpath } from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const layers = ["user", "system"];
|
|
11
|
+
const hashPattern = /^[a-f0-9]{64}$/;
|
|
12
|
+
const text = (value) => typeof value === "string" && value.trim().length > 0;
|
|
13
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
14
|
+
export const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
15
|
+
export const contextDigest = (context) => sha256(JSON.stringify(canonical(context)));
|
|
16
|
+
|
|
17
|
+
function canonical(value) {
|
|
18
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
19
|
+
if (!object(value)) return value;
|
|
20
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function requireValue(condition, message) {
|
|
24
|
+
if (!condition) throw new Error(message);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function validateCharter(charter) {
|
|
28
|
+
requireValue(object(charter) && charter.schema_version === 1 && text(charter.run_id), "Invalid charter header");
|
|
29
|
+
requireValue(Array.isArray(charter.criteria) && charter.criteria.length > 0, "Charter requires criteria");
|
|
30
|
+
const ids = new Set();
|
|
31
|
+
for (const criterion of charter.criteria) {
|
|
32
|
+
requireValue(object(criterion) && text(criterion.id) && !ids.has(criterion.id), "Duplicate or invalid criterion ID");
|
|
33
|
+
ids.add(criterion.id);
|
|
34
|
+
requireValue(text(criterion.outcome) && text(criterion.acceptance_source), "Criterion needs outcome and independent acceptance source");
|
|
35
|
+
requireValue(object(criterion.layers), "Criterion needs verification layers");
|
|
36
|
+
let required = 0;
|
|
37
|
+
for (const layer of layers) {
|
|
38
|
+
const spec = criterion.layers[layer];
|
|
39
|
+
requireValue(object(spec), `Missing ${layer} layer specification`);
|
|
40
|
+
if (spec.applicability === "required") {
|
|
41
|
+
requireValue(text(spec.method), "Required layer needs a verification method");
|
|
42
|
+
required += 1;
|
|
43
|
+
} else {
|
|
44
|
+
requireValue(spec.applicability === "not_applicable" && text(spec.reason) && text(spec.source), "Non-applicability needs a reason and source in the charter");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
requireValue(required > 0, "A criterion cannot waive both verification layers");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateContext(context, runId) {
|
|
52
|
+
requireValue(object(context) && context.run_id === runId, "Context run ID mismatch");
|
|
53
|
+
for (const field of ["stage", "environment", "fixture_id"]) {
|
|
54
|
+
requireValue(text(context[field]), `Context needs ${field}`);
|
|
55
|
+
}
|
|
56
|
+
requireValue(hashPattern.test(context.input_sha256), "Context needs input SHA-256");
|
|
57
|
+
requireValue(object(context.source_revisions) && Object.keys(context.source_revisions).length > 0, "Context needs repository revisions");
|
|
58
|
+
for (const [repository, revision] of Object.entries(context.source_revisions)) {
|
|
59
|
+
requireValue(text(repository) && typeof revision === "string" && /^[a-f0-9]{40}$/.test(revision), "Invalid repository revision");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function readEvidenceArtifact(root, artifact) {
|
|
64
|
+
requireValue(object(artifact) && text(artifact.path) && hashPattern.test(artifact.sha256), "Invalid artifact reference");
|
|
65
|
+
// Explicit bundle-relative paths only; never fetch URLs or execute evidence.
|
|
66
|
+
const segments = artifact.path.split("/");
|
|
67
|
+
requireValue(!path.isAbsolute(artifact.path) && !artifact.path.includes("\\") &&
|
|
68
|
+
segments.every((part) => part && part !== "." && part !== ".." && !part.includes(":")), "Unsafe artifact path");
|
|
69
|
+
let target = root;
|
|
70
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
71
|
+
target = path.join(target, segments[index]);
|
|
72
|
+
const stat = await lstat(target);
|
|
73
|
+
requireValue(!stat.isSymbolicLink(), "Symbolic evidence paths are not allowed");
|
|
74
|
+
if (index < segments.length - 1) requireValue(stat.isDirectory(), "Invalid evidence directory");
|
|
75
|
+
}
|
|
76
|
+
const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
77
|
+
try {
|
|
78
|
+
const stat = await handle.stat();
|
|
79
|
+
requireValue(stat.isFile() && stat.size > 0 && stat.size <= 16 * 1024 * 1024, "Artifact must be a nonempty regular file of at most 16 MiB");
|
|
80
|
+
const bytes = await handle.readFile();
|
|
81
|
+
requireValue(sha256(bytes) === artifact.sha256, "Artifact digest mismatch");
|
|
82
|
+
return bytes;
|
|
83
|
+
} finally {
|
|
84
|
+
await handle.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const tableText = (value) => String(value ?? "").replaceAll("|", "\\|").replace(/[\r\n]+/g, " ");
|
|
89
|
+
|
|
90
|
+
export function renderChecklist(report) {
|
|
91
|
+
const header = [
|
|
92
|
+
"| Criterion / outcome | Layer / method | Status | Observation | Evidence | Unmet reason / next action |",
|
|
93
|
+
"| --- | --- | --- | --- | --- | --- |",
|
|
94
|
+
];
|
|
95
|
+
const rows = report.checks.map((row) => "| " + [
|
|
96
|
+
`${row.id}: ${row.outcome}`, `${row.layer}: ${row.method}`, row.status,
|
|
97
|
+
row.observation, row.evidence.join(", "), [row.reason, row.next_action].filter(Boolean).join(" / "),
|
|
98
|
+
].map(tableText).join(" | ") + " |");
|
|
99
|
+
return [...header, ...rows, "", `Validator: ${report.status}`,
|
|
100
|
+
...report.errors.map((error) => `- ${tableText(error)}`)].join("\n") + "\n";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function checkAcceptance({ charterBytes, matrix, expectedCharterSha256, expectedContextSha256, evidenceRoot }) {
|
|
104
|
+
requireValue(hashPattern.test(expectedCharterSha256) && hashPattern.test(expectedContextSha256), "Trusted charter and context SHA-256 values are required");
|
|
105
|
+
requireValue(sha256(charterBytes) === expectedCharterSha256, "Frozen charter digest mismatch");
|
|
106
|
+
const charter = JSON.parse(charterBytes.toString());
|
|
107
|
+
validateCharter(charter);
|
|
108
|
+
requireValue(object(matrix) && matrix.schema_version === 1, "Invalid matrix schema version");
|
|
109
|
+
requireValue(matrix.charter_sha256 === expectedCharterSha256, "Matrix charter digest mismatch");
|
|
110
|
+
validateContext(matrix.context, charter.run_id);
|
|
111
|
+
requireValue(contextDigest(matrix.context) === expectedContextSha256, "Current context digest mismatch");
|
|
112
|
+
requireValue(Array.isArray(matrix.results), "Matrix requires results");
|
|
113
|
+
const root = await realpath(evidenceRoot);
|
|
114
|
+
const report = { status: "evidence_incomplete", checks: [], errors: [] };
|
|
115
|
+
const results = new Map();
|
|
116
|
+
const expectedIds = new Set(charter.criteria.map((criterion) => criterion.id));
|
|
117
|
+
for (const row of matrix.results) {
|
|
118
|
+
requireValue(object(row) && expectedIds.has(row.id) && !results.has(row.id), "Duplicate or unknown result criterion");
|
|
119
|
+
requireValue(object(row.layers) && Object.keys(row.layers).every((key) => layers.includes(key)), "Invalid result layers");
|
|
120
|
+
results.set(row.id, row);
|
|
121
|
+
}
|
|
122
|
+
for (const criterion of charter.criteria) {
|
|
123
|
+
for (const layer of layers) {
|
|
124
|
+
const spec = criterion.layers[layer];
|
|
125
|
+
const cell = results.get(criterion.id)?.layers[layer];
|
|
126
|
+
const row = { id: criterion.id, outcome: criterion.outcome, layer, method: spec.method ?? spec.reason,
|
|
127
|
+
status: "pending", observation: "", evidence: [], reason: "", next_action: "" };
|
|
128
|
+
report.checks.push(row);
|
|
129
|
+
if (spec.applicability === "not_applicable") {
|
|
130
|
+
row.status = "not_applicable";
|
|
131
|
+
row.reason = `${spec.reason} (${spec.source})`;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (!object(cell)) {
|
|
135
|
+
row.reason = "Missing result";
|
|
136
|
+
row.next_action = "Run the specified check and record evidence";
|
|
137
|
+
} else {
|
|
138
|
+
row.observation = text(cell.observation) ? cell.observation : "";
|
|
139
|
+
row.next_action = text(cell.next_action) ? cell.next_action : "";
|
|
140
|
+
row.evidence = Array.isArray(cell.evidence) ? cell.evidence.map((entry) => object(entry) && text(entry.path) ? entry.path : "invalid reference") : [];
|
|
141
|
+
if (!["passed", "failed", "pending"].includes(cell.status)) {
|
|
142
|
+
row.reason = "Invalid status (results cannot waive a required layer)";
|
|
143
|
+
} else if (cell.context_sha256 !== expectedContextSha256 || cell.charter_sha256 !== expectedCharterSha256) {
|
|
144
|
+
row.reason = "Stale result; reverify for the current charter and context";
|
|
145
|
+
} else if (cell.status !== "passed") {
|
|
146
|
+
row.status = cell.status;
|
|
147
|
+
row.reason = text(cell.reason) ? cell.reason : "Missing unmet reason";
|
|
148
|
+
if (!row.next_action) row.reason += "; missing next action";
|
|
149
|
+
} else {
|
|
150
|
+
try {
|
|
151
|
+
requireValue(text(cell.action) && text(cell.observation), "Passed result needs action and observation");
|
|
152
|
+
requireValue(text(cell.observed_at) && /^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d{2}:\d{2})$/.test(cell.observed_at) &&
|
|
153
|
+
Number.isFinite(Date.parse(cell.observed_at)) && Date.parse(cell.observed_at) <= Date.now(), "Invalid or future observation timestamp");
|
|
154
|
+
requireValue(Array.isArray(cell.evidence) && cell.evidence.length > 0, "Passed result needs evidence");
|
|
155
|
+
for (const artifact of cell.evidence) await readEvidenceArtifact(root, artifact);
|
|
156
|
+
row.status = "passed";
|
|
157
|
+
} catch {
|
|
158
|
+
row.reason = "Invalid, missing, empty, unsafe, or changed evidence; check action, observation, timestamp and artifact hashes";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (row.status !== "passed") report.errors.push(`${criterion.id}/${layer}: ${row.reason}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!report.errors.length) report.status = "evidence_complete";
|
|
166
|
+
report.markdown = renderChecklist(report);
|
|
167
|
+
return report;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function main(args) {
|
|
171
|
+
requireValue(args.length === 4 || (args.length === 5 && args[4] === "--markdown"),
|
|
172
|
+
"Usage: node check-acceptance.mjs <charter.json> <matrix.json> <trusted-charter-sha256> <current-context-sha256> [--markdown]");
|
|
173
|
+
const [charterPath, matrixPath, expectedCharterSha256, expectedContextSha256] = args;
|
|
174
|
+
const load = async (target) => {
|
|
175
|
+
const stat = await lstat(target);
|
|
176
|
+
requireValue(stat.isFile() && stat.size <= 1024 * 1024, "Input must be a regular JSON file of at most 1 MiB");
|
|
177
|
+
return readFile(target);
|
|
178
|
+
};
|
|
179
|
+
const report = await checkAcceptance({ charterBytes: await load(charterPath),
|
|
180
|
+
matrix: JSON.parse((await load(matrixPath)).toString()), expectedCharterSha256,
|
|
181
|
+
expectedContextSha256, evidenceRoot: path.dirname(path.resolve(matrixPath)) });
|
|
182
|
+
process.stdout.write(args[4] === "--markdown" ? report.markdown : JSON.stringify(report, null, 2) + "\n");
|
|
183
|
+
process.exitCode = report.status === "evidence_complete" ? 0 : 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const entryPath = process.argv[1] ? await realpath(process.argv[1]).catch(() => null) : null;
|
|
187
|
+
if (entryPath === fileURLToPath(import.meta.url)) {
|
|
188
|
+
main(process.argv.slice(2)).catch(() => {
|
|
189
|
+
// Do not echo arbitrary JSON, filesystem contents, or sensitive input values.
|
|
190
|
+
process.stderr.write("Acceptance input invalid. Check JSON schema, paths, frozen charter and current context digests. Usage: node check-acceptance.mjs <charter.json> <matrix.json> <trusted-charter-sha256> <current-context-sha256> [--markdown]\n");
|
|
191
|
+
process.exitCode = 2;
|
|
192
|
+
});
|
|
193
|
+
}
|