task-pipeline-skill 1.72.0 → 1.74.0
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/CHANGELOG.md +176 -0
- package/CONTRIBUTING.md +61 -0
- package/README.md +1 -0
- package/SKILL-CARD.md +1 -1
- package/package.json +4 -3
- package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
- package/plugins/task-pipeline/agents/verifier-product.md +96 -0
- package/plugins/task-pipeline/agents/verifier-seam.md +98 -0
- package/plugins/task-pipeline/agents/verifier-unit.md +86 -0
- package/plugins/task-pipeline/agents/verifier.md +9 -0
- package/plugins/task-pipeline/skills/task-pipeline/SKILL.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/graph.schema.json +167 -72
- package/plugins/task-pipeline/skills/task-pipeline/references/acceptance.md +23 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/audit.md +15 -6
- package/plugins/task-pipeline/skills/task-pipeline/references/certification.md +146 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/gates.md +9 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/progress.md +9 -3
- package/plugins/task-pipeline/skills/task-pipeline/references/retrospective.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +6 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/work-graph.md +3 -0
- package/plugins/task-pipeline/skills/task-pipeline/scripts/graph.py +326 -6
- package/plugins/task-pipeline/skills/task-pipeline/templates/retro.md +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/templates/run.md +22 -8
- package/plugins/task-pipeline/skills/task-pipeline/templates/verification.md +38 -5
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"not": {
|
|
118
118
|
"pattern": "[\\n\\r]"
|
|
119
119
|
},
|
|
120
|
-
"description": "**How this node will be closed**
|
|
120
|
+
"description": "**How this node will be closed** — the command a verifier runs, or the named judgement that stands in where no command can (`references/gates.md`'s third gate type: name the judge, record the verdict AS judgement). Its output becomes the node's `evidence` row.\n\nRequired on every node except a `parked` one, and that is the whole point of the field: `agents/verifier.md` tells the verifier to run *the check this node names*, so a node with none leaves it the two options that same paragraph forbids — invent a check, or run everything. Shipped doctrine read a field the schema did not have (B-080); the field the doctrine reads now exists.\n\nOne string, never a list, because the requirement it serves is singular throughout — one input, one job, one output, one owner, **one** completion test. A node needing two unrelated checks is a node doing two jobs, and the answer is to split it rather than to widen this field. One gate made of two commands is `a && b`, which is still one gate.\n\nA `parked` node is exempt because it is the one node nobody will close, and a placeholder there — *n/a, parked* — is the confidence-without-correctness this schema refuses everywhere else. The field survives a park: `park` sets the status and never removes what the node said it would run."
|
|
121
121
|
},
|
|
122
122
|
"evidence": {
|
|
123
123
|
"type": [
|
|
@@ -145,87 +145,100 @@
|
|
|
145
145
|
"pattern": "\\S"
|
|
146
146
|
},
|
|
147
147
|
"description": "What this node MUTATES — paths, register names, remote resource ids. `references/planning.md` states the rule the frontier needs: *distinct is not the same as independent, and the check is what they touch, never what they are called.* That rule lived in the markdown plan, and the graph replaced the plan as the thing deciding what runs next — so `next` could hand two agents two runnable nodes that write the same file, with nothing able to report it.\\n\\nOptional, and its absence is DISCLOSED rather than treated as «touches nothing»: `next` prints how many frontier nodes declared no targets, because a quiet run and a checked one must not look alike."
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
148
|
+
},
|
|
149
|
+
"certification": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"description": "What the three-tier certification recorded for this node. Written by `graph.py certify` on every round, pass or fail: a failing round that wrote nothing would erase the only evidence that a node is churning, which is the number the loop-guard ceiling reads.",
|
|
152
|
+
"additionalProperties": false,
|
|
153
|
+
"required": [
|
|
154
|
+
"round",
|
|
155
|
+
"tiers",
|
|
156
|
+
"at",
|
|
157
|
+
"history"
|
|
158
|
+
],
|
|
159
|
+
"properties": {
|
|
160
|
+
"round": {
|
|
161
|
+
"type": "integer",
|
|
162
|
+
"minimum": 1
|
|
163
|
+
},
|
|
164
|
+
"tiers": {
|
|
165
|
+
"type": "object",
|
|
166
|
+
"additionalProperties": false,
|
|
167
|
+
"required": [
|
|
168
|
+
"unit",
|
|
169
|
+
"seam",
|
|
170
|
+
"product"
|
|
171
|
+
],
|
|
172
|
+
"properties": {
|
|
173
|
+
"unit": {
|
|
174
|
+
"enum": [
|
|
175
|
+
"pass",
|
|
176
|
+
"fail"
|
|
177
|
+
]
|
|
178
|
+
},
|
|
179
|
+
"seam": {
|
|
180
|
+
"enum": [
|
|
181
|
+
"pass",
|
|
182
|
+
"fail"
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
"product": {
|
|
186
|
+
"enum": [
|
|
187
|
+
"pass",
|
|
188
|
+
"fail"
|
|
189
|
+
]
|
|
157
190
|
}
|
|
158
191
|
}
|
|
159
192
|
},
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
|
|
163
|
-
},
|
|
164
|
-
"then": {
|
|
165
|
-
"required": [
|
|
166
|
-
"check"
|
|
167
|
-
],
|
|
168
|
-
"properties": {
|
|
169
|
-
"check": {
|
|
170
|
-
"type": "string",
|
|
171
|
-
"minLength": 1,
|
|
172
|
-
"pattern": "\\S"
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
"if": {
|
|
179
|
-
"properties": {
|
|
180
|
-
"status": {
|
|
181
|
-
"const": "done"
|
|
182
|
-
}
|
|
193
|
+
"at": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"minLength": 1
|
|
183
196
|
},
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
"history": {
|
|
198
|
+
"type": "array",
|
|
199
|
+
"minItems": 1,
|
|
200
|
+
"items": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"additionalProperties": false,
|
|
203
|
+
"required": [
|
|
204
|
+
"unit",
|
|
205
|
+
"seam",
|
|
206
|
+
"product"
|
|
207
|
+
],
|
|
208
|
+
"properties": {
|
|
209
|
+
"unit": {
|
|
210
|
+
"enum": [
|
|
211
|
+
"pass",
|
|
212
|
+
"fail"
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
"seam": {
|
|
216
|
+
"enum": [
|
|
217
|
+
"pass",
|
|
218
|
+
"fail"
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
"product": {
|
|
222
|
+
"enum": [
|
|
223
|
+
"pass",
|
|
224
|
+
"fail"
|
|
225
|
+
]
|
|
226
|
+
}
|
|
201
227
|
}
|
|
202
228
|
}
|
|
203
229
|
}
|
|
204
230
|
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"allOf": [
|
|
234
|
+
{
|
|
235
|
+
"$ref": "#/definitions/rule_check_unless_parked"
|
|
205
236
|
},
|
|
206
237
|
{
|
|
207
|
-
"
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
"required": [
|
|
214
|
-
"status"
|
|
215
|
-
]
|
|
216
|
-
},
|
|
217
|
-
"then": {
|
|
218
|
-
"required": [
|
|
219
|
-
"parked_reason"
|
|
220
|
-
],
|
|
221
|
-
"properties": {
|
|
222
|
-
"parked_reason": {
|
|
223
|
-
"type": "string",
|
|
224
|
-
"minLength": 1,
|
|
225
|
-
"pattern": "\\S"
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
238
|
+
"$ref": "#/definitions/rule_done_needs_evidence"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"$ref": "#/definitions/rule_parked_needs_reason"
|
|
229
242
|
}
|
|
230
243
|
]
|
|
231
244
|
},
|
|
@@ -283,6 +296,88 @@
|
|
|
283
296
|
"description": "The reason, written for a person reading it later. The non-whitespace pattern is required for the same reason `parked_reason` needs one: `minLength: 1` counts a space."
|
|
284
297
|
}
|
|
285
298
|
}
|
|
299
|
+
},
|
|
300
|
+
"rule_check_unless_parked": {
|
|
301
|
+
"description": "B-080's rule, factored out of `node.allOf` so it is stated once and referenced. Factoring it exposed B-079: `test/validate.py`'s `_conditionals()` recursed into `allOf` and never followed a `$ref`, so a schema that shares a rule this way read as a schema with NO conditionals and both REQ-006 and REQ-012 were reported missing from a schema that states them. The shipped schema now uses the `$ref` form, so the deref branch is exercised by every run of the gate rather than by one fixture.",
|
|
302
|
+
"if": {
|
|
303
|
+
"properties": {
|
|
304
|
+
"status": {
|
|
305
|
+
"not": {
|
|
306
|
+
"const": "parked"
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
"required": [
|
|
311
|
+
"status"
|
|
312
|
+
]
|
|
313
|
+
},
|
|
314
|
+
"then": {
|
|
315
|
+
"required": [
|
|
316
|
+
"check"
|
|
317
|
+
],
|
|
318
|
+
"properties": {
|
|
319
|
+
"check": {
|
|
320
|
+
"type": "string",
|
|
321
|
+
"minLength": 1,
|
|
322
|
+
"pattern": "\\S"
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
"rule_done_needs_evidence": {
|
|
328
|
+
"description": "REQ-006 as draft-07 states it: `done` implies at least one non-whitespace evidence string. Referenced from `node.allOf`, never inlined, so the rule has one home.",
|
|
329
|
+
"if": {
|
|
330
|
+
"properties": {
|
|
331
|
+
"status": {
|
|
332
|
+
"const": "done"
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
"required": [
|
|
336
|
+
"status"
|
|
337
|
+
]
|
|
338
|
+
},
|
|
339
|
+
"then": {
|
|
340
|
+
"required": [
|
|
341
|
+
"evidence"
|
|
342
|
+
],
|
|
343
|
+
"properties": {
|
|
344
|
+
"evidence": {
|
|
345
|
+
"type": "array",
|
|
346
|
+
"minItems": 1,
|
|
347
|
+
"items": {
|
|
348
|
+
"type": "string",
|
|
349
|
+
"minLength": 1,
|
|
350
|
+
"pattern": "\\S",
|
|
351
|
+
"description": "At least one non-whitespace character. `minLength: 1` alone accepts a single space, which is the one shape where this schema and `graph.py`'s verdict gate disagreed — the gate strips, the schema counted. A cross-check fixture comparing the two found it."
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
"rule_parked_needs_reason": {
|
|
358
|
+
"description": "REQ-012: a parked node names why it is parked. Referenced from `node.allOf`.",
|
|
359
|
+
"if": {
|
|
360
|
+
"properties": {
|
|
361
|
+
"status": {
|
|
362
|
+
"const": "parked"
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
"required": [
|
|
366
|
+
"status"
|
|
367
|
+
]
|
|
368
|
+
},
|
|
369
|
+
"then": {
|
|
370
|
+
"required": [
|
|
371
|
+
"parked_reason"
|
|
372
|
+
],
|
|
373
|
+
"properties": {
|
|
374
|
+
"parked_reason": {
|
|
375
|
+
"type": "string",
|
|
376
|
+
"minLength": 1,
|
|
377
|
+
"pattern": "\\S"
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
286
381
|
}
|
|
287
382
|
}
|
|
288
383
|
}
|
|
@@ -319,6 +319,29 @@ spends its length refusing.
|
|
|
319
319
|
|
|
320
320
|
## GATE (manual)
|
|
321
321
|
|
|
322
|
+
> **Acceptance policy `AP-1` · owner: the operator of the project running this pipeline ·
|
|
323
|
+
> in force since 2026-08-20 · supersedes: the unversioned ladder that stood before it.**
|
|
324
|
+
>
|
|
325
|
+
> This block is the policy, and naming it that closes a contradiction that stood in the
|
|
326
|
+
> shipped doctrine. [`gates.md`](gates.md) → *Axis A — the stage gate type* says which stages are
|
|
327
|
+
> manual is the operator's decision and that **the framework fixes no stage count and no
|
|
328
|
+
> gate assignment** — while the ladder below fixes twelve criteria and the statuses they
|
|
329
|
+
> may take. Both were true and neither was scoped, so a reader could take either as the
|
|
330
|
+
> rule.
|
|
331
|
+
>
|
|
332
|
+
> **The scope, stated once:** `gates.md`'s sentence governs the **pipeline's shape** — how
|
|
333
|
+
> many stages a project runs and which of them are `auto`, `judgment` or `manual`, all of
|
|
334
|
+
> it in the project's own `pipeline.json`. `AP-1` governs **what stage 10's manual gate
|
|
335
|
+
> asks when a project runs one.** A project may drop stage 10, or make it `auto` for a
|
|
336
|
+
> class of work, and `AP-1` then does not apply to it; a project that keeps it manual gets
|
|
337
|
+
> these criteria and this vocabulary, not a per-run selection from them.
|
|
338
|
+
>
|
|
339
|
+
> **Changing it is a decision, not an edit.** An amended criterion moves the version to
|
|
340
|
+
> `AP-2` and lands with a row in the project's decision register, because a policy that can
|
|
341
|
+
> be edited silently is the *«recorded in the slot reserved for what a machine
|
|
342
|
+
> established»* failure one level up: an acceptance standard nobody can cite by version is
|
|
343
|
+
> one every run re-negotiates.
|
|
344
|
+
|
|
322
345
|
All of:
|
|
323
346
|
|
|
324
347
|
1. **Every shipped REQ has a verification row, and every row names a REQ its own run
|
|
@@ -47,12 +47,21 @@ The method most audits use is **horizontal**: compare the documents against each
|
|
|
47
47
|
other, then do it again. It works, and then it fails in a way that is invisible
|
|
48
48
|
from inside it. Measured over seven passes on a production repository:
|
|
49
49
|
|
|
50
|
-
| Pass | Findings | …of which the previous pass's own fixes caused |
|
|
51
|
-
|
|
52
|
-
| 4 | 12 | 5 |
|
|
53
|
-
| 5 | 17 | 9 |
|
|
54
|
-
| 6 | 13 | 10 |
|
|
55
|
-
| 7 | 19 | 4 |
|
|
50
|
+
| Pass | Findings | …of which the previous pass's own fixes caused | Self-inflicted share |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| 4 | 12 | 5 | 42% |
|
|
53
|
+
| 5 | 17 | 9 | 53% |
|
|
54
|
+
| 6 | 13 | 10 | 77% |
|
|
55
|
+
| 7 | 19 | 4 | 21% — see the note below |
|
|
56
|
+
|
|
57
|
+
**The trend above is measured over passes four to six, and pass seven is not part of
|
|
58
|
+
it.** 42% → 53% → 77% is the decay this section teaches; the seventh pass fell back to
|
|
59
|
+
4 of 19 and **the record does not say what that pass did differently.** The row stays,
|
|
60
|
+
with the gap named, for two reasons. Deleting a measured row to protect a claim is the
|
|
61
|
+
opposite of what this file asks of every gate it describes — and the vertical pass
|
|
62
|
+
described below cannot be credited for the drop, because it is a separate pass over the
|
|
63
|
+
same repository, not the seventh. Whatever pass seven did, it is unrecorded, and an
|
|
64
|
+
unrecorded cause is what this table has to say about it.
|
|
56
65
|
|
|
57
66
|
By pass six the audit was **mostly repairing itself**. Not fatigue — arithmetic.
|
|
58
67
|
Each pass edits the corpus the next pass reads, so the newest edits are always the
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Certification — three readings at three distances, and all three must pass
|
|
2
|
+
|
|
3
|
+
A node is not closed by one agent's opinion. It is closed by **three independent
|
|
4
|
+
readings at escalating visibility**, and the run may not advance until all three
|
|
5
|
+
pass.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- Why one verifier is not enough, stated as the failure it produces
|
|
10
|
+
- The three tiers
|
|
11
|
+
- Blind, and it is the whole design
|
|
12
|
+
- A pass has to mean something, so two rules have teeth
|
|
13
|
+
- The report, and where each field lands
|
|
14
|
+
- The commands
|
|
15
|
+
- The fix cycle, and its ceiling
|
|
16
|
+
- What this costs, said out loud
|
|
17
|
+
- Rationalizations
|
|
18
|
+
|
|
19
|
+
## Why one verifier is not enough, stated as the failure it produces
|
|
20
|
+
|
|
21
|
+
A verifier reads the diff it was handed. That is not a shortcoming of the agent; it
|
|
22
|
+
is the definition of its context. And it means a whole class of defect is invisible
|
|
23
|
+
to it by construction:
|
|
24
|
+
|
|
25
|
+
- the change is correct where it was made, and a **caller's contract moved** under it
|
|
26
|
+
- a **second implementation of the same rule** did not get the fix
|
|
27
|
+
- a **documented behaviour** is now false, and the document still reads as true
|
|
28
|
+
- **another feature** reaches the same path and nobody considered the interaction
|
|
29
|
+
|
|
30
|
+
None of these is a bug in the changed lines. All of them ship. Each is found by
|
|
31
|
+
looking one level further out than the change — which is a different reading, not a
|
|
32
|
+
longer one, because the context that finds it is the context that excludes the diff.
|
|
33
|
+
|
|
34
|
+
## The three tiers
|
|
35
|
+
|
|
36
|
+
| Tier | Subject | Characteristic finding |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `unit` | the changed functions, classes and branches, plus the node's own `check` | a branch nothing exercises; a boundary that moved |
|
|
39
|
+
| `seam` | everything that can reach the change — callers, callees, implementors, shared state, the neighbours' tests | a contract that moved under a dependent; the duplicate that did not get the fix |
|
|
40
|
+
| `product` | documentation, scenarios, user-visible strings, and the neighbouring features that share this path | a documented behaviour that is now false; an interaction nobody listed |
|
|
41
|
+
|
|
42
|
+
Agents: [`../../../agents/verifier-unit.md`](../../../agents/verifier-unit.md),
|
|
43
|
+
[`verifier-seam.md`](../../../agents/verifier-seam.md),
|
|
44
|
+
[`verifier-product.md`](../../../agents/verifier-product.md).
|
|
45
|
+
|
|
46
|
+
## Blind, and it is the whole design
|
|
47
|
+
|
|
48
|
+
**The three run in parallel and no tier reads another's report.** Three readings
|
|
49
|
+
that inform each other are one opinion with three signatures — and the failure mode
|
|
50
|
+
is specific: an agent that has just read a convincing account of the implementation
|
|
51
|
+
will paraphrase it back as product truth. The disagreement between blind readings is
|
|
52
|
+
the instrument, so `graph.py certify` refuses a report whose prose cites another
|
|
53
|
+
tier's verdict.
|
|
54
|
+
|
|
55
|
+
Dispatch all three in one message so they run concurrently. Give each the node id,
|
|
56
|
+
its `serves`, and the diff — nothing else, and never another tier's output.
|
|
57
|
+
|
|
58
|
+
## A pass has to mean something, so two rules have teeth
|
|
59
|
+
|
|
60
|
+
**A tier cannot pass on an empty `scope`.** `scope` is what the tier actually
|
|
61
|
+
opened, with `file:line`. A report that names nothing it read is a rubber stamp, and
|
|
62
|
+
three rubber stamps cost three times one verifier while reading as three times the
|
|
63
|
+
assurance — strictly worse than the single verdict it replaced. `certify` refuses it
|
|
64
|
+
by name.
|
|
65
|
+
|
|
66
|
+
**A tier cannot pass while carrying a `breaks` finding.** There are two severities
|
|
67
|
+
and no third, because a certification that admits a maybe admits everything:
|
|
68
|
+
|
|
69
|
+
- **`breaks`** — the node is not done. Carries a `check`: the command or judgement
|
|
70
|
+
that will prove the fix, for the same reason `replan.add` does. The finding
|
|
71
|
+
becomes a node the next round has to close, and handing that node the absence is
|
|
72
|
+
how the defect returns one round later.
|
|
73
|
+
- **`risk`** — found, judged survivable, and named. It ships, and it reaches the
|
|
74
|
+
closing verdict as a blocker with `can_continue_around: true`, which is exactly
|
|
75
|
+
what a named survivable finding is.
|
|
76
|
+
|
|
77
|
+
## The report, and where each field lands
|
|
78
|
+
|
|
79
|
+
Eight keys, all required, `[]` a valid answer and silence not one. On a pass
|
|
80
|
+
`certify` assembles the canonical seven-key verdict that
|
|
81
|
+
[`work-graph.md`](work-graph.md) already specifies, and no field is used for
|
|
82
|
+
something it does not mean:
|
|
83
|
+
|
|
84
|
+
| Tier field | Becomes | Because |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `confirms` | `done` | asked for, and now true |
|
|
87
|
+
| `not_examined` | `not_verified` | present, and no check touched it |
|
|
88
|
+
| `findings` at `risk` | `blockers`, `can_continue_around: true` | found, judged survivable, named |
|
|
89
|
+
| `evidence` | `evidence`, prefixed with the tier | the command and what it printed |
|
|
90
|
+
|
|
91
|
+
`certify` runs the assembled verdict through the same `verdict_violations` gate
|
|
92
|
+
`close` will apply, so a certification cannot hand the run a verdict its own
|
|
93
|
+
consumer refuses.
|
|
94
|
+
|
|
95
|
+
## The commands
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# three reports in, one verdict out — exits 1 if any tier failed
|
|
99
|
+
graph.py certify --node N-007 \
|
|
100
|
+
--tier unit.json --tier seam.json --tier product.json
|
|
101
|
+
|
|
102
|
+
# unchanged, and still the only thing that moves the graph
|
|
103
|
+
graph.py close --verdict .task-pipeline/verdict-N-007.json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## The fix cycle, and its ceiling
|
|
107
|
+
|
|
108
|
+
A failing round **records itself and leaves the node open.** `certification` on the
|
|
109
|
+
node carries the round number, this round's three verdicts and the history of every
|
|
110
|
+
round — written on failure too, because a failing round that wrote nothing would
|
|
111
|
+
erase the only evidence that a node is churning.
|
|
112
|
+
|
|
113
|
+
The cycle is: `certify` → fail → the `breaks` findings become nodes (each already
|
|
114
|
+
carrying its `check`) → fix → `certify` again, round `N+1`. Same three tiers, same
|
|
115
|
+
blind dispatch. A tier that passed in an earlier round is **re-run**, because the
|
|
116
|
+
fix is a new change and the level it passed on is not the level it now faces.
|
|
117
|
+
|
|
118
|
+
**At the ceiling the gate measures rather than stops** —
|
|
119
|
+
[`loop-guard.md`](loop-guard.md). `--ceiling` defaults to 3. At or over it, `certify`
|
|
120
|
+
still runs and still reports the tiers; what it adds is the name of the tier that
|
|
121
|
+
has failed **every** round. A run spinning on one level needs the operator to see
|
|
122
|
+
*which* level:
|
|
123
|
+
|
|
124
|
+
- the same tier every round → the level is being misread, or the node is the wrong
|
|
125
|
+
shape. Not one fix away. Re-plan the node, do not attempt round four.
|
|
126
|
+
- different tiers each round → churn across levels. Usually one requirement that
|
|
127
|
+
was never decided, surfacing at whichever distance looks at it.
|
|
128
|
+
|
|
129
|
+
## What this costs, said out loud
|
|
130
|
+
|
|
131
|
+
Three agents per node instead of one. That is the price of the visibility, and it is
|
|
132
|
+
paid per node rather than per run. The three are dispatched in parallel, so the
|
|
133
|
+
wall-clock cost is roughly one reading; the token cost is three. A node whose
|
|
134
|
+
`check` is mechanical and whose blast radius is genuinely nil still pays it — and a
|
|
135
|
+
tier with nothing to find says so in `scope` and `not_examined` rather than being
|
|
136
|
+
skipped, because **a skipped level and a clean level are indistinguishable
|
|
137
|
+
afterwards**, and only one of them is evidence.
|
|
138
|
+
|
|
139
|
+
## Rationalizations
|
|
140
|
+
|
|
141
|
+
| Temptation | Why it is wrong |
|
|
142
|
+
|---|---|
|
|
143
|
+
| «All three would say the same thing» | Then all three say it, at a cost you already know, and the run has three signatures instead of one guess about what the other two would have found |
|
|
144
|
+
| «The seam tier can read the unit report first — it saves tokens» | It saves tokens by removing the second opinion. The reports are cheap; the independence is the product |
|
|
145
|
+
| «Tier 3 passed last round, skip it» | The fix is a new change. A tier's pass is about the tree it read, and that tree moved |
|
|
146
|
+
| «Round 4 will get it» | Read the ceiling's output. The same tier failing three times is a planning defect wearing a verification failure's clothes |
|
|
@@ -58,6 +58,15 @@ not the operator confirming it is what they asked for, and no amount of checking
|
|
|
58
58
|
makes it one. Which stages are manual is the **operator's** decision, recorded in
|
|
59
59
|
their `pipeline.json`; the framework fixes no stage count and no gate assignment.
|
|
60
60
|
|
|
61
|
+
**That sentence is about the pipeline's SHAPE, and nothing else.** It says a project
|
|
62
|
+
chooses how many stages it runs and which of them wait for a person. It does not say the
|
|
63
|
+
criteria inside a gate are per-run negotiable: where a project keeps stage 10 manual, what
|
|
64
|
+
that gate asks is [`acceptance.md`](acceptance.md)'s policy **`AP-1`**, which is versioned
|
|
65
|
+
and has an owner. The two rules stood side by side unscoped until 2026-08-20 (`B-091`), and
|
|
66
|
+
a reader could take either as the whole rule — *the framework fixes nothing* and *the ladder
|
|
67
|
+
is fixed* are both in the shipped doctrine, which is how an acceptance standard becomes
|
|
68
|
+
something every run re-argues.
|
|
69
|
+
|
|
61
70
|
## The judgment gate — a ruling is not a measurement
|
|
62
71
|
|
|
63
72
|
Two types were not enough, and the gap was not cosmetic. A reviewer's ruling, a check that
|
|
@@ -71,6 +71,7 @@ a row pointing outside the bundle is the defect this file exists to catch.
|
|
|
71
71
|
| What a spec must lock, the UX-track order, the module dossier | `references/spec.md` |
|
|
72
72
|
| The zero-context plan format, parallel groups, set equality | `references/planning.md` |
|
|
73
73
|
| The work graph: its fields, the verbs and their exit codes, and the three invariants a schema cannot state | `references/work-graph.md`, `scripts/graph.py`, `graph.schema.json` |
|
|
74
|
+
| How a node is CLOSED: three blind readings at escalating visibility, all three required, and the round ledger the ceiling reads | `references/certification.md`, `agents/verifier-{unit,seam,product}.md`, `scripts/graph.py certify` |
|
|
74
75
|
| Workspace isolation, the subagent loop, who may write the register | `references/build.md` |
|
|
75
76
|
| The review rubric, diff packages, the three verdicts | `references/review.md` |
|
|
76
77
|
| **False success** — the class, its known shapes and its two rules | `references/gates.md` |
|
|
@@ -369,12 +369,18 @@ deduplicated, and always exiting 0 — a hook that can fail a `Read` breaks ever
|
|
|
369
369
|
every session. `scripts/graph.py doctrine` reports how many of the bundle's reference files
|
|
370
370
|
a run opened and lists the rest.
|
|
371
371
|
|
|
372
|
-
Why
|
|
373
|
-
written by the party the claim is about, is not evidence. And
|
|
372
|
+
Why a hook appends it is the same reason as above: a claim about what somebody read,
|
|
373
|
+
written by the party the claim is about, is not evidence. **And that is an intent rather
|
|
374
|
+
than a proof, so the verb reports `unattested`:** the ledger is the file the agent appends
|
|
375
|
+
to at every stage and carries no writer field, so nothing in it separates a hook-written
|
|
376
|
+
line from one an agent typed. Saying *never agent-written* was a provenance claim the
|
|
377
|
+
format cannot support — B-014's class, in the mechanism built to close it.
|
|
378
|
+
|
|
379
|
+
And why the verb prints
|
|
374
380
|
`unmeasured` rather than `0` when there are no such lines is the same reason again — the
|
|
375
381
|
hook being absent and the run reading nothing are **opposite facts** the ledger cannot
|
|
376
382
|
separate, so it claims neither. A `0` there would be the reassuring answer to a question
|
|
377
|
-
nobody asked, over
|
|
383
|
+
nobody asked, over 35 files nobody checked.
|
|
378
384
|
|
|
379
385
|
It is a disclosure: no floor, no direction, never a target. The moment the number becomes
|
|
380
386
|
something to raise, a run will open files to raise it.
|
|
@@ -19,7 +19,7 @@ justifies reading it protects one section while the file below it doubles.
|
|
|
19
19
|
| Artifact | Parts | How it is read |
|
|
20
20
|
|---|---|---|
|
|
21
21
|
| `docs/evidence/retro.md` — **one per project** | **Standing instructions** (max **10**) · **Run stamps** (max **10**, oldest rotate out) | stage 0, **in full** — both are bounded by a **cap**, which *one line each* never was |
|
|
22
|
-
| the same file's **Recent log** | entries from the last five run stamps
|
|
22
|
+
| the same file's **Recent log** | narrative entries, **uncapped by design** — the heading said *entries from the last five run stamps* until 2026-08-20 while the section held 25 going back nine days, a bound in a heading that nothing enforced | stage 0, **queried** by the task's nouns. It said *in full* until 2026-08-10, when it measured **74%** of the file: an uncapped section inside a binding source is what makes the capped part get skimmed |
|
|
23
23
|
| `docs/evidence/retro/YYYY-QN.md` — the archive | every entry and every retirement ever written, append-only | **queried** by the task's nouns; never read end to end |
|
|
24
24
|
|
|
25
25
|
Seed the archive from [`../templates/retro-archive.md`](../templates/retro-archive.md).
|
|
@@ -273,6 +273,12 @@ never that the work was skipped quietly.
|
|
|
273
273
|
`next` refuses to walk one. The reason to prefer it over a prose plan is measured, not
|
|
274
274
|
aesthetic: a 400-node graph and a 4-node graph produce the same 27-byte frontier, so the
|
|
275
275
|
cost of knowing what is next does not grow with the programme.
|
|
276
|
+
- **And the check is what THREE readings will run, not one**
|
|
277
|
+
([`certification.md`](certification.md)). A node is closed by `unit`, `seam` and `product`
|
|
278
|
+
reports, dispatched blind and in parallel, and `certify` refuses the close until all three
|
|
279
|
+
pass. That is a planning fact, not only a verification one: a node whose blast radius nobody
|
|
280
|
+
can name is a node the seam and product tiers cannot scope, so `touches` and `serves` are
|
|
281
|
+
what make the two outer readings possible at all.
|
|
276
282
|
- **The queue exists here, so the loop arms here** ([`continuity.md`](continuity.md) →
|
|
277
283
|
*Part 1a*). Where `run.loop.arm` is `after-decomposition` and the map holds more than
|
|
278
284
|
one module, arm the mode at the close of this stage and print one line: the mode, and
|
|
@@ -59,6 +59,7 @@ conditional on the code, never merely sequenced after it.**
|
|
|
59
59
|
| `goal` | the release goal | `0` · `3` unstated |
|
|
60
60
|
| `add` | the id it allocated | `0` · `1` refused |
|
|
61
61
|
| `park` | the id and the reason | `0` · `1` refused |
|
|
62
|
+
| `certify` | the round, and on a failure every `breaks` finding with its fix and its check | `0` all three tiers passed · `1` a tier failed, or a report is malformed |
|
|
62
63
|
| `close` | the goal, the new frontier count, and what was not verified | `0` · `1` refused **or the verdict stops the run** |
|
|
63
64
|
| `producer` | what produced this proof — actor, model, runtime, skill, config, commit, trace | `0` |
|
|
64
65
|
| `doctrine` | how many of the bundle's reference files this run opened | `0` |
|
|
@@ -77,6 +78,8 @@ A **`parked`** node is the single exemption: it is the one node nobody will clos
|
|
|
77
78
|
*n/a — parked* in that field is confidence without correctness. `park` never removes what
|
|
78
79
|
the node said it would run.
|
|
79
80
|
|
|
81
|
+
**A node is closed by three readings, not one.** `certify` takes one tier report from each of `unit`, `seam` and `product` — dispatched blind and in parallel — requires all three to pass, and assembles the seven-key verdict `close` consumes. `close`'s contract is unchanged; what changed is that the verdict is now built from three readings at different distances instead of written from one, because a change can be correct where it was made and wrong one level out. A failing round records itself and leaves the node open. Doctrine: [`certification.md`](certification.md).
|
|
82
|
+
|
|
80
83
|
**`close` stamps the commit; the verifier never supplies it.** A verdict written after the
|
|
81
84
|
tree moved is evidence about a different tree, and an agent cannot name the wrong commit if
|
|
82
85
|
it is never the one naming one.
|