@abelxiaoxing/cadence 1.1.0 → 1.1.1
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 +11 -1
- package/package.json +1 -1
- package/prompts/abel-design.md +69 -13
- package/prompts/abel-diagnose.md +2 -0
- package/prompts/abel-implement.md +19 -8
- package/skills/abel-workflow/SKILL.md +37 -3
- package/src/contracts.ts +18 -2
- package/src/control-contracts.ts +2 -15
- package/src/delivery-compiler.ts +47 -17
- package/src/design-control.ts +846 -0
- package/src/design-journal.ts +1569 -0
- package/src/index.ts +580 -197
- package/src/run-state.ts +39 -2
- package/src/run-store.ts +81 -9
- package/src/subagent-activity.ts +1 -1
- package/src/verification-capability.ts +43 -15
- package/src/workflow-engine.ts +428 -113
package/README.md
CHANGED
|
@@ -21,11 +21,21 @@ Implement 只暴露 `start`、`status`、`resume`、`rebind`、`cancel` 和 `dis
|
|
|
21
21
|
|
|
22
22
|
普通 artifact、错误 Red、transport、environment、stale、conflict、baseline 与验证失败都留在同一 Implement run 中恢复。
|
|
23
23
|
累计验证发现 introduced failure 时会自动重开责任任务做有界修复;自动预算耗尽后 `resume` 复用已有 baseline 和 phase facts。
|
|
24
|
-
|
|
24
|
+
Design 证据包必须绑定 durable `runId`;接受后的有界证据、决策版本、Gate 证明与 canonical plan 身份写入 owner-private journal。
|
|
25
|
+
Design 激活期间,父模型只保留进入前已启用的 `read`、`grep`、`find`、`ls` 与 `abel_dispatch`;原工具集合会在 finalize、finish、切换阶段或 session 结束时精确恢复。
|
|
26
|
+
OpenSpec change 制品只能通过私有 `write-artifact` / `delete-artifact` 原子操作变更;产品文件、AGENTS、`gate-a.yaml`、`ready.yaml` 和 `implement-plan.json` 对该通道不可达。
|
|
27
|
+
Gate A/B 收据使用 schema v4,并在 Implement admission 时同时对照同一 root/change 的私有批准事实与 finalization revision/hash 事实验证。
|
|
28
|
+
|
|
29
|
+
只有继续工作确实需要新增行为、架构/策略、依赖、路径、资源、验证或 AGENTS 权限时才进入 `approval-needed`。
|
|
30
|
+
结果保留原 Implement run,明确给出 authority category、所需 Gate、引用和 `/abel-design --change <change>` 用户指引;不会自动调用另一阶段。
|
|
31
|
+
`resume` 只作为带有更高 `deliveryRevision` 与匹配 `receiptHash` 的条件命令出现。
|
|
32
|
+
用户显式完成 Design 后,新的 Implement 上下文会通过本地 `status` 自动发现 proof-bound 的精确 revision/hash,再对同一 run 做完整 admission 与 resume;不依赖复制上一次会话内容。
|
|
25
33
|
|
|
26
34
|
交互式 TUI、print、JSON 和 RPC 都以 durable semantic state 为准。
|
|
27
35
|
queued、connecting、waiting-first-response、running、validating、retrying、verifying、paused、approval-needed、applying 和 recovering 都不是完成;operation-cancelled 表示本次操作取消但 run 仍可恢复,discarded 与 rejected 是非成功终态。
|
|
28
36
|
只有最终 apply 与 post-apply verification 提交后的 `completed` 才显示成功。
|
|
37
|
+
Design finalization、Implement 终态、Diagnose/显式 `finish` 或 session shutdown 会清除 active stage 与 parent bridge;Design 还会恢复进入前的精确父工具集合,其他阶段只撤下 `abel_dispatch`。
|
|
38
|
+
Gate 等待和可恢复暂停保持激活以接收直接后续操作。
|
|
29
39
|
|
|
30
40
|
私有 journal、artifact 与 change workspace 位于 consumer repository 之外的 owner-private state root。
|
|
31
41
|
暂停会保留最小结构化恢复事实;完成或显式 discard 后安全清理。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abelxiaoxing/cadence",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Four explicit Abel workflow prompts with stage-isolated design and diagnosis, durable resumable implementation, and package-owned professional Agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/prompts/abel-design.md
CHANGED
|
@@ -25,29 +25,46 @@ An explicit `--change <name>` resumes only that existing change; never reinterpr
|
|
|
25
25
|
|
|
26
26
|
## Write boundary
|
|
27
27
|
|
|
28
|
+
- During verified Design, the parent tool set is mechanically limited to the previously active `read`, `grep`, `find`, and `ls` tools plus `abel_dispatch`.
|
|
29
|
+
Shell, edit, write, and unknown tools are unavailable until Design exits; never try to recover them or write through another tool.
|
|
28
30
|
- Before explicit Gate A approval, all repository work is read-only.
|
|
29
31
|
A provisional Design run may exist only in the repository-external private run store.
|
|
30
|
-
- After Gate A,
|
|
32
|
+
- After Gate A, mutate only the canonical `openspec/changes/<change>/` root through the private `write-artifact` or `delete-artifact` Design operations.
|
|
33
|
+
Use a path relative to the active change root; it may be only `.openspec.yaml`, `proposal.md`, `design.md`, `tasks.md`, `plan-draft.json`, or `specs/<safe-segments>/spec.md`.
|
|
34
|
+
Content is exact bounded UTF-8 text; send it directly, never as Base64.
|
|
35
|
+
- Code-owned `gate-a.yaml` and `ready.yaml` receipts are forbidden targets.
|
|
36
|
+
Code-owned `implement-plan.json` is also unreachable through artifact mutation; only `compile-plan` may install it.
|
|
31
37
|
- Every repository `AGENTS.md` remains read-only throughout Design.
|
|
32
38
|
Audit it and seal any needed `none | update-existing | create-index | remove-index` operation into the implementation plan; never edit the index here.
|
|
33
39
|
- Never edit OpenSpec schemas, `openspec/config.yaml`, generated OpenSpec skills/commands, product code, tests, package manifests, or repository state outside the change root.
|
|
34
40
|
|
|
41
|
+
Write one artifact per operation and use a unique operation id.
|
|
42
|
+
Delete only an obsolete allowed artifact; no directory is ever removed.
|
|
43
|
+
Identical replay is idempotent and conflicting reuse of an operation id fails closed.
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{"action":"design","request":{"operation":"write-artifact","runId":"<run-id>","operationId":"<unique-operation>","path":"proposal.md","content":"<exact-utf8-content>"}}
|
|
47
|
+
{"action":"design","request":{"operation":"delete-artifact","runId":"<run-id>","operationId":"<unique-operation>","path":"specs/<capability>/spec.md"}}
|
|
48
|
+
```
|
|
49
|
+
|
|
35
50
|
## Durable Design identity
|
|
36
51
|
|
|
37
52
|
Use `start` once to create or recover the Design run identity and `status` to inspect it.
|
|
38
53
|
A new requirement may begin with a code-generated SHA-256 `provisionalKey`; after Gate A fixes the change name, a named `start` binds the same provisional run.
|
|
39
54
|
These commands record lifecycle only and do not execute implementation work.
|
|
55
|
+
A completed Design run is immutable; an explicit later `--change <change>` starts the next durable Design revision for that change while preserving prior approval facts for receipt verification.
|
|
40
56
|
|
|
41
57
|
```json
|
|
42
|
-
{"
|
|
43
|
-
{"
|
|
44
|
-
{"
|
|
58
|
+
{"command":"start","stage":"abel-design","provisionalKey":"<sha256>","operationId":"<unique-operation>"}
|
|
59
|
+
{"command":"start","stage":"abel-design","change":"<change>","provisionalKey":"<same-sha256>","operationId":"<unique-operation>"}
|
|
60
|
+
{"command":"status","stage":"abel-design","change":"<change>"}
|
|
45
61
|
```
|
|
46
62
|
|
|
47
63
|
## Read-only evidence packets
|
|
48
64
|
|
|
49
65
|
Use `abel_dispatch` with `action: "run"` only for bounded Design evidence.
|
|
50
66
|
Each packet is one tool call, has `stage: "abel-design"`, role `design-explorer`, phase `evidence`, an exact read scope, an empty write set, and output `evidence`.
|
|
67
|
+
It must include the exact `runId` returned by Design `start`.
|
|
51
68
|
Never send an implementation phase to a Design packet.
|
|
52
69
|
|
|
53
70
|
```json
|
|
@@ -56,6 +73,7 @@ Never send an implementation phase to a Design packet.
|
|
|
56
73
|
"request":{
|
|
57
74
|
"stage":"abel-design",
|
|
58
75
|
"role":"design-explorer",
|
|
76
|
+
"runId":"<durable-design-run-id>",
|
|
59
77
|
"id":"<unique-packet-id>",
|
|
60
78
|
"phase":"evidence",
|
|
61
79
|
"objective":"<bounded evidence question>",
|
|
@@ -69,6 +87,8 @@ Never send an implementation phase to a Design packet.
|
|
|
69
87
|
|
|
70
88
|
When two or more independent packets are needed, issue all sibling calls in the same assistant turn.
|
|
71
89
|
Do not wrap them in a `requests` array.
|
|
90
|
+
The control plane validates the run before launch and records only a bounded normalized evidence fact after structural acceptance.
|
|
91
|
+
An identical packet/result replay is idempotent; a conflicting result for one packet id fails closed.
|
|
72
92
|
Validate packet id, scope, citations, and structure before synthesis; uncited, conflicting, or out-of-scope claims require a bounded follow-up or a minimal parent check.
|
|
73
93
|
|
|
74
94
|
Evidence packets may read code and tests but may not execute validation.
|
|
@@ -76,7 +96,14 @@ Design may inspect manifests and verification capability statically; it must not
|
|
|
76
96
|
|
|
77
97
|
## Decision ledger and Gate A
|
|
78
98
|
|
|
79
|
-
Maintain
|
|
99
|
+
Maintain the conversational decision presentation with id, `behavior | technical`, question, cited evidence, alternatives, recommendation, user decision, status, and affected artifacts.
|
|
100
|
+
After the user resolves a substantive decision, record only its normalized identity, category, canonical contract hash, and artifact references through the durable private journal:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{"action":"design","request":{"operation":"record-decision","runId":"<run-id>","operationId":"<unique-operation>","decisionId":"<stable-id>","category":"behavior","contractHash":"<sha256>","refs":["<artifact-anchor>"]}}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Do not put prompts, transcripts, hidden reasoning, credentials, environment values, or raw model output in this request.
|
|
80
107
|
|
|
81
108
|
Ask the user only for substantive choices: observable behavior, scope/non-goals, data/security/privacy/compatibility/migration policy, new dependencies, architecture/policy, irreversible changes, and technical choices with real trade-offs.
|
|
82
109
|
Resolve reversible details mechanically from one established repository convention and do not ask them repeatedly.
|
|
@@ -86,9 +113,16 @@ Before approval, present the unresolved behavior decisions together.
|
|
|
86
113
|
After explicit approval:
|
|
87
114
|
|
|
88
115
|
1. resolve the final kebab-case change name and schema;
|
|
89
|
-
2. create the change only if it does not already exist;
|
|
116
|
+
2. create the change only if it does not already exist and bind the named Design run;
|
|
90
117
|
3. materialize schema-ready behavior artifacts one at a time;
|
|
91
|
-
4.
|
|
118
|
+
4. compute the canonical behavior-contract hash and record approval with:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{"action":"design","request":{"operation":"approve-gate","runId":"<run-id>","operationId":"<unique-operation>","gate":"gate-a","contractHash":"<behavior-contract-sha256>"}}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The returned proof contains the approval revision, contract hash, and owner-private record hash.
|
|
125
|
+
`gate-a.yaml` is installed later by code-owned finalization, not hand-authored here.
|
|
92
126
|
|
|
93
127
|
Gate A is not tool permission and carries no session/model/timestamp identity.
|
|
94
128
|
|
|
@@ -114,8 +148,15 @@ The plan admits only shell-free `vitest`, `package-script`, `static-check`, or o
|
|
|
114
148
|
Pin local runners, package scripts, arguments, `minTests`, classifications, and `noInstall` behavior.
|
|
115
149
|
Reject shell operators, implicit downloads, absolute/escaping paths, missing local capability, and unsupported verification shapes during Design readiness.
|
|
116
150
|
|
|
117
|
-
|
|
118
|
-
|
|
151
|
+
After current Gate A approval, send the complete draft through `write-artifact` only to the relative path `plan-draft.json`, then invoke code-owned compilation:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{"action":"design","request":{"operation":"compile-plan","runId":"<run-id>","operationId":"<unique-operation>"}}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The compiler reads only that fixed safe path, validates capability and graph closure, atomically installs canonical `implement-plan.json`, and returns its raw and canonical hashes.
|
|
158
|
+
Do not hand-assemble canonical plan bytes, hashes, generated task Markdown, or receipts.
|
|
159
|
+
The canonical `ImplementPlan` contains:
|
|
119
160
|
|
|
120
161
|
- tasks and regular-file outputs;
|
|
121
162
|
- target/affected/full-suite baselines and normalized failure identity policy;
|
|
@@ -134,12 +175,24 @@ Every absent future input must be a unique declared output from a transitive dep
|
|
|
134
175
|
Gate B approves the complete HOW: substantive technical choices, task DAG, exact boundaries, verification/repair contracts, output postconditions, dependency changes, scheduling declarations, tracking, and AGENTS operations.
|
|
135
176
|
Do not request approval while any capability, closure, traceability, or blocking decision is unresolved.
|
|
136
177
|
|
|
137
|
-
After explicit approval,
|
|
178
|
+
After explicit approval, materialize any remaining schema artifacts through `write-artifact` one at a time and complete the code-owned sequence below.
|
|
179
|
+
First record Gate B against the exact canonical plan hash returned by `compile-plan`:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{"action":"design","request":{"operation":"approve-gate","runId":"<run-id>","operationId":"<unique-operation>","gate":"gate-b","contractHash":"<exact-plan-canonical-sha256>"}}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Then request finalization:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{"action":"design","request":{"operation":"finalize-delivery","runId":"<run-id>","operationId":"<unique-operation>"}}
|
|
189
|
+
```
|
|
138
190
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
191
|
+
Finalization revalidates both current private Gate proofs, the stored plan, strict/planning-complete OpenSpec state, artifact coverage, exact traceability, and executable verification closure.
|
|
192
|
+
It atomically installs canonical `gate-a.yaml`, writes `ready.yaml` last, rereads the installed delivery, removes `plan-draft.json`, marks the Design run completed, and returns `deliveryRevision` plus `receiptHash`.
|
|
193
|
+
Any failure leaves Design nonterminal and installs no new `ready.yaml`.
|
|
142
194
|
|
|
195
|
+
Receipt schema version 4 binds the Gate A and Gate B approval revisions, canonical contract hashes, and owner-private record hashes.
|
|
143
196
|
`ready.yaml` references the plan path, schema version, raw-byte hash, canonical plan hash, executable verification closure, Gate A hash, artifact hashes, and traceability hash.
|
|
144
197
|
It does not embed caller-owned runtime task state, completed/blocked arrays, snapshots, launch identities, or retry budgets.
|
|
145
198
|
|
|
@@ -149,6 +202,7 @@ Every other byte remains bound.
|
|
|
149
202
|
## Recovery without approval loops
|
|
150
203
|
|
|
151
204
|
On resume, validate current artifacts and receipts first.
|
|
205
|
+
Use Design `status` to recover bounded accepted-evidence identities/hashes, latest decision revisions, Gate currentness, and compiled-plan identity without relying on a prior child session.
|
|
152
206
|
Preserve decisions whose bound content did not change.
|
|
153
207
|
A mechanical hash, formatting, checkbox, stale path, or traceability repair regenerates only invalidated artifacts and does not reopen a Gate.
|
|
154
208
|
Reopen Gate A only for changed behavior authority; reopen Gate B only for changed technical authority.
|
|
@@ -158,6 +212,8 @@ Endpoint/transport failure in an evidence packet pauses that packet and retains
|
|
|
158
212
|
Missing package scripts/runners or a non-executable verification contract must be corrected before Gate B; they must not be deferred as an Implement surprise.
|
|
159
213
|
|
|
160
214
|
Report `READY_TO_IMPLEMENT` only when strict validation passes, both Gates have zero unresolved decisions, all artifacts/hashes/traceability resolve, and the canonical plan is executable.
|
|
215
|
+
Successful finalization deactivates private dispatch before an unrelated later request.
|
|
216
|
+
Gate waits remain active for direct follow-up; if the user explicitly ends an unfinished Design interaction, send `{"action":"finish"}`.
|
|
161
217
|
Otherwise report the current Design state, retained evidence, and the one unresolved decision or artifact that prevents readiness.
|
|
162
218
|
|
|
163
219
|
Do not implement product code and do not archive, publish, release, stage, or commit implicitly.
|
package/prompts/abel-diagnose.md
CHANGED
|
@@ -51,4 +51,6 @@ Do not silently widen Diagnose and do not transform an ordinary repair failure i
|
|
|
51
51
|
When an approved browser E2E check requires `dev-browser` and it is unavailable, pause only that check and provide executable remediation; continue independent non-browser evidence when safe.
|
|
52
52
|
|
|
53
53
|
Finish only when reproduction, falsification, failing regression, minimum repair, affected verification, and baseline comparison are all evidenced.
|
|
54
|
+
After presenting the final structural diagnosis/repair result, send `{"action":"finish"}` so private dispatch is deactivated.
|
|
55
|
+
A resumable evidence or capability pause remains active for direct follow-up and must not send finish.
|
|
54
56
|
Never archive, publish, or commit implicitly.
|
|
@@ -37,15 +37,15 @@ A fresh context validates existing approval facts and does not ask the user to a
|
|
|
37
37
|
|
|
38
38
|
## Closed control surface
|
|
39
39
|
|
|
40
|
-
Use `abel_dispatch` only with the
|
|
40
|
+
Use `abel_dispatch` only with the change commands below; never send `version`; never send `action: "run"`; and never send `admit-graph`, `task-attempt`, caller-owned snapshots, completed/blocked arrays, or apply identities.
|
|
41
41
|
|
|
42
42
|
```json
|
|
43
|
-
{"
|
|
44
|
-
{"
|
|
45
|
-
{"
|
|
46
|
-
{"
|
|
47
|
-
{"
|
|
48
|
-
{"
|
|
43
|
+
{"command":"start","stage":"abel-implement","change":"<change>","operationId":"<unique-operation>"}
|
|
44
|
+
{"command":"status","stage":"abel-implement","change":"<change>"}
|
|
45
|
+
{"command":"resume","stage":"abel-implement","change":"<change>","operationId":"<unique-operation>"}
|
|
46
|
+
{"command":"rebind","stage":"abel-implement","change":"<change>","operationId":"<unique-operation>","routeId":"<approved-route>"}
|
|
47
|
+
{"command":"cancel","stage":"abel-implement","change":"<change>","operationId":"<unique-operation>"}
|
|
48
|
+
{"command":"discard","stage":"abel-implement","change":"<change>","operationId":"<unique-operation>"}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
When the user approves a revised delivery boundary, resume the same run with both `deliveryRevision` and `receiptHash`.
|
|
@@ -55,6 +55,11 @@ The engine revalidates retained phase/repair facts, invalidates only incompatibl
|
|
|
55
55
|
Repeating the same operation id returns its committed outcome.
|
|
56
56
|
Use a new operation id for a new attempt.
|
|
57
57
|
|
|
58
|
+
For an approval-needed retained run, a later explicit Implement invocation must inspect local `status`.
|
|
59
|
+
When a newer owner-private-proof-bound receipt is present, status exposes its exact pair in `availableDelivery`, adds `resume` to `legalCommands`, and fills the matching `conditionalCommands` arguments.
|
|
60
|
+
Resume with that discovered pair; do not require the user to copy revision or hash data from a prior Design conversation.
|
|
61
|
+
Discovery never admits the delivery: `resume` still performs the full artifact, traceability, capability, and currentness validation before any Worker starts.
|
|
62
|
+
|
|
58
63
|
## Execution and verification
|
|
59
64
|
|
|
60
65
|
Before any candidate, the engine records target-contract, task-affected, and full-suite baselines with normalized failure identities.
|
|
@@ -98,9 +103,14 @@ No item above carries stage-routing metadata or asks the user to restart the app
|
|
|
98
103
|
|
|
99
104
|
Use `approval-needed` only when continuing requires authority not present in the sealed delivery: new observable behavior, architecture or policy; a new/changed dependency; an undeclared write/delete/read target; expanded conflict/resource authority; a changed verification contract; an AGENTS target/impact/managed block change; or another irreversible scope decision.
|
|
100
105
|
|
|
106
|
+
Classify only codes in the closed authority table.
|
|
107
|
+
An unknown Worker, verifier, or internal approval code is an `approval-code-invalid` integrity pause; never guess a category or Gate from keywords.
|
|
108
|
+
|
|
101
109
|
Report the exact missing authority and retained run state.
|
|
102
110
|
Do not discard compatible work.
|
|
103
|
-
|
|
111
|
+
Expose `approval.category`, `approval.requiredGates`, safe `approval.refs`, and `approval.designRequest` as `/abel-design --change <change>` user guidance only; never invoke Design automatically.
|
|
112
|
+
Bare `resume` is not immediately executable progress while approval is missing, so keep it out of `legalCommands` and expose it only in `conditionalCommands` with a `deliveryRevision` greater than the retained revision and the matching `receiptHash` precondition.
|
|
113
|
+
If the user supplies that newly approved receipt revision, resume the same run with the revision/hash pair.
|
|
104
114
|
A generated artifact defect, endpoint outage, stale snapshot, environment failure, pre-existing failure, introduced in-boundary repair, approved documentation/test edit, or approved AGENTS checkpoint is never `approval-needed`.
|
|
105
115
|
|
|
106
116
|
## AGENTS and tracking
|
|
@@ -118,6 +128,7 @@ Treat `queued`, `connecting`, `waiting-first-response`, `running`, `validating`,
|
|
|
118
128
|
`operation-cancelled` ends only the current operation; `discarded` and `rejected` are non-success terminal run states.
|
|
119
129
|
Tool-call settlement is not success.
|
|
120
130
|
Report success only when the durable state is `completed`, final application and post-apply verification committed, and private run content was cleaned.
|
|
131
|
+
Terminal `completed`, `discarded`, or `rejected` settlement deactivates private dispatch; a nonterminal pause or approval wait keeps it active for direct follow-up.
|
|
121
132
|
|
|
122
133
|
On a nonterminal result, report the safe code, affected task/scope when present, retained progress, and the exact legal command that continues or inspects the same run.
|
|
123
134
|
Never hide an incomplete run behind a success checkmark.
|
|
@@ -21,7 +21,8 @@ Neither Gate grants tool permission, repository permission, identity, or authori
|
|
|
21
21
|
Design and Implement may run in fresh contexts.
|
|
22
22
|
Trusted delivery requires:
|
|
23
23
|
|
|
24
|
-
- canonical Gate A and Gate B
|
|
24
|
+
- canonical receipt-schema-v4 Gate A and Gate B proofs for the same root/change/schema, each binding its approval revision, canonical contract hash, and owner-private journal record hash;
|
|
25
|
+
- an owner-private finalization fact binding the exact delivery revision, ready-receipt raw hash, both Gate proofs, and canonical plan hash;
|
|
25
26
|
- safe relative artifact paths and exact raw SHA-256 bindings;
|
|
26
27
|
- a canonical `implement-plan.json` with schema version, raw hash, and canonical hash;
|
|
27
28
|
- OpenSpec strict validity and planning completeness;
|
|
@@ -40,16 +41,40 @@ A new receipt revision is needed only when approved authority changes.
|
|
|
40
41
|
It uses no Subagent dispatch, preserves dirty/human content and nested repositories, and is idempotent.
|
|
41
42
|
- **Design** is evidence and delivery compilation only.
|
|
42
43
|
Product code, tests, implementation candidates, and AGENTS files are read-only.
|
|
44
|
+
The parent sees only its previously active `read`, `grep`, `find`, and `ls` tools plus `abel_dispatch` while Design is active.
|
|
43
45
|
Design may use bounded read-only `design-explorer` packets.
|
|
44
46
|
- **Implement** uses only the durable change command surface.
|
|
45
47
|
It executes the sealed plan in private immutable revisions and applies the complete change once.
|
|
46
48
|
- **Diagnose** independently follows reproduce → falsify → failing regression → minimum repair.
|
|
47
49
|
It does not inherit Implement state, retry policy, or approval classification.
|
|
48
50
|
|
|
49
|
-
Design and Diagnose bounded packets use `action: "run"` with their own stage.
|
|
51
|
+
Design and Diagnose bounded packets use `action: "run"` with their own stage; every Design packet also binds the durable Design `runId` that requested it.
|
|
50
52
|
Implement never uses that packet protocol.
|
|
51
53
|
Cross-stage calls fail closed.
|
|
52
54
|
|
|
55
|
+
## Durable Design control
|
|
56
|
+
|
|
57
|
+
Design uses one closed private `action: "design"` family with `record-decision`, `approve-gate`, `write-artifact`, `delete-artifact`, `compile-plan`, and `finalize-delivery`.
|
|
58
|
+
The owner-private SQLite journal retains only normalized evidence identities/hashes, versioned decision hashes and refs, Gate proof facts, canonical compiled-plan identity/bytes, finalization identity, and idempotent operation outcomes.
|
|
59
|
+
It never retains prompts, transcripts, hidden reasoning, credentials, environment values, or raw model output.
|
|
60
|
+
|
|
61
|
+
Accepted evidence is recorded only after structural validation.
|
|
62
|
+
Same packet/result replay is idempotent; conflicting evidence for one packet id fails closed.
|
|
63
|
+
A later behavior decision invalidates both Gates; a later technical decision or plan compilation invalidates Gate B only.
|
|
64
|
+
|
|
65
|
+
The extension snapshots the exact pre-Design non-dispatch tool set; during Design it retains only the previously active `read`, `grep`, `find`, and `ls` tools plus `abel_dispatch`.
|
|
66
|
+
It removes every write-capable or unknown tool and restores the snapshot on finalization, explicit finish, verified stage switch, session start, or shutdown.
|
|
67
|
+
Private artifact mutation accepts bounded UTF-8 only at `.openspec.yaml`, `proposal.md`, `design.md`, `tasks.md`, `plan-draft.json`, and `specs/<safe-segments>/spec.md` relative to the active change root.
|
|
68
|
+
It creates missing directories component-wise without following symlinks, writes atomically, deletes only an allowed regular file, journals only bounded path/hash outcomes, and replays idempotently.
|
|
69
|
+
Code-owned `gate-a.yaml` and `ready.yaml` receipts remain forbidden; `implement-plan.json` remains unreachable except through compilation.
|
|
70
|
+
|
|
71
|
+
All filesystem-mutating Design operations serialize against the durable expiring finalization lease.
|
|
72
|
+
A competing or stale owner cannot inspect, replace, remove, or commit another operation's receipt bytes, and cleanup is fenced by the current operation's raw receipt hash.
|
|
73
|
+
After current Gate A, the parent sends the fixed `plan-draft.json` input only through `write-artifact`.
|
|
74
|
+
`compile-plan` owns canonical plan construction and installation.
|
|
75
|
+
Gate B binds that exact stored canonical plan hash.
|
|
76
|
+
`finalize-delivery` validates current proofs, OpenSpec, artifacts, traceability, and executable closure, writes Gate A and ready receipts atomically with `ready.yaml` last, rereads them, removes the draft, and completes Design.
|
|
77
|
+
|
|
53
78
|
## Canonical ImplementPlan
|
|
54
79
|
|
|
55
80
|
`ImplementPlan` is the only machine source for implementation mechanics.
|
|
@@ -80,7 +105,8 @@ Arbitrary shell, `&&`, path escape, and implicit downloads are forbidden.
|
|
|
80
105
|
|
|
81
106
|
## Durable control
|
|
82
107
|
|
|
83
|
-
Implement accepts only `start`, `status`, `resume`, `rebind`, `cancel`, and `discard` with
|
|
108
|
+
Implement accepts only `start`, `status`, `resume`, `rebind`, `cancel`, and `discard` with stage, change, and operation identity where required.
|
|
109
|
+
The internal-development command surface has no caller-supplied protocol version.
|
|
84
110
|
|
|
85
111
|
- `start` is idempotent for one change run.
|
|
86
112
|
- `status` is local and requires no Worker endpoint.
|
|
@@ -135,6 +161,12 @@ Use `approval-needed` only when continuing requires new authority: observable be
|
|
|
135
161
|
Report the exact missing authority.
|
|
136
162
|
After the user approves a new canonical delivery revision, resume the same run and preserve compatible work.
|
|
137
163
|
|
|
164
|
+
An approval-needed status exposes the closed authority category, required Gate set, safe refs, retained run/revision, and `/abel-design --change <change>` as user guidance only.
|
|
165
|
+
It never invokes Design automatically.
|
|
166
|
+
Bare `resume` or `rebind` is not an immediately legal approval continuation; `conditionalCommands` exposes resume only with a newer `deliveryRevision` and matching `receiptHash`.
|
|
167
|
+
Authority classification uses one exhaustive typed code map; an unknown approval code becomes an `approval-code-invalid` integrity pause rather than a guessed Gate.
|
|
168
|
+
A later explicit Implement status may locally discover a newer receipt only when its change, revision/hash, both current Gate proofs, and owner-private finalization fact match; it exposes the exact pair without admitting it or mutating the run.
|
|
169
|
+
|
|
138
170
|
Do not label an approved AGENTS checkpoint, documentation/test edit, endpoint outage, artifact error, stale snapshot, environment problem, baseline failure, or in-boundary repair as approval-needed.
|
|
139
171
|
|
|
140
172
|
## AGENTS indexes
|
|
@@ -163,4 +195,6 @@ Its absence pauses that verification only and does not block unrelated tasks or
|
|
|
163
195
|
`operation-cancelled` ends only the current operation; `discarded` and `rejected` are non-success terminal run states.
|
|
164
196
|
Tool settlement is not completion.
|
|
165
197
|
Only durable `completed` after cumulative apply and postconditions receives success.
|
|
198
|
+
Successful Design finalization, terminal Implement settlement, Diagnose `finish`, explicit stage finish, and session shutdown deactivate only `abel_dispatch`, drain packets, clear the parent bridge, and clear active stage identity.
|
|
199
|
+
Gate waits and resumable pauses remain active for direct follow-up.
|
|
166
200
|
Never archive, publish, release, stage, or commit implicitly.
|
package/src/contracts.ts
CHANGED
|
@@ -10,7 +10,6 @@ export type {
|
|
|
10
10
|
export {
|
|
11
11
|
assertControlCommand,
|
|
12
12
|
CONTROL_COMMANDS,
|
|
13
|
-
CONTROL_PROTOCOL_VERSION,
|
|
14
13
|
CONTROL_STAGES,
|
|
15
14
|
controlRunKey,
|
|
16
15
|
validateControlCommand,
|
|
@@ -146,8 +145,10 @@ export const APPROVAL_BOUNDARY_CODES = [
|
|
|
146
145
|
"behavior-contract-insufficient",
|
|
147
146
|
"architecture-contract-insufficient",
|
|
148
147
|
"task-scope-insufficient",
|
|
148
|
+
"conflict-resource-authority-insufficient",
|
|
149
149
|
"verification-contract-insufficient",
|
|
150
150
|
"agents-contract-insufficient",
|
|
151
|
+
"irreversible-scope-insufficient",
|
|
151
152
|
] as const;
|
|
152
153
|
export const VERIFICATION_ADAPTER_CODES = [
|
|
153
154
|
"input-missing",
|
|
@@ -439,6 +440,7 @@ export type ImplementGraphReadinessDiagnostic =
|
|
|
439
440
|
export interface PacketEnvelope {
|
|
440
441
|
stage: "abel-design" | "abel-diagnose";
|
|
441
442
|
role: "design-explorer" | "diagnosis-worker";
|
|
443
|
+
runId?: string;
|
|
442
444
|
id: string;
|
|
443
445
|
phase: string;
|
|
444
446
|
objective: string;
|
|
@@ -1230,7 +1232,7 @@ export function validatePacketEnvelope(
|
|
|
1230
1232
|
return { ok: false, reason: "missing request envelope" };
|
|
1231
1233
|
}
|
|
1232
1234
|
const packet = value as Record<string, unknown>;
|
|
1233
|
-
const
|
|
1235
|
+
const baseRequired = [
|
|
1234
1236
|
"stage",
|
|
1235
1237
|
"role",
|
|
1236
1238
|
"id",
|
|
@@ -1241,6 +1243,14 @@ export function validatePacketEnvelope(
|
|
|
1241
1243
|
"declared",
|
|
1242
1244
|
"output",
|
|
1243
1245
|
] as const;
|
|
1246
|
+
const required =
|
|
1247
|
+
packet.stage === "abel-design"
|
|
1248
|
+
? ([
|
|
1249
|
+
...baseRequired.slice(0, 2),
|
|
1250
|
+
"runId",
|
|
1251
|
+
...baseRequired.slice(2),
|
|
1252
|
+
] as const)
|
|
1253
|
+
: baseRequired;
|
|
1244
1254
|
for (const field of required) {
|
|
1245
1255
|
if (packet[field] === undefined) {
|
|
1246
1256
|
return { ok: false, reason: `missing required field: ${field}` };
|
|
@@ -1262,6 +1272,12 @@ export function validatePacketEnvelope(
|
|
|
1262
1272
|
) {
|
|
1263
1273
|
return { ok: false, reason: "packet role does not match stage" };
|
|
1264
1274
|
}
|
|
1275
|
+
if (
|
|
1276
|
+
(packet.stage === "abel-design" && !validIdentifier(packet.runId)) ||
|
|
1277
|
+
(packet.stage === "abel-diagnose" && packet.runId !== undefined)
|
|
1278
|
+
) {
|
|
1279
|
+
return { ok: false, reason: "invalid Design run identity" };
|
|
1280
|
+
}
|
|
1265
1281
|
if (
|
|
1266
1282
|
typeof packet.id !== "string" ||
|
|
1267
1283
|
packet.id.length === 0 ||
|
package/src/control-contracts.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
// Mechanical graph, snapshot, task, and apply identities are intentionally not
|
|
3
3
|
// part of this surface: the control plane derives them from approved delivery.
|
|
4
4
|
|
|
5
|
-
export const CONTROL_PROTOCOL_VERSION = 2 as const;
|
|
6
|
-
|
|
7
5
|
export const CONTROL_COMMANDS = [
|
|
8
6
|
"start",
|
|
9
7
|
"status",
|
|
@@ -23,7 +21,6 @@ const CHANGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/u;
|
|
|
23
21
|
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
24
22
|
|
|
25
23
|
interface ControlBase {
|
|
26
|
-
version: typeof CONTROL_PROTOCOL_VERSION;
|
|
27
24
|
command: ControlCommandName;
|
|
28
25
|
stage: ControlStage;
|
|
29
26
|
}
|
|
@@ -86,10 +83,7 @@ export type ControlCommand =
|
|
|
86
83
|
|
|
87
84
|
export type ControlCommandValidation =
|
|
88
85
|
| { ok: true; value: ControlCommand }
|
|
89
|
-
| {
|
|
90
|
-
ok: false;
|
|
91
|
-
code: "invalid-control-command" | "unsupported-control-version";
|
|
92
|
-
};
|
|
86
|
+
| { ok: false; code: "invalid-control-command" };
|
|
93
87
|
|
|
94
88
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
95
89
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -110,7 +104,6 @@ function hasExactKeys(
|
|
|
110
104
|
|
|
111
105
|
function validCommon(value: Record<string, unknown>): boolean {
|
|
112
106
|
return (
|
|
113
|
-
value.version === CONTROL_PROTOCOL_VERSION &&
|
|
114
107
|
(CONTROL_COMMANDS as readonly unknown[]).includes(value.command) &&
|
|
115
108
|
(CONTROL_STAGES as readonly unknown[]).includes(value.stage)
|
|
116
109
|
);
|
|
@@ -128,17 +121,11 @@ export function validateControlCommand(
|
|
|
128
121
|
value: unknown,
|
|
129
122
|
): ControlCommandValidation {
|
|
130
123
|
if (!isRecord(value)) return { ok: false, code: "invalid-control-command" };
|
|
131
|
-
if (
|
|
132
|
-
Object.hasOwn(value, "version") &&
|
|
133
|
-
value.version !== CONTROL_PROTOCOL_VERSION
|
|
134
|
-
) {
|
|
135
|
-
return { ok: false, code: "unsupported-control-version" };
|
|
136
|
-
}
|
|
137
124
|
if (!validCommon(value)) {
|
|
138
125
|
return { ok: false, code: "invalid-control-command" };
|
|
139
126
|
}
|
|
140
127
|
|
|
141
|
-
const common = ["
|
|
128
|
+
const common = ["command", "stage"] as const;
|
|
142
129
|
const named = [...common, "change"] as const;
|
|
143
130
|
switch (value.command) {
|
|
144
131
|
case "status":
|
package/src/delivery-compiler.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { validateVerificationAdapterCapability } from "./verification-capability
|
|
|
21
21
|
|
|
22
22
|
export const IMPLEMENT_PLAN_SCHEMA_VERSION = 3 as const;
|
|
23
23
|
export const IMPLEMENT_PLAN_PATH = "implement-plan.json" as const;
|
|
24
|
-
export const DELIVERY_RECEIPT_VERSION =
|
|
24
|
+
export const DELIVERY_RECEIPT_VERSION = 4 as const;
|
|
25
25
|
export const GATE_A_RECEIPT_PATH = "gate-a.yaml" as const;
|
|
26
26
|
export const READY_RECEIPT_PATH = "ready.yaml" as const;
|
|
27
27
|
|
|
@@ -35,14 +35,17 @@ export interface DeliveryArtifactBinding {
|
|
|
35
35
|
rawSha256: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export interface GateApprovalProof {
|
|
39
|
+
revision: number;
|
|
40
|
+
contractHash: string;
|
|
41
|
+
recordHash: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
export interface GateAReceipt {
|
|
39
45
|
receiptVersion: typeof DELIVERY_RECEIPT_VERSION;
|
|
40
46
|
change: string;
|
|
41
47
|
schema: string;
|
|
42
|
-
approval:
|
|
43
|
-
approved: true;
|
|
44
|
-
blockingDecisions: 0;
|
|
45
|
-
};
|
|
48
|
+
approval: GateApprovalProof;
|
|
46
49
|
artifacts: DeliveryArtifactBinding[];
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -65,8 +68,9 @@ export interface ReadyReceipt {
|
|
|
65
68
|
rawSha256: string;
|
|
66
69
|
};
|
|
67
70
|
gateB: {
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
revision: number;
|
|
72
|
+
contractHash: string;
|
|
73
|
+
recordHash: string;
|
|
70
74
|
};
|
|
71
75
|
};
|
|
72
76
|
artifacts: DeliveryArtifactBinding[];
|
|
@@ -320,6 +324,22 @@ function requireReceiptIdentity(
|
|
|
320
324
|
}
|
|
321
325
|
}
|
|
322
326
|
|
|
327
|
+
function normalizeGateProof(value: GateApprovalProof): GateApprovalProof {
|
|
328
|
+
if (
|
|
329
|
+
!isRecord(value) ||
|
|
330
|
+
!exactKeys(value, ["revision", "contractHash", "recordHash"]) ||
|
|
331
|
+
!Number.isSafeInteger(value.revision) ||
|
|
332
|
+
value.revision < 1 ||
|
|
333
|
+
typeof value.contractHash !== "string" ||
|
|
334
|
+
!SHA256.test(value.contractHash) ||
|
|
335
|
+
typeof value.recordHash !== "string" ||
|
|
336
|
+
!SHA256.test(value.recordHash)
|
|
337
|
+
) {
|
|
338
|
+
throw new Error("delivery-gate-proof-invalid");
|
|
339
|
+
}
|
|
340
|
+
return structuredClone(value);
|
|
341
|
+
}
|
|
342
|
+
|
|
323
343
|
function parseCanonicalReceipt<T>(bytes: Uint8Array, code: string): T {
|
|
324
344
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
325
345
|
let parsed: unknown;
|
|
@@ -335,6 +355,7 @@ function parseCanonicalReceipt<T>(bytes: Uint8Array, code: string): T {
|
|
|
335
355
|
export function compileGateAReceipt(input: {
|
|
336
356
|
change: string;
|
|
337
357
|
schema: string;
|
|
358
|
+
approval: GateApprovalProof;
|
|
338
359
|
artifacts: readonly DeliveryArtifactBinding[];
|
|
339
360
|
}): { receipt: GateAReceipt; bytes: Uint8Array; rawSha256: string } {
|
|
340
361
|
requireReceiptIdentity(input.change, input.schema);
|
|
@@ -342,7 +363,7 @@ export function compileGateAReceipt(input: {
|
|
|
342
363
|
receiptVersion: DELIVERY_RECEIPT_VERSION,
|
|
343
364
|
change: input.change,
|
|
344
365
|
schema: input.schema,
|
|
345
|
-
approval:
|
|
366
|
+
approval: normalizeGateProof(input.approval),
|
|
346
367
|
artifacts: normalizeArtifactBindings(input.artifacts),
|
|
347
368
|
};
|
|
348
369
|
const bytes = canonicalBytes(receipt);
|
|
@@ -369,15 +390,17 @@ export function parseGateAReceipt(bytes: Uint8Array): GateAReceipt {
|
|
|
369
390
|
]) ||
|
|
370
391
|
receipt.receiptVersion !== DELIVERY_RECEIPT_VERSION ||
|
|
371
392
|
!isRecord(receipt.approval) ||
|
|
372
|
-
!exactKeys(receipt.approval, ["
|
|
373
|
-
receipt.approval.approved !== true ||
|
|
374
|
-
receipt.approval.blockingDecisions !== 0
|
|
393
|
+
!exactKeys(receipt.approval, ["revision", "contractHash", "recordHash"])
|
|
375
394
|
) {
|
|
376
395
|
throw new Error("gate-a-receipt-invalid");
|
|
377
396
|
}
|
|
378
397
|
requireReceiptIdentity(receipt.change, receipt.schema);
|
|
398
|
+
const approval = normalizeGateProof(receipt.approval);
|
|
379
399
|
const artifacts = normalizeArtifactBindings(receipt.artifacts);
|
|
380
|
-
if (
|
|
400
|
+
if (
|
|
401
|
+
canonicalJson(approval) !== canonicalJson(receipt.approval) ||
|
|
402
|
+
canonicalJson(artifacts) !== canonicalJson(receipt.artifacts)
|
|
403
|
+
) {
|
|
381
404
|
throw new Error("gate-a-receipt-invalid");
|
|
382
405
|
}
|
|
383
406
|
return structuredClone(receipt);
|
|
@@ -415,6 +438,7 @@ export function compileReadyReceipt(input: {
|
|
|
415
438
|
schema: string;
|
|
416
439
|
deliveryRevision: number;
|
|
417
440
|
gateA: { rawSha256: string };
|
|
441
|
+
gateB: GateApprovalProof;
|
|
418
442
|
artifacts: readonly DeliveryArtifactBinding[];
|
|
419
443
|
compiledPlan: CompiledDelivery;
|
|
420
444
|
traceability: DeliveryTraceability;
|
|
@@ -424,7 +448,8 @@ export function compileReadyReceipt(input: {
|
|
|
424
448
|
!Number.isSafeInteger(input.deliveryRevision) ||
|
|
425
449
|
input.deliveryRevision < 1 ||
|
|
426
450
|
!SHA256.test(input.gateA.rawSha256) ||
|
|
427
|
-
input.compiledPlan.plan.changeId !== input.change
|
|
451
|
+
input.compiledPlan.plan.changeId !== input.change ||
|
|
452
|
+
input.gateB.contractHash !== input.compiledPlan.planHash
|
|
428
453
|
) {
|
|
429
454
|
throw new Error("delivery-receipt-invalid");
|
|
430
455
|
}
|
|
@@ -438,7 +463,7 @@ export function compileReadyReceipt(input: {
|
|
|
438
463
|
path: GATE_A_RECEIPT_PATH,
|
|
439
464
|
rawSha256: input.gateA.rawSha256,
|
|
440
465
|
},
|
|
441
|
-
gateB:
|
|
466
|
+
gateB: normalizeGateProof(input.gateB),
|
|
442
467
|
},
|
|
443
468
|
artifacts: normalizeArtifactBindings(input.artifacts),
|
|
444
469
|
plan: structuredClone(input.compiledPlan.receipt.plan),
|
|
@@ -485,9 +510,11 @@ export function parseReadyReceipt(bytes: Uint8Array): ReadyReceipt {
|
|
|
485
510
|
receipt.approvals.gateA.path !== GATE_A_RECEIPT_PATH ||
|
|
486
511
|
!SHA256.test(receipt.approvals.gateA.rawSha256) ||
|
|
487
512
|
!isRecord(receipt.approvals.gateB) ||
|
|
488
|
-
!exactKeys(receipt.approvals.gateB, [
|
|
489
|
-
|
|
490
|
-
|
|
513
|
+
!exactKeys(receipt.approvals.gateB, [
|
|
514
|
+
"revision",
|
|
515
|
+
"contractHash",
|
|
516
|
+
"recordHash",
|
|
517
|
+
]) ||
|
|
491
518
|
!isRecord(receipt.plan) ||
|
|
492
519
|
!exactKeys(receipt.plan, [
|
|
493
520
|
"path",
|
|
@@ -513,8 +540,11 @@ export function parseReadyReceipt(bytes: Uint8Array): ReadyReceipt {
|
|
|
513
540
|
}
|
|
514
541
|
requireReceiptIdentity(receipt.change, receipt.schema);
|
|
515
542
|
const artifacts = normalizeArtifactBindings(receipt.artifacts);
|
|
543
|
+
const gateB = normalizeGateProof(receipt.approvals.gateB);
|
|
516
544
|
const traceability = normalizeTraceability(receipt.traceability);
|
|
517
545
|
if (
|
|
546
|
+
gateB.contractHash !== receipt.plan.canonicalHash ||
|
|
547
|
+
canonicalJson(gateB) !== canonicalJson(receipt.approvals.gateB) ||
|
|
518
548
|
canonicalJson(artifacts) !== canonicalJson(receipt.artifacts) ||
|
|
519
549
|
canonicalJson(traceability) !== canonicalJson(receipt.traceability)
|
|
520
550
|
) {
|