create-agent-rig 0.7.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -11,6 +11,66 @@ Numbering is ordinary semver — **additive is a minor, a fix is a patch** — s
11
11
  that "I only take minors" remains a usable policy; 0.3.2 shipped additive
12
12
  content as a patch by the owner's call and stays recorded as one.
13
13
 
14
+ ## 0.7.1
15
+
16
+ **The gate could not be run on work that has no queue item.** `pr-ship` names
17
+ owner-directed work and hotfixes with no item as a legitimate path — step 4
18
+ tells the fan-out to declare it and have the reviewer skip the item-contract
19
+ check openly. Step 1 then made that path unexecutable: it called
20
+ `revalidate.mjs` with an unconditional `--ticket`, and the script refused
21
+ without one. A newly scaffolded project inherited a rulebook that contradicted
22
+ itself at the one checkpoint before every PR, so the first hotfix in a fresh rig
23
+ had nothing it was allowed to do. Found downstream while integrating published
24
+ 0.7.0.
25
+
26
+ A patch: no file is added or removed, no new dependency, and the public CLI of
27
+ the generator is untouched. What changes is one flag on one internal script and
28
+ the skill step that calls it.
29
+
30
+ ### Fixed
31
+
32
+ - **`revalidate.mjs` BEFORE_PR now has two modes, and neither is inferred.**
33
+ `--ticket <key>` is unchanged, including the mandatory claim comparison.
34
+ `--owner-directed` runs the same default-branch drift comparison for work
35
+ with no item, reaching no tracker, no adapter and no claim record — so it
36
+ needs no tracker credentials. Passing both flags, or neither, is exit 1: a
37
+ mode chosen by absence is a mode nobody reviewed.
38
+
39
+ It is not a lighter checkpoint. A default-branch change under a path the
40
+ branch touches, or one a `check-premises` record cited, holds with the same
41
+ exit 2. What it drops is the claim comparison, because work with no item has
42
+ no claim to compare, and it records `ticket: null` rather than inventing an
43
+ id.
44
+
45
+ **Four refusals keep it from becoming a bypass** — exit 1, nothing
46
+ journalled: when the run carries an unresolved `revalidationHold`, when the
47
+ run declares a take-up, when the branch touches a tracked
48
+ `.rig/claims/*.json` in any direction (added, modified, removed or renamed),
49
+ and at `BEFORE_CLOSE`. The first is the one that makes re-running a held or
50
+ `UNVERIFIABLE` ticketed call in this mode a refusal rather than a way past
51
+ it; the run's stop inputs are read fail-closed, so an unreadable
52
+ `state.json` refuses instead of reading as an empty run.
53
+
54
+ An owner-directed HOLD is answered the same way a ticketed one is, with
55
+ `revalidate.mjs outcome` at the same point, passing `--owner-directed`
56
+ instead of `--ticket`. It addresses the detection by mode, since that
57
+ detection carries no ticket to name.
58
+
59
+ ⚠ Its stated limits, because a governance mode is trusted as far as it is
60
+ described. Nothing can prove an item does not exist. With no `RIG_RUN_DIR`
61
+ there is no run state, so the hold and take-up refusals cannot fire — the
62
+ command says so on stdout and in `evidence.runState` rather than reporting a
63
+ clean check. The claim refusal reads the branch diff, so a record already on
64
+ the default branch or not yet committed is not seen. And `--base` is the sole
65
+ authority for the verdict here, the claim comparison that would otherwise
66
+ survive a wrong base being absent.
67
+
68
+ - **`pr-ship` step 1 states both paths**, and step 4 now spells the words the
69
+ fan-out is launched with — `no item — owner-directed` — and says that this
70
+ skips the item-contract check and **nothing else**: the checks, the routing,
71
+ the security, code and prose/governance reviews, the coverage check and the
72
+ DoD all still run.
73
+
14
74
  ## 0.7.0
15
75
 
16
76
  **A durable claim record under the revalidation 0.6.2 already had, and the
@@ -56,6 +116,31 @@ a patch is the case the rule exists to prevent.
56
116
  was allowed on the original surface too — and it is unchanged by this
57
117
  release.
58
118
 
119
+ - **A shell command the two shell guards cannot read is refused, where it used
120
+ to be allowed.** `guard-bash` and `block-no-verify` read the `command` a hook
121
+ hands them on a shell tool, and asked only whether it was a string. A command
122
+ that was **there** in some other container — an array of argv words, an
123
+ object — failed that test and returned _allow_ before the kill switch was ever
124
+ consulted, so restating a forbidden command in another shape stepped over an
125
+ armed brake. Those two guards now decide the three outcomes in one shared
126
+ place: an **absent** command still allows, because there is nothing to judge;
127
+ a **string** is inspected as before; a command that is present in a shape they
128
+ cannot read is **refused**, naming the shape they expected.
129
+
130
+ ⚠ The scope is those two guards, and it is narrow on purpose — it is **not** a
131
+ repository-wide ruling on the word `command`. `guard-secret-file` reads an
132
+ `apply_patch` `command` that is a _list of strings_, exactly the shape this
133
+ contract calls unreadable, and it is right to; routing a third guard through
134
+ the same place without checking what its tool actually sends would start
135
+ refusing input another guard exists to read.
136
+
137
+ ⚠ Two further limits. This changes only the unreadable case — the name-exact
138
+ bound described just above is untouched, and an absent field remains fail-open
139
+ on purpose, since a guard that blocked when handed nothing would be turned off
140
+ within the hour. And the **edit** surfaces are not part of this: what
141
+ `.claude/hooks/lib/edit-input.mjs` does with a `tool_input` it cannot read is
142
+ unchanged by this release.
143
+
59
144
  - **An adapter it cannot read is `UNVERIFIABLE`, not a stack trace.** A
60
145
  revalidation whose queue adapter could not be reached exited on a raw Node
61
146
  stack trace, which a caller could read as noise rather than as a hold. It now
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agent-rig",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Scaffold a new project with an agent operating system (rules, gates, hooks) and a runnable code skeleton",
5
5
  "keywords": [
6
6
  "create",
@@ -57,13 +57,66 @@ blockers.
57
57
  confidently-wrong reviews. Everything below is scoped to this diff.
58
58
 
59
59
  Then, on the fetched ref, ask whether the branch is still the branch the run
60
- took up:
60
+ took up. **Two paths, and you state which one — the command infers neither.**
61
+ A branch that is a queue item's take-up:
61
62
 
62
63
  ```sh
63
64
  node .claude/scripts/revalidate.mjs --point BEFORE_PR --ticket <item-id> --base origin/<default>
64
65
  ```
65
66
 
66
- It runs the existing revalidation chain against the tracked, versioned
67
+ Owner-directed work or a hotfix that has **no item** — the case step 4 below
68
+ already tells you to declare to the reviewers:
69
+
70
+ ```sh
71
+ node .claude/scripts/revalidate.mjs --point BEFORE_PR --owner-directed --base origin/<default>
72
+ ```
73
+
74
+ 🔴 **The second path is not a lighter checkpoint, and it is never a skip.**
75
+ It runs the same `main:<path>` comparison and holds on the same exit 2; what
76
+ it drops is the claim comparison, because work with no item has no claim to
77
+ compare. It resolves no queue config, so it reaches no tracker and needs no
78
+ credentials. Passing both flags, or neither, is exit 1.
79
+
80
+ **Four refusals** — exit 1, nothing journalled — keep it from being the way
81
+ around a claim or revalidation failure. It is refused when this run carries
82
+ an **unresolved revalidation hold** (what the ticketed path writes when it
83
+ holds or answers `UNVERIFIABLE`), when this run **declares a take-up**, when
84
+ the branch **touches a tracked `.rig/claims/*.json`** in any direction —
85
+ added, modified, removed or renamed — and at `BEFORE_CLOSE`. So re-running a
86
+ held ticketed call in this mode does not get past it: resolve the hold with
87
+ `outcome` instead.
88
+
89
+ ⚠ **What those refusals do not cover**, because a governance mode is trusted
90
+ exactly as far as it is described:
91
+
92
+ - **With no `RIG_RUN_DIR` there is no run state to read**, so the hold and
93
+ take-up refusals cannot fire — and nothing is journalled. The command says
94
+ so on stdout and in `evidence.runState`; it is not evidence that neither
95
+ exists. An attended gate run is exactly this shape, so read that line.
96
+ - The claim refusal reads the **branch diff**, so a claim record already on
97
+ the default branch, or written and not committed, is not seen.
98
+ - **`--base` is the sole authority for the verdict here**, the claim
99
+ comparison that would otherwise survive a wrong base being absent. Pass
100
+ the fetched `origin/<default>`, not a local copy and not `HEAD`.
101
+ - Nothing can prove an item does not exist. The rest is your word, recorded
102
+ as such, with `ticket: null` and no invented id.
103
+
104
+ Pinned in the generator's `test/template/owner-directed-revalidation.test.ts`
105
+ (absent in a generated rig) › "refuses when this run carries an unresolved
106
+ revalidation hold", › "refuses when the declared run already carries a
107
+ take-up", › "refuses when the branch RENAMES a claim record — the case
108
+ --diff-filter=AM could not see" and › "says out loud that an undeclared run
109
+ checked neither the hold nor the take-up".
110
+
111
+ **Exit 2 here is a HOLD with the same shape as the ticketed one**, and the
112
+ same two-step remedy: re-read the default branch on each named path, then
113
+ record what the re-read concluded — `node .claude/scripts/revalidate.mjs
114
+ outcome --point BEFORE_PR --owner-directed --action-changed <true | false>
115
+ --note '…'` — and come back through step 0. The owner-directed detection
116
+ carries no ticket, so `--owner-directed` is how the outcome addresses it;
117
+ the ticketed `--ticket <item-id>` form below cannot match it and is refused.
118
+
119
+ The ticketed path runs the existing revalidation chain against the tracked, versioned
67
120
  `.rig/claims/<item-id>.json`: the content-blind `scope` fingerprint set is
68
121
  authoritative here, while `takeUps` / `updatedAt` remain evidence only. It
69
122
  also names what the default branch changed since this branch forked on paths
@@ -71,14 +124,18 @@ blockers.
71
124
  journals one `revalidation` event at `point: BEFORE_PR`; **exit code 2 is a HOLD**, with one blocker per named source: re-read the item, or the default
72
125
  branch on that path, record what the re-read concluded —
73
126
  `node .claude/scripts/revalidate.mjs outcome --point BEFORE_PR --ticket <item-id> --action-changed <true | false> --note '…'`
74
- — and come back through step 0. A hold with no outcome is counted by the
75
- report as a re-read the run skipped. A missing, untracked, unreadable or
127
+ — and come back through step 0. **That `--ticket` form is this path's, not
128
+ both paths'** the owner-directed detection carries no ticket for it to
129
+ name, and its own `--owner-directed` outcome is written out above. A hold
130
+ with no outcome, in either mode, is counted by the report as a re-read the
131
+ run skipped. A missing, untracked, unreadable or
76
132
  unsupported claim is `UNVERIFIABLE`, exits 2, and stops automatic progress;
77
133
  so is a tracker whose adapter the command cannot READ, which means the
78
134
  question was never put rather than that the claim record is unreadable.
79
135
  Neither is ever read as a pass. Exit 1 is the command refusing (unknown
80
- point, no ticket, a base that is not a revision, or a queue config that does
81
- not resolve): fix the call or the config the message says which. Its limits are its own header's; the
136
+ point, neither mode or both, a base that is not a revision, or a queue config
137
+ that does not resolve plus, on the owner-directed path, the four refusals
138
+ above): fix the call or the config — the message says which. Its limits are its own header's; the
82
139
  cited-path set is a labelled assumption, not a recorded fact. Pinned in the
83
140
  generator's `test/template/revalidate.test.ts` (absent in a generated rig) ›
84
141
  "continues when only updatedAt moved and still reports the marker evidence"
@@ -182,9 +239,13 @@ blockers.
182
239
  **Whatever you launch, pass it the text of the queue item this branch
183
240
  implements.** A reviewer given only a diff cannot check the change against
184
241
  what was asked: a cold context has no way to know, and reconstructing it from
185
- the PR description would mean trusting the run under review. If there is no
186
- item — owner-directed work, a hotfix — say so when launching, and the
187
- reviewer skips that check openly instead of guessing at it.
242
+ the PR description would mean trusting the run under review. When there is
243
+ no item — owner-directed work, a hotfix — launch every reviewer with the
244
+ words **`no item owner-directed`** instead. That skips the item-contract
245
+ check, openly, and **nothing else**: the checks, the routing, the security,
246
+ code and prose/governance reviews, the coverage check and the DoD all still
247
+ run. It is one check narrower than a ticketed fan-out, not a cheaper gate,
248
+ and the same words are what step 1's `--owner-directed` call records.
188
249
 
189
250
  🔴 **The triggers below are lane-independent and may only ADD.** They read
190
251
  *what the code does*; the router reads *paths*, and a path cannot say that a
@@ -5,6 +5,62 @@
5
5
  * run took up?
6
6
  *
7
7
  * node .claude/scripts/revalidate.mjs --point BEFORE_PR --ticket <id> [--base origin/master] [--config <queue.json>] [--json]
8
+ * node .claude/scripts/revalidate.mjs --point BEFORE_PR --owner-directed [--base origin/master] [--json]
9
+ *
10
+ * BEFORE_PR has two mutually exclusive modes and neither is inferred (RP-94):
11
+ * `--ticket` for a branch that is an item's take-up, `--owner-directed` for
12
+ * owner-directed work or a hotfix that has no item — the path `pr-ship` step 4
13
+ * already named while step 1 could not execute it. Passing both, or neither,
14
+ * is exit 1 — owner-directed-revalidation.test.ts › "refuses both modes at
15
+ * once: exit 1, stderr only, nothing journaled" and › "refuses neither mode:
16
+ * exit 1, and the message names both ways forward".
17
+ *
18
+ * Owner-directed mode runs the `main:<path>` comparison below and nothing
19
+ * else. It resolves no queue config, so no tracker or adapter is reached and
20
+ * no credential is needed — owner-directed-revalidation.test.ts › "needs no
21
+ * tracker credentials: an adapter name that cannot resolve is never reached".
22
+ * A `hold` there is the same exit 2 as the ticketed path's — › "HOLDs when the
23
+ * default branch moved under a path the branch touches".
24
+ *
25
+ * FOUR refusals keep it from becoming a way around the claim chain — exit 1,
26
+ * nothing journalled, each with its own test in that file:
27
+ *
28
+ * - an unresolved `revalidationHold` in this run's state, which is what the
29
+ * ticketed path writes when it holds or answers UNVERIFIABLE — › "refuses
30
+ * when this run carries an unresolved revalidation hold";
31
+ * - a take-up this run declares — › "refuses when the declared run already
32
+ * carries a take-up";
33
+ * - a tracked `.rig/claims/*.json` this branch touches, added, modified,
34
+ * removed or renamed — › "refuses when the branch diff adds a tracked claim
35
+ * record", › "refuses when the branch diff modifies a tracked claim record",
36
+ * › "refuses when the branch RENAMES a claim record — the case
37
+ * --diff-filter=AM could not see" and › "refuses when the branch DELETES its
38
+ * claim record";
39
+ * - `BEFORE_CLOSE` — › "refuses owner-directed at BEFORE_CLOSE — the mode
40
+ * exists for BEFORE_PR only".
41
+ *
42
+ * The first of those is the one this mode most needs, and the first version
43
+ * shipped without it: a ticketed call that had already held was re-run here and
44
+ * exited 0 with nothing in the repository changed.
45
+ *
46
+ * ⚠ Its limits, stated because the mode is a governance surface. What makes a
47
+ * call owner-directed is the CALLER's word plus those four refusals: nothing
48
+ * here can prove an item does not exist. Three specific gaps, each measured
49
+ * rather than reasoned:
50
+ *
51
+ * - with no `RIG_RUN_DIR` there is no run state, so the hold and take-up
52
+ * refusals have nothing to read and cannot fire — and nothing is journalled
53
+ * either. The result says so in `evidence.runState`, and the report on
54
+ * stdout says so out loud — › "says out loud that an undeclared run checked
55
+ * neither the hold nor the take-up".
56
+ * - the claim refusal reads the branch DIFF, so a claim record already on the
57
+ * default branch, or written but not committed, is not seen.
58
+ * - `--base` decides more here than the verdict. The claim comparison that
59
+ * would otherwise survive a wrong base is absent, AND the claim-touch refusal
60
+ * reads the same `mergeBase..HEAD` range — so `--base HEAD` empties the
61
+ * branch diff and disarms that refusal as well as reporting `continue`. Pass
62
+ * the up-to-date `origin/<default>`; `pr-ship` step 1 refreshes it, since
63
+ * this script never talks to a remote itself.
8
64
  *
9
65
  * One existing checkpoint chain, with one authoritative durable baseline:
10
66
  *
@@ -45,18 +101,33 @@
45
101
  * "re-reads each dependant's state, and names one the tracker no longer
46
102
  * offers") for the loop's write-back.
47
103
  *
48
- * `outcome --point <P> --ticket <id> --action-changed true|false [--note …]`
49
- * (AR-136) is the second half of the evidence: after the re-read, it appends a
50
- * `revalidation-outcome` record whose `answers` is the seq of the latest
51
- * `revalidation` for that ticket and point in this run the join a report
52
- * needs, made by the writer rather than guessed by the reader. It refuses
53
- * without a run, without a matching revalidation, and with any word but
54
- * `true`/`false`, and writes nothing then. The typed resolution names the
55
- * stable detection id and clears only the matching run-level hold. Exit 2 on
56
- * `hold` or `unverifiable`, 0 on `continue`, and 1 when the call cannot be
57
- * acted on (unknown point, no ticket, a base that is not a revision — or, on
58
- * the paths that reach it, a queue config that does not resolve) — and then
59
- * nothing is journalled, because a refusal is not an answer.
104
+ * `outcome --point <P> {--ticket <id> | --owner-directed} --action-changed
105
+ * true|false [--note …]` (AR-136, extended by RP-94) is the second half of the
106
+ * evidence: after the re-read, it appends a `revalidation-outcome` record whose
107
+ * `answers` is the seq of the latest matching `revalidation` at that point in
108
+ * this run — the join a report needs, made by the writer rather than guessed by
109
+ * the reader.
110
+ *
111
+ * **Which revalidation it matches depends on the mode, and the two never
112
+ * cross.** `--ticket` matches by key and skips owner-directed detections
113
+ * outright; `--owner-directed` matches by `mode`, because such a detection
114
+ * carries `ticket: null` and cannot be addressed by key — and a ticketed
115
+ * `--ticket null` must not answer it either. Pinned in the generator's
116
+ * `test/template/owner-directed-revalidation.test.ts` (absent in a generated
117
+ * rig) › "answers an owner-directed hold with an owner-directed outcome", ›
118
+ * "refuses a ticketed outcome aimed at an owner-directed detection" and ›
119
+ * "refuses an owner-directed outcome when a ticketed hold is the only one this
120
+ * run carries — and leaves that hold latched".
121
+ *
122
+ * It refuses without a run, without a matching revalidation, and with any word
123
+ * but `true`/`false`, and writes nothing then. The typed resolution names the
124
+ * stable detection id and clears only the matching run-level hold — and only a
125
+ * hold whose id it actually names, which is why an owner-directed outcome
126
+ * cannot release a ticketed one. Exit 2 on `hold` or `unverifiable`, 0 on
127
+ * `continue`, and 1 when the call cannot be acted on (unknown point, neither
128
+ * mode or both, a base that is not a revision — or, on the paths that reach it,
129
+ * a queue config that does not resolve) — and then nothing is journalled,
130
+ * because a refusal is not an answer.
60
131
  *
61
132
  * ⚠ It reads `<base>` as it is in this checkout and never updates the remote
62
133
  * ref itself; `pr-ship` step 1 does that before calling this. A stale ref
@@ -66,17 +137,24 @@
66
137
  */
67
138
 
68
139
  import { execFileSync } from 'node:child_process';
140
+ import { createHash } from 'node:crypto';
69
141
  import { realpathSync } from 'node:fs';
70
142
  import { dirname, join } from 'node:path';
71
143
  import { fileURLToPath } from 'node:url';
72
144
  import { withoutGitLocation } from './git-env.mjs';
73
145
  import { readRun, recordEvent } from './run-journal.mjs';
74
- import { clearRevalidationHold, readState, recordRevalidationHold } from './run-state.mjs';
146
+ import {
147
+ clearRevalidationHold,
148
+ readState,
149
+ readStateForSelection,
150
+ recordRevalidationHold,
151
+ } from './run-state.mjs';
75
152
  import { POINTS as ALL_POINTS, REVALIDATES } from './lib/revalidation-points.mjs';
76
153
  import { takeUpEvidenceOf } from './queue/core.mjs';
77
154
  import { loadConfig, optionsWithPlanPath, resolveAdapter } from './queue/index.mjs';
78
155
  import { projectRootOfConfig } from './queue/index.mjs';
79
156
  import {
157
+ CLAIM_SCHEMA_VERSION,
80
158
  revalidateClaim,
81
159
  targetShaOf,
82
160
  unverifiableResult,
@@ -95,6 +173,7 @@ const parseArgs = (argv) => {
95
173
  outcome: false,
96
174
  point: null,
97
175
  ticket: null,
176
+ ownerDirected: false,
98
177
  base: 'origin/master',
99
178
  config: null,
100
179
  json: false,
@@ -105,6 +184,7 @@ const parseArgs = (argv) => {
105
184
  for (let i = 0; i < argv.length; i += 1) {
106
185
  const arg = argv[i];
107
186
  if (i === 0 && arg === 'outcome') args.outcome = true;
187
+ else if (arg === '--owner-directed') args.ownerDirected = true;
108
188
  else if (arg === '--json') args.json = true;
109
189
  else if (arg === '--action-changed') args.actionChanged = argv[++i] ?? null;
110
190
  else if (arg === '--note') args.note = argv[++i] ?? null;
@@ -300,6 +380,79 @@ const answerUnverifiable = ({ runDir, ticket, point, json }, operation, cause) =
300
380
  process.exit(2);
301
381
  };
302
382
 
383
+ /**
384
+ * A tracked claim record, by repository-relative path (RP-94).
385
+ *
386
+ * ⚠ It is a SECOND spelling of the path `claimPathFor` builds, and the two are
387
+ * kept in step by `test/template/owner-directed-revalidation.test.ts` (absent
388
+ * in a generated rig) › "matches the path claimPathFor actually builds, from
389
+ * the repository root and from a nested rig root" — a correspondence check
390
+ * rather than a comment asking the next reader to remember. The leading
391
+ * `(^|/)` is why a rig whose root sits below the git root is still matched;
392
+ * this mode resolves no queue config, so it cannot ask where that root is.
393
+ */
394
+ const CLAIM_RECORD = /(^|\/)\.rig\/claims\/[^/]+\.json$/;
395
+
396
+ const OWNER_DIRECTED = 'owner-directed';
397
+
398
+ /**
399
+ * The BEFORE_PR verdict for work that has no item (RP-94).
400
+ *
401
+ * It carries the SAME `main:<path>` drift decision the ticketed path reaches —
402
+ * this mode drops the claim comparison because there is no claim, and drops
403
+ * nothing else. `ticket` is `null` rather than a placeholder: a record naming
404
+ * an item that does not exist is worse than one that admits it has none, and
405
+ * every reader downstream distinguishes them by that field.
406
+ *
407
+ * Pinned in the generator's `test/template/owner-directed-revalidation.test.ts`
408
+ * — absent in a generated rig — › "runs BEFORE_PR with no item and no claim
409
+ * when the default branch did not move under the branch" and › "HOLDs when the
410
+ * default branch moved under a path the branch touches".
411
+ */
412
+ const ownerDirectedResult = ({ point, base, mergeBase, cited, changed, now, runDeclared }) => {
413
+ const source = changed.map((path) => `main:${path}`);
414
+ const held = source.length > 0;
415
+ return {
416
+ schemaVersion: CLAIM_SCHEMA_VERSION,
417
+ // 🔴 `mergeBase` is in the hash, and it is what keeps this id from being a
418
+ // CONSTANT. Without it the digest was `{mode, point, source}` alone — the
419
+ // same value for every `continue` that has ever run, and the same for any
420
+ // two holds naming the same paths. `revalidation-report.mjs` flattens every
421
+ // run into one typed-resolution index, so one `--action-changed false`
422
+ // recorded last week would mark a genuine hold today as already answered:
423
+ // the metric the report exists to produce, quietly wrong. It stays stable
424
+ // across RETRIES of the same checkpoint on the same branch, which is the
425
+ // property `answerUnverifiable`'s identity has and the one that matters.
426
+ id: createHash('sha256')
427
+ .update(JSON.stringify({ mode: OWNER_DIRECTED, point, mergeBase, source }))
428
+ .digest('hex'),
429
+ ticket: null,
430
+ mode: OWNER_DIRECTED,
431
+ point,
432
+ checkpoint: point,
433
+ result: held ? 'CHANGED' : 'CURRENT',
434
+ changed: held,
435
+ source,
436
+ action: held ? 'hold' : 'continue',
437
+ movedFingerprintSet: [],
438
+ sourcePointer: null,
439
+ evidence: {
440
+ claim: 'not compared: owner-directed work has no item, so there is no claim record',
441
+ tracker: 'not read: owner-directed mode resolves no queue adapter',
442
+ // 🔴 "Could not check" is recorded as itself, never as "checked and
443
+ // clean". With no run directory the hold and take-up refusals have
444
+ // nothing to read, and a reader who saw only `continue` would take the
445
+ // pair for having passed.
446
+ runState: runDeclared
447
+ ? 'read fail-closed: no unresolved revalidation hold, no declared take-up'
448
+ : 'NOT read: no RIG_RUN_DIR, so neither the revalidation hold nor the take-up was checked',
449
+ },
450
+ observedAt: now,
451
+ task: { changed: null, from: null, to: null },
452
+ main: { base, mergeBase, cited, changed },
453
+ };
454
+ };
455
+
303
456
  /** Every adapter call in this script goes through here, or it can still crash. */
304
457
  const readAdapter = async (operation, read, context) => {
305
458
  try {
@@ -332,7 +485,31 @@ if (invokedDirectly()) {
332
485
  if (!known.includes(args.point)) {
333
486
  refuse(`unknown point: ${args.point ?? '(none)'}. This script knows ${known.join(', ')}.`);
334
487
  }
335
- if (!args.ticket) refuse('--ticket is required: the item whose take-up this branch is.');
488
+ // RP-94. BEFORE_PR has two modes, and neither is ever inferred. `pr-ship`
489
+ // already named owner-directed work with no item as a legitimate path while
490
+ // this script refused every call without `--ticket`, so that path could not
491
+ // be walked at all. The mode is now stated at the call site — silence is a
492
+ // refusal, not a default, because a mode chosen by absence is a mode nobody
493
+ // reviewed.
494
+ if (args.ticket && args.ownerDirected) {
495
+ refuse(
496
+ '--ticket and --owner-directed are mutually exclusive: a branch that is an item\'s ' +
497
+ 'take-up is not owner-directed work. Pass exactly one.',
498
+ );
499
+ }
500
+ if (args.ownerDirected) {
501
+ if (args.point !== 'BEFORE_PR') {
502
+ refuse(
503
+ `--owner-directed is a BEFORE_PR mode only; ${args.point} compares the claim record ` +
504
+ 'itself and still needs --ticket.',
505
+ );
506
+ }
507
+ } else if (!args.ticket) {
508
+ refuse(
509
+ '--ticket is required: the item whose take-up this branch is. For owner-directed ' +
510
+ 'work or a hotfix that has no item, pass --owner-directed instead.',
511
+ );
512
+ }
336
513
 
337
514
  const runDir = process.env.RIG_RUN_DIR || null;
338
515
 
@@ -342,16 +519,27 @@ if (invokedDirectly()) {
342
519
  refuse(`--action-changed must be true or false, got ${args.actionChanged ?? '(none)'}.`);
343
520
  }
344
521
  const { events } = readRun({ runDir });
522
+ // RP-94. An owner-directed detection carries `ticket: null`, so it cannot
523
+ // be addressed by key — `String(null)` would also match a literal ticket
524
+ // named "null". It is addressed by MODE instead, which is the only thing
525
+ // that distinguishes it. Without this, a `hold` the owner-directed path
526
+ // returned had no way to be answered at all: the skill's stated exit-2
527
+ // remedy was a command the script refused, which is the same shape of
528
+ // contradiction RP-94 exists to remove.
529
+ const subject = args.ownerDirected ? `${OWNER_DIRECTED} work` : args.ticket;
345
530
  const target = [...events]
346
531
  .reverse()
347
532
  .find(
348
533
  (e) =>
349
534
  e.kind === 'revalidation' &&
350
- String(e.data?.ticket) === String(args.ticket) &&
351
- e.data?.point === args.point,
535
+ e.data?.point === args.point &&
536
+ (args.ownerDirected
537
+ ? e.data?.mode === OWNER_DIRECTED
538
+ : e.data?.mode !== OWNER_DIRECTED &&
539
+ String(e.data?.ticket) === String(args.ticket)),
352
540
  );
353
541
  if (!target) {
354
- refuse(`no revalidation of ${args.ticket} at ${args.point} in ${runDir} for this outcome to answer.`);
542
+ refuse(`no revalidation of ${subject} at ${args.point} in ${runDir} for this outcome to answer.`);
355
543
  }
356
544
  const now = new Date().toISOString();
357
545
  const actionRequired = args.actionChanged === 'true';
@@ -364,7 +552,10 @@ if (invokedDirectly()) {
364
552
  actionRequired,
365
553
  driftOrigin: 'unknown',
366
554
  resolvedAt: now,
367
- ticket: args.ticket,
555
+ // `null`, never a placeholder, for the same reason the detection this
556
+ // answers carries none.
557
+ ticket: args.ownerDirected ? null : args.ticket,
558
+ ...(args.ownerDirected ? { mode: OWNER_DIRECTED } : {}),
368
559
  point: args.point,
369
560
  actionChanged: actionRequired,
370
561
  note: args.note,
@@ -376,11 +567,181 @@ if (invokedDirectly()) {
376
567
  process.stdout.write(
377
568
  args.json
378
569
  ? `${JSON.stringify(record, null, 2)}\n`
379
- : `revalidation-outcome: ${args.ticket} at ${args.point} answers seq ${target.seq} — actionChanged ${args.actionChanged}\n`,
570
+ : `revalidation-outcome: ${subject} at ${args.point} answers seq ${target.seq} — actionChanged ${args.actionChanged}\n`,
380
571
  );
381
572
  process.exit(0);
382
573
  }
383
574
 
575
+ if (args.ownerDirected) {
576
+ // RP-94. BEFORE_PR for work that has no item: the same main-vs-branch drift
577
+ // comparison, with no tracker, no adapter and no claim record.
578
+ //
579
+ // 🔴 It sits ABOVE the queue-config resolution on purpose. "This mode reads
580
+ // no tracker" has to be a property of the control flow rather than a
581
+ // promise in a comment — nothing below this block runs, so a missing
582
+ // credential or an adapter name that does not resolve cannot reach it.
583
+ // Pinned by › "needs no tracker credentials: an adapter name that cannot
584
+ // resolve is never reached".
585
+ //
586
+ // The refusals below are what keep the mode from becoming a way around the
587
+ // claim chain, and every one is decided BEFORE anything is journalled: a
588
+ // refusal is not an answer, so it leaves no revalidation record behind.
589
+ //
590
+ // 🔴 Read FAIL-CLOSED. `readState` is the permissive reader and its own
591
+ // header forbids exactly this use — "a corrupt file there may be hiding a
592
+ // persisted stop" — so an unreadable run refuses here instead of reading as
593
+ // "this run declares nothing". The first version used `readState`, and a
594
+ // truncated `state.json` carrying a take-up was measured continuing.
595
+ let state = {};
596
+ if (runDir) {
597
+ try {
598
+ state = readStateForSelection(runDir);
599
+ } catch (error) {
600
+ refuse(
601
+ `--owner-directed refused: this run's state could not be read (${runDir}): ` +
602
+ `${error.message}. An unreadable run may be hiding a stop.`,
603
+ );
604
+ }
605
+ }
606
+
607
+ // 🔴 The refusal this mode most needs, and the one its first version did
608
+ // not have. A ticketed BEFORE_PR that holds or comes back UNVERIFIABLE
609
+ // latches `revalidationHold`; re-running the same checkpoint here was
610
+ // MEASURED exiting 0 with nothing in the repository changed — the exact
611
+ // bypass RP-94 names. Neither other refusal can fire in that state, and
612
+ // that is structural rather than unlucky: `takeUps` is never populated on
613
+ // the default `plan-md` adapter at all, and the commonest hold is a
614
+ // MISSING claim record, which is precisely when the branch writes none.
615
+ // This one is adapter-independent because `recordRevalidationHold` is.
616
+ const hold = state.revalidationHold;
617
+ if (hold) {
618
+ // A malformed hold still refuses — it is a hold either way — but it must
619
+ // not print `undefined at undefined`, which reads as a broken command
620
+ // rather than as the stop it is.
621
+ const describe = (value, fallback) => (typeof value === 'string' ? value : fallback);
622
+ refuse(
623
+ `--owner-directed refused: this run carries an unresolved revalidation hold ` +
624
+ `(${describe(hold.ticket, 'an unnamed item')} at ` +
625
+ `${describe(hold.checkpoint, 'an unnamed checkpoint')}, ` +
626
+ `${describe(hold.result, 'result unrecorded')}, detection ` +
627
+ `${describe(hold.detectionId, 'unrecorded')}). ` +
628
+ 'Resolve it with `revalidate.mjs outcome`; re-running the checkpoint in the other ' +
629
+ 'mode is not a resolution, it is the bypass this mode refuses.',
630
+ );
631
+ }
632
+
633
+ const takeUps = state.takeUps;
634
+ // A present-but-unreadable take-up record is a refusal, not an empty one:
635
+ // `Object.keys` answers `[]` for a number and for a list, which would turn
636
+ // "this cannot be read" into "there is nothing here".
637
+ if (
638
+ takeUps !== undefined &&
639
+ (typeof takeUps !== 'object' || takeUps === null || Array.isArray(takeUps))
640
+ ) {
641
+ refuse(
642
+ `--owner-directed refused: this run's take-up record is not readable ` +
643
+ `(takeUps is ${Array.isArray(takeUps) ? 'a list' : typeof takeUps}, expected an object).`,
644
+ );
645
+ }
646
+ const takenUp = Object.keys(takeUps ?? {});
647
+ if (takenUp.length > 0) {
648
+ refuse(
649
+ `--owner-directed refused: this run already declares a take-up (${takenUp.join(', ')}). ` +
650
+ 'A run holding an item revalidates with --ticket; owner-directed is for work with none.',
651
+ );
652
+ }
653
+
654
+ let mergeBase;
655
+ try {
656
+ mergeBase = git(['merge-base', args.base, 'HEAD']).trim();
657
+ } catch (error) {
658
+ refuse(
659
+ `--base ${args.base} is not a revision this checkout can compare against: ${error.message}`,
660
+ );
661
+ }
662
+
663
+ // Any claim record this branch TOUCHES, in either direction.
664
+ //
665
+ // 🔴 `--no-renames`, and no `--diff-filter`, because both narrower forms
666
+ // were measured letting a claim record through. `--diff-filter=AM` reports
667
+ // NOTHING for `git mv .rig/claims/RP-1.json .rig/claims/RP-2.json` — git
668
+ // calls it `R100` — so a branch that demonstrably ends up carrying a claim
669
+ // record passed the check. And a branch that DELETES its claim makes the
670
+ // ticketed call `UNVERIFIABLE`, so excluding `D` left the deletion on the
671
+ // bypass path rather than out of scope. `--no-renames` splits a rename back
672
+ // into its delete and its add, which is what puts both halves in front of
673
+ // the filter. Match the CLASS — "this branch touched the claim store" —
674
+ // rather than enumerating the statuses that class can wear.
675
+ const claimsTouched = pathsOf(
676
+ git(['diff', '--name-only', '--no-renames', '-z', mergeBase, 'HEAD']),
677
+ ).filter((path) => CLAIM_RECORD.test(path));
678
+ if (claimsTouched.length > 0) {
679
+ refuse(
680
+ `--owner-directed refused: this branch touches tracked claim records ` +
681
+ `(${claimsTouched.join(', ')}). A branch that writes, moves or removes a claim is ` +
682
+ "an item's take-up; revalidate it with --ticket.",
683
+ );
684
+ }
685
+
686
+ const branchPaths = pathsOf(git(['diff', '--name-only', '-z', mergeBase, 'HEAD']));
687
+ const mainPaths = pathsOf(git(['diff', '--name-only', '-z', mergeBase, args.base]));
688
+ const cited = [...new Set([...branchPaths, ...citedByPremises(runDir)])];
689
+ const mainChanged = mainPaths.filter((path) => cited.includes(path));
690
+ const result = ownerDirectedResult({
691
+ point: args.point,
692
+ base: args.base,
693
+ mergeBase,
694
+ cited,
695
+ changed: mainChanged,
696
+ now: new Date().toISOString(),
697
+ runDeclared: Boolean(runDir),
698
+ });
699
+
700
+ if (runDir) {
701
+ recordEvent({ runDir, kind: 'revalidation', data: result, now: result.observedAt });
702
+ // 🔴 No run-level revalidation hold is recorded here, and the reason is
703
+ // NOT the one first written down. That said "a hold written here could
704
+ // never be cleared" — which stopped being true the moment `outcome
705
+ // --owner-directed` computed the very id `clearRevalidationHold` matches
706
+ // on. The real reason is narrower: `recordRevalidationHold` requires a
707
+ // string ticket, and this mode has none to give it without inventing
708
+ // one, which is the thing the whole mode refuses to do.
709
+ //
710
+ // ⚠ **State the asymmetry rather than let a reader assume symmetry.** A
711
+ // ticketed hold has TWO stops — the exit code, and a latch that
712
+ // `queue/index.mjs` and `unresolvedBlockingDetectionOf` both read (the
713
+ // latter also requires a string ticket, so it skips this one). An
714
+ // owner-directed hold has ONE: the exit code below. A caller who ignores
715
+ // it is not stopped a second time. Widening the latch to a ticketless
716
+ // hold is a run-state schema change — Tier 2, and not this hotfix's.
717
+ }
718
+
719
+ if (args.json) {
720
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
721
+ } else {
722
+ const detail = result.source.length > 0 ? ` — ${result.source.join(', ')}` : '';
723
+ process.stdout.write(
724
+ `revalidate ${args.point}: ${OWNER_DIRECTED} (no item) ${result.action}${detail}\n`,
725
+ );
726
+ if (!runDir) {
727
+ // Loud, on the normal path, not only in the JSON: a run this command
728
+ // could not inspect must not read as a run it inspected and cleared.
729
+ process.stdout.write(
730
+ ' ⚠ no RIG_RUN_DIR: the revalidation-hold and take-up refusals were NOT checked,\n' +
731
+ ' and nothing was journalled. This is not evidence that neither exists.\n',
732
+ );
733
+ }
734
+ if (result.action === 'hold') {
735
+ process.stdout.write(
736
+ ' re-read the default branch on those paths before opening or updating the PR,\n' +
737
+ ' then record what the re-read concluded:\n' +
738
+ ` node .claude/scripts/revalidate.mjs outcome --point ${args.point} --owner-directed --action-changed <true | false>\n`,
739
+ );
740
+ }
741
+ }
742
+ process.exit(result.action === 'hold' ? 2 : 0);
743
+ }
744
+
384
745
  const projectRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
385
746
  const configPath = args.config ?? join(projectRoot, '.claude', 'queue.json');
386
747
  // 🔴 The queue CONFIG, not the tracker behind it. `readAdapter` covers every
@@ -57,13 +57,66 @@ blockers.
57
57
  confidently-wrong reviews. Everything below is scoped to this diff.
58
58
 
59
59
  Then, on the fetched ref, ask whether the branch is still the branch the run
60
- took up:
60
+ took up. **Two paths, and you state which one — the command infers neither.**
61
+ A branch that is a queue item's take-up:
61
62
 
62
63
  ```sh
63
64
  node .claude/scripts/revalidate.mjs --point BEFORE_PR --ticket <item-id> --base origin/<default>
64
65
  ```
65
66
 
66
- It runs the existing revalidation chain against the tracked, versioned
67
+ Owner-directed work or a hotfix that has **no item** — the case step 4 below
68
+ already tells you to declare to the reviewers:
69
+
70
+ ```sh
71
+ node .claude/scripts/revalidate.mjs --point BEFORE_PR --owner-directed --base origin/<default>
72
+ ```
73
+
74
+ 🔴 **The second path is not a lighter checkpoint, and it is never a skip.**
75
+ It runs the same `main:<path>` comparison and holds on the same exit 2; what
76
+ it drops is the claim comparison, because work with no item has no claim to
77
+ compare. It resolves no queue config, so it reaches no tracker and needs no
78
+ credentials. Passing both flags, or neither, is exit 1.
79
+
80
+ **Four refusals** — exit 1, nothing journalled — keep it from being the way
81
+ around a claim or revalidation failure. It is refused when this run carries
82
+ an **unresolved revalidation hold** (what the ticketed path writes when it
83
+ holds or answers `UNVERIFIABLE`), when this run **declares a take-up**, when
84
+ the branch **touches a tracked `.rig/claims/*.json`** in any direction —
85
+ added, modified, removed or renamed — and at `BEFORE_CLOSE`. So re-running a
86
+ held ticketed call in this mode does not get past it: resolve the hold with
87
+ `outcome` instead.
88
+
89
+ ⚠ **What those refusals do not cover**, because a governance mode is trusted
90
+ exactly as far as it is described:
91
+
92
+ - **With no `RIG_RUN_DIR` there is no run state to read**, so the hold and
93
+ take-up refusals cannot fire — and nothing is journalled. The command says
94
+ so on stdout and in `evidence.runState`; it is not evidence that neither
95
+ exists. An attended gate run is exactly this shape, so read that line.
96
+ - The claim refusal reads the **branch diff**, so a claim record already on
97
+ the default branch, or written and not committed, is not seen.
98
+ - **`--base` is the sole authority for the verdict here**, the claim
99
+ comparison that would otherwise survive a wrong base being absent. Pass
100
+ the fetched `origin/<default>`, not a local copy and not `HEAD`.
101
+ - Nothing can prove an item does not exist. The rest is your word, recorded
102
+ as such, with `ticket: null` and no invented id.
103
+
104
+ Pinned in the generator's `test/template/owner-directed-revalidation.test.ts`
105
+ (absent in a generated rig) › "refuses when this run carries an unresolved
106
+ revalidation hold", › "refuses when the declared run already carries a
107
+ take-up", › "refuses when the branch RENAMES a claim record — the case
108
+ --diff-filter=AM could not see" and › "says out loud that an undeclared run
109
+ checked neither the hold nor the take-up".
110
+
111
+ **Exit 2 here is a HOLD with the same shape as the ticketed one**, and the
112
+ same two-step remedy: re-read the default branch on each named path, then
113
+ record what the re-read concluded — `node .claude/scripts/revalidate.mjs
114
+ outcome --point BEFORE_PR --owner-directed --action-changed <true | false>
115
+ --note '…'` — and come back through step 0. The owner-directed detection
116
+ carries no ticket, so `--owner-directed` is how the outcome addresses it;
117
+ the ticketed `--ticket <item-id>` form below cannot match it and is refused.
118
+
119
+ The ticketed path runs the existing revalidation chain against the tracked, versioned
67
120
  `.rig/claims/<item-id>.json`: the content-blind `scope` fingerprint set is
68
121
  authoritative here, while `takeUps` / `updatedAt` remain evidence only. It
69
122
  also names what the default branch changed since this branch forked on paths
@@ -71,14 +124,18 @@ blockers.
71
124
  journals one `revalidation` event at `point: BEFORE_PR`; **exit code 2 is a HOLD**, with one blocker per named source: re-read the item, or the default
72
125
  branch on that path, record what the re-read concluded —
73
126
  `node .claude/scripts/revalidate.mjs outcome --point BEFORE_PR --ticket <item-id> --action-changed <true | false> --note '…'`
74
- — and come back through step 0. A hold with no outcome is counted by the
75
- report as a re-read the run skipped. A missing, untracked, unreadable or
127
+ — and come back through step 0. **That `--ticket` form is this path's, not
128
+ both paths'** the owner-directed detection carries no ticket for it to
129
+ name, and its own `--owner-directed` outcome is written out above. A hold
130
+ with no outcome, in either mode, is counted by the report as a re-read the
131
+ run skipped. A missing, untracked, unreadable or
76
132
  unsupported claim is `UNVERIFIABLE`, exits 2, and stops automatic progress;
77
133
  so is a tracker whose adapter the command cannot READ, which means the
78
134
  question was never put rather than that the claim record is unreadable.
79
135
  Neither is ever read as a pass. Exit 1 is the command refusing (unknown
80
- point, no ticket, a base that is not a revision, or a queue config that does
81
- not resolve): fix the call or the config the message says which. Its limits are its own header's; the
136
+ point, neither mode or both, a base that is not a revision, or a queue config
137
+ that does not resolve plus, on the owner-directed path, the four refusals
138
+ above): fix the call or the config — the message says which. Its limits are its own header's; the
82
139
  cited-path set is a labelled assumption, not a recorded fact. Pinned in the
83
140
  generator's `test/template/revalidate.test.ts` (absent in a generated rig) ›
84
141
  "continues when only updatedAt moved and still reports the marker evidence"
@@ -182,9 +239,13 @@ blockers.
182
239
  **Whatever you launch, pass it the text of the queue item this branch
183
240
  implements.** A reviewer given only a diff cannot check the change against
184
241
  what was asked: a cold context has no way to know, and reconstructing it from
185
- the PR description would mean trusting the run under review. If there is no
186
- item — owner-directed work, a hotfix — say so when launching, and the
187
- reviewer skips that check openly instead of guessing at it.
242
+ the PR description would mean trusting the run under review. When there is
243
+ no item — owner-directed work, a hotfix — launch every reviewer with the
244
+ words **`no item owner-directed`** instead. That skips the item-contract
245
+ check, openly, and **nothing else**: the checks, the routing, the security,
246
+ code and prose/governance reviews, the coverage check and the DoD all still
247
+ run. It is one check narrower than a ticketed fan-out, not a cheaper gate,
248
+ and the same words are what step 1's `--owner-directed` call records.
188
249
 
189
250
  🔴 **The triggers below are lane-independent and may only ADD.** They read
190
251
  *what the code does*; the router reads *paths*, and a path cannot say that a
@@ -8,7 +8,8 @@
8
8
  "0.5.0",
9
9
  "0.6.0",
10
10
  "0.6.1",
11
- "0.6.2"
11
+ "0.6.2",
12
+ "0.7.0"
12
13
  ],
13
14
  "files": {
14
15
  ".agents/skills/check-premises/SKILL.md": {
@@ -24,7 +25,8 @@
24
25
  "eb52ef41269018107534d8dc2f552c57698e1677a1e76b5e666b5d09c007cac0",
25
26
  "47922b182915cb92696f851174f2e88c4ae8c67dc119ef39a3a597ef1300fab5",
26
27
  "57f6add0504db45e7577474f97705fe474310e4cd3310cdb348a2c64eabe88cd",
27
- "18f4300de4c4c7ea380d726fb109fa224da950ef0a4cea5015ab1d408b823be5"
28
+ "18f4300de4c4c7ea380d726fb109fa224da950ef0a4cea5015ab1d408b823be5",
29
+ "0a1b6575f6df8139766c8e71ba7320e14d2a5a5ed8ab26b6b43f7a006153d37d"
28
30
  ]
29
31
  },
30
32
  ".agents/skills/new-invariant/SKILL.md": {
@@ -56,7 +58,8 @@
56
58
  "since": "0.5.0",
57
59
  "hashes": [
58
60
  "a2fb0d03c65701af9d2d69cbcb2582ae573f097b1e1071eb2916e5e4f7832b37",
59
- "9616e4767942b2021e34ab7bb76eec9a5c280cc608a0756abb02743741d5fc8a"
61
+ "9616e4767942b2021e34ab7bb76eec9a5c280cc608a0756abb02743741d5fc8a",
62
+ "a6340b95a03000b89fb27f5357cdd9c7ec12b714054b5334b61a30da600405e8"
60
63
  ]
61
64
  },
62
65
  ".agents/skills/ro-debug/SKILL.md": {
@@ -117,7 +120,8 @@
117
120
  "hashes": [
118
121
  "c7c86de47b601dcb594ba8d8e23b5624ef8d47dafeddbc1aadd351c72e051d83",
119
122
  "ece5a81db41bd48695a1a009239837b682d247baf6d19214eaa859a7519ab246",
120
- "1646ed80de5268a886a2e861124cb3b871fa46df1857991a645667601f52253b"
123
+ "1646ed80de5268a886a2e861124cb3b871fa46df1857991a645667601f52253b",
124
+ "3d7bcbccd9a163f076bd1d8cc63021b7c8605d1cb1df24a3536db3e57c1d7630"
121
125
  ]
122
126
  },
123
127
  ".claude/hooks/dod-checks.json": {
@@ -143,7 +147,8 @@
143
147
  "4d396f412fc53094188b14348c5af4e79c7542a182630c20c4687d79899c5b0e",
144
148
  "e7a513216ebbceaa7f11ec8fb611de8bb5d5925dfdfd75b779dcb7c0ee6e83cc",
145
149
  "78047f42dda65076dd2cdb73ad530553db3d4a6f3ea7f0729590ac7286323cb1",
146
- "5782d91c6c54155ad8bf3e107b6574b3ad4c97baa7be673a5ed252c70b387300"
150
+ "5782d91c6c54155ad8bf3e107b6574b3ad4c97baa7be673a5ed252c70b387300",
151
+ "5ffaba69ffc9b6c39cd3c97c4f29b44627416d55793b50aa1a26ffe5efc45497"
147
152
  ]
148
153
  },
149
154
  ".claude/hooks/guard-core-purity.mjs": {
@@ -151,7 +156,8 @@
151
156
  "hashes": [
152
157
  "e36fbb92ee8395c6686fef582f29f2701747074b3b781bed42fe068f5c000bd7",
153
158
  "282faa364c2d152808cd92ff7e39da26fd0549a5b2d0af9711d3453be2c666eb",
154
- "4bdd9a38b1aa4050c109f7809ef924be4d411f5720b6030d57c5264ce3df556d"
159
+ "4bdd9a38b1aa4050c109f7809ef924be4d411f5720b6030d57c5264ce3df556d",
160
+ "15de3ae9a6007270f7b10d71ff9940160aadabd8f5b0995662fc324c483807e5"
155
161
  ]
156
162
  },
157
163
  ".claude/hooks/guard-rulebook.mjs": {
@@ -175,7 +181,8 @@
175
181
  "hashes": [
176
182
  "93d4fa0fd1c18261cc8ef2b1ae2abe3a0200cc4b5a7b1d1d4ae433ca92c514b1",
177
183
  "8deda7061122b69d72e708222ef2d38a971bb713269ec7fd1ca3440fe0a1150f",
178
- "df325e672793e06b8315e64b59a329775d110fc166176074ff3bb8dea557c561"
184
+ "df325e672793e06b8315e64b59a329775d110fc166176074ff3bb8dea557c561",
185
+ "60959520dbe326880d284c9e2b76a2a89f72b981ea6a2cb76e2a252b1233bdfc"
179
186
  ]
180
187
  },
181
188
  ".claude/hooks/inject-rules.mjs": {
@@ -197,7 +204,8 @@
197
204
  ".claude/hooks/lib/hook-input.mjs": {
198
205
  "since": "0.6.2",
199
206
  "hashes": [
200
- "c40320d90e9585a52026e074e399b6883a0dfed704115b28e8da4dea284cf85f"
207
+ "c40320d90e9585a52026e074e399b6883a0dfed704115b28e8da4dea284cf85f",
208
+ "3be3d4af4a25536d2a2bac0441a1370be7efafb5f8aa737b195bdce77db0091b"
201
209
  ]
202
210
  },
203
211
  ".claude/queue.json": {
@@ -237,14 +245,16 @@
237
245
  "518d9b3d7cbae50f30c86d33b2b4bbea11ab3ba6cfef270876e92604b2af5f50",
238
246
  "389484f0846c3dd8207c0f41bd56409103fbcc7f58b94b1bf4bd1d81b5af0e87",
239
247
  "a2e10499a0bcd2cdc32f3986d27b59077c86e524d40323faabb2e5d78afc002c",
240
- "c94c33d7ccbd46f191948325dfc7f68ce13b0d3a9470215de1de3f06b1499111"
248
+ "c94c33d7ccbd46f191948325dfc7f68ce13b0d3a9470215de1de3f06b1499111",
249
+ "ce90c1e697fdd444ca3fe2203d7991645f2c284c413ae187e4d6d6c618af52c9"
241
250
  ]
242
251
  },
243
252
  ".claude/rules/node-ts.md": {
244
253
  "since": "0.2.0",
245
254
  "hashes": [
246
255
  "3db68a4280cc30655be56fed9385179c378f560f3df55e21952a24066427e845",
247
- "be3b7bf901ef250b5957a37815b9a339083f7a8c4ecc866aeb12e3a0797c3c09"
256
+ "be3b7bf901ef250b5957a37815b9a339083f7a8c4ecc866aeb12e3a0797c3c09",
257
+ "b74979a3a1e57b69a6e1b3ee2d0cf9fc671d4487c411dc947e9ad0a47ac56dd1"
248
258
  ]
249
259
  },
250
260
  ".claude/rules/workflow.md": {
@@ -285,12 +295,24 @@
285
295
  "b022399f287cdce1bcb07329c0aab44bf99714b5f0515f274c962f41ea072b86"
286
296
  ]
287
297
  },
298
+ ".claude/scripts/lib/claim-records.mjs": {
299
+ "since": "0.7.0",
300
+ "hashes": [
301
+ "acb1d5702a826832977e7eb01b76c50cab19292528901f1b33693e561dbb0a24"
302
+ ]
303
+ },
288
304
  ".claude/scripts/lib/gate-coverage.mjs": {
289
305
  "since": "0.6.0",
290
306
  "hashes": [
291
307
  "1ef7f355071a747d8a8d9cd5e2072a19fe3d8c11a0e2a5f884d95a33fb95f12d"
292
308
  ]
293
309
  },
310
+ ".claude/scripts/lib/revalidation-evidence.mjs": {
311
+ "since": "0.7.0",
312
+ "hashes": [
313
+ "513369213374cf2c5415c7d5ea1f6d5c67300c77cec7af3e9190e710b156969f"
314
+ ]
315
+ },
294
316
  ".claude/scripts/lib/revalidation-points.mjs": {
295
317
  "since": "0.6.0",
296
318
  "hashes": [
@@ -305,6 +327,12 @@
305
327
  "60fab2cc9f2028bccf6139e6a744406b4a86a9039d1367f3cef409902e9b327d"
306
328
  ]
307
329
  },
330
+ ".claude/scripts/lib/shell-tools.mjs": {
331
+ "since": "0.7.0",
332
+ "hashes": [
333
+ "402428f4b2c4e507652d775e43b26c1e0a74aadb2cedec3156ac186af17d667a"
334
+ ]
335
+ },
308
336
  ".claude/scripts/lib/verdict.mjs": {
309
337
  "since": "0.5.0",
310
338
  "hashes": [
@@ -318,7 +346,8 @@
318
346
  "21083ff7baeebf8aedbdf85679a353f671276a9e0201a58a8235ae63f55f081c",
319
347
  "ceb6d35fa028f9c5e5e2d5965c627095d5ba80c37191a584627b4bc645b6be15",
320
348
  "efa193dc543237d82dcb371fa1df6483ccd43c1db88d0e218f5d89769564974c",
321
- "bed44df40412a7edac96914de21edc796df604ddc275ff1a02764dfcc4d4ede7"
349
+ "bed44df40412a7edac96914de21edc796df604ddc275ff1a02764dfcc4d4ede7",
350
+ "b3893c365de37ef6d9d04ab933296b9fcf09a0886197d7e12313fa63eacdbd89"
322
351
  ]
323
352
  },
324
353
  ".claude/scripts/queue/as-of.mjs": {
@@ -340,7 +369,8 @@
340
369
  "214f36e39f3809da5a121f404005d16a694280ca672ed10150b1b07d47912f6a",
341
370
  "d8a5916bae71efcc6a21978af713112c9ec33a7554fd07f5978bdf2c3633b560",
342
371
  "1804c2351a4f3042dc6a3fb4203f96e98ae12bec32569b99bda77d002d6ba5f2",
343
- "b81ae3181e02b9c1c3f0e954688490ab3db3961381c2dc397c47c651c9d07f88"
372
+ "b81ae3181e02b9c1c3f0e954688490ab3db3961381c2dc397c47c651c9d07f88",
373
+ "c9902da15d06de3e952352115db0288433520eae2caac1f70ee6e06753d1f444"
344
374
  ]
345
375
  },
346
376
  ".claude/scripts/queue/gate-rounds.mjs": {
@@ -355,7 +385,8 @@
355
385
  "f473f0ee5fc469f47b2b21ee601310944faa1abed21942e97ed251aba826cd5d",
356
386
  "86489ac7fcd06e43e31fb3d0ac9ab23174ef9c95196b08f127f1ae74da620c0c",
357
387
  "f84148fee47109a388a248c51d3eb40919ce341785b561dd4135b69c6fedabf3",
358
- "5ab8f741f17efb935b11caf245edf2b562cb887020c2a43d45464158342c933a"
388
+ "5ab8f741f17efb935b11caf245edf2b562cb887020c2a43d45464158342c933a",
389
+ "b5349837d16de0966139f5b65e2800486592f1b24d7e87087c2b7212cead479d"
359
390
  ]
360
391
  },
361
392
  ".claude/scripts/queue/index.mjs": {
@@ -364,7 +395,8 @@
364
395
  "cd149245dc29ef7489ae0e5394fa5a063dea2cc017bd3cbbface1ace6f8d7b46",
365
396
  "7db3c17b781e6876b4822afd09e6affa97e46607b63d443975d987daf1f14464",
366
397
  "9edd70b5960f367958e8e7500150b4edd285c99869cca803e40e0537d2d347b0",
367
- "4cf917ac06708e1068dfe9f8bbebc9376c4748526bd669b9bea32929ba996d0a"
398
+ "4cf917ac06708e1068dfe9f8bbebc9376c4748526bd669b9bea32929ba996d0a",
399
+ "b4a0789be7ad4b401de872edb06665bca349048178b4dd6c5db848ca44f7c977"
368
400
  ]
369
401
  },
370
402
  ".claude/scripts/queue/jira.mjs": {
@@ -375,7 +407,8 @@
375
407
  "13c301f828d4402fcf81e7e0b2b1d22472cbdb50f6fd606770056af4ec215e51",
376
408
  "b8be5855809c0d44ece94858456098663296c17c5ad9abb1ea2cf1a8f48ca491",
377
409
  "3fc8ff9a06a2e22355a660a31e2943a97f50869f7e5726f768a7931d17b64b5b",
378
- "f44203af187e855b9f738f8dbedfd313abf9d6a5cc29182fb46cd58d70698d7a"
410
+ "f44203af187e855b9f738f8dbedfd313abf9d6a5cc29182fb46cd58d70698d7a",
411
+ "c2f2af8ac5eadd8d35a15bcdeffe585e32c6f99b4e6ab9ace0f9d7fd9d694943"
379
412
  ]
380
413
  },
381
414
  ".claude/scripts/queue/plan-md.mjs": {
@@ -384,7 +417,8 @@
384
417
  "d137dad23f263b1a12b04b46625247fea648ded36d0fea7c5b4e4f2211a142eb",
385
418
  "07eab69bd86b19b7a1f01eb14efe4f8c007481806cbe029d4014fe9bf0703000",
386
419
  "27d7ae3832a32219d5c0ab136bc542388eb686e9f34053c3058104cdeea1574d",
387
- "1320909605824b2cd1ff57878e463ef360463b2b175d2152638eda2dff72f5ea"
420
+ "1320909605824b2cd1ff57878e463ef360463b2b175d2152638eda2dff72f5ea",
421
+ "816aec4cb84492370972f71813a9fec7c0309e5f501951a93620a37cb318ac39"
388
422
  ]
389
423
  },
390
424
  ".claude/scripts/queue/state.mjs": {
@@ -403,14 +437,16 @@
403
437
  "since": "0.6.0",
404
438
  "hashes": [
405
439
  "32f847a2a3a5136cabcb42f6447da9f8cf98e2d20512212ee5201461128d7798",
406
- "b62348a27bcaa1d62a0e4ddd53873fab6cb75f9af8e8c69d24f6ce81c592c4e3"
440
+ "b62348a27bcaa1d62a0e4ddd53873fab6cb75f9af8e8c69d24f6ce81c592c4e3",
441
+ "b151045e8083c398e94571b35dc9568dfa0102abcb847cdf764f6f694914d53e"
407
442
  ]
408
443
  },
409
444
  ".claude/scripts/revalidation-report.mjs": {
410
445
  "since": "0.6.0",
411
446
  "hashes": [
412
447
  "b99a3537158c2851698bfac027f7061811d7d428c3eea57f3ce0f58ea75dc249",
413
- "78adac5c8f696f2c9cc86f404abf1d55604ccb10f11dd612501c14d4cf5935b1"
448
+ "78adac5c8f696f2c9cc86f404abf1d55604ccb10f11dd612501c14d4cf5935b1",
449
+ "fed627d2c8f0f5e6a4524b7015ac7a1cb5051a8c316ff4544af18be9c61eb163"
414
450
  ]
415
451
  },
416
452
  ".claude/scripts/run-journal.mjs": {
@@ -423,7 +459,8 @@
423
459
  "since": "0.5.0",
424
460
  "hashes": [
425
461
  "5682b4ef1e63b83e1398da4959d8b34f52aede38f896557c041687d101df015f",
426
- "82c83fe9a66ff1a48c9e23fdfe84eef7da0de337c420e8d870085e7078d6f5e2"
462
+ "82c83fe9a66ff1a48c9e23fdfe84eef7da0de337c420e8d870085e7078d6f5e2",
463
+ "c502cefafdc720d178de685e8372d2c140cb2663517c9d88b5345a9f0c50d0de"
427
464
  ]
428
465
  },
429
466
  ".claude/scripts/stop-flag.mjs": {
@@ -454,7 +491,8 @@
454
491
  "48212660b2823725c2c9ee785ad739701e4c0e4733b275201a544df1abba28d0",
455
492
  "55a6938b4d9967291a26e93301e34e5ffdef74a88f8c773a4d11b5d87d864fff",
456
493
  "713718fbb2cda132f05665b29b8f1108a6305729b5e7c789d2053d117c13e08e",
457
- "e588ab05bee707a099c80c9ea399008be31488e616f3db3c0598f66f3d1b3a52"
494
+ "e588ab05bee707a099c80c9ea399008be31488e616f3db3c0598f66f3d1b3a52",
495
+ "a180578d6e6de6a21900ff2f8a8ade2242170d16d2dd95574e5004435d870b9b"
458
496
  ]
459
497
  },
460
498
  ".claude/skills/check-premises/SKILL.md": {
@@ -475,7 +513,8 @@
475
513
  "eb52ef41269018107534d8dc2f552c57698e1677a1e76b5e666b5d09c007cac0",
476
514
  "47922b182915cb92696f851174f2e88c4ae8c67dc119ef39a3a597ef1300fab5",
477
515
  "57f6add0504db45e7577474f97705fe474310e4cd3310cdb348a2c64eabe88cd",
478
- "18f4300de4c4c7ea380d726fb109fa224da950ef0a4cea5015ab1d408b823be5"
516
+ "18f4300de4c4c7ea380d726fb109fa224da950ef0a4cea5015ab1d408b823be5",
517
+ "0a1b6575f6df8139766c8e71ba7320e14d2a5a5ed8ab26b6b43f7a006153d37d"
479
518
  ]
480
519
  },
481
520
  ".claude/skills/new-invariant/SKILL.md": {
@@ -510,7 +549,8 @@
510
549
  "368a50be809f7be865d6d29ab3e54b9d69acd2ea3ba7929c0edeef161c2521b7",
511
550
  "75adb84414a330db621ff4f1363b3926f537133199236a9e25bb9880b935e02e",
512
551
  "a2fb0d03c65701af9d2d69cbcb2582ae573f097b1e1071eb2916e5e4f7832b37",
513
- "9616e4767942b2021e34ab7bb76eec9a5c280cc608a0756abb02743741d5fc8a"
552
+ "9616e4767942b2021e34ab7bb76eec9a5c280cc608a0756abb02743741d5fc8a",
553
+ "a6340b95a03000b89fb27f5357cdd9c7ec12b714054b5334b61a30da600405e8"
514
554
  ]
515
555
  },
516
556
  ".claude/skills/ro-debug/SKILL.md": {
@@ -565,7 +605,14 @@
565
605
  "hashes": [
566
606
  "c676647d585ec87427545a391598706ecaa74dfe413a7e05877e336bf8942874",
567
607
  "89d999c068ef5b6e48913c15a165359e66b1f505b9f97c145d8c730cbf42d09a",
568
- "1fc2fc35b04fa4ad0859841af72f45695bc346d1a1a9b20ba5d971d48888a910"
608
+ "1fc2fc35b04fa4ad0859841af72f45695bc346d1a1a9b20ba5d971d48888a910",
609
+ "fa9a34eb9f889910e55269b76172608b989d553583de38af34316a31f4ad570b"
610
+ ]
611
+ },
612
+ ".rig/revalidation.json": {
613
+ "since": "0.7.0",
614
+ "hashes": [
615
+ "312b59f80fcbcf2897ef80fb54be236fb6b2b1745f705dae8e66c58f0bbd3099"
569
616
  ]
570
617
  },
571
618
  "AGENTS.md": {
@@ -603,7 +650,14 @@
603
650
  "docs/decisions/codex-adapter.md": {
604
651
  "since": "0.5.0",
605
652
  "hashes": [
606
- "30e55d866d9a2c7359e594181070c63b1974d9df36e000caf419229284303a2e"
653
+ "30e55d866d9a2c7359e594181070c63b1974d9df36e000caf419229284303a2e",
654
+ "ebf0ef61407944373c862d37e6a80d4965ece72304b931428c6b3fcc5d9527c4"
655
+ ]
656
+ },
657
+ "docs/decisions/content-blind-revalidation.md": {
658
+ "since": "0.7.0",
659
+ "hashes": [
660
+ "d640a152428ea4cc6a5f0a19be58e0d23f3a99c44b0ad0b72152ec7b48fe5bfd"
607
661
  ]
608
662
  },
609
663
  "docs/decisions/fail-open-guards.md": {
@@ -8,5 +8,6 @@
8
8
  "0.5.0": "d78d0e77307a2be128de46ff4f64e3c146b5df2b",
9
9
  "0.6.0": "29ca0543368e922349945e52541a92b46ddcc5bd",
10
10
  "0.6.1": "f1d1e3dbd2161545d77ee5e7f90fa8e74b8a9f3e",
11
- "0.6.2": "2a1fc8e10cd2b65deb5c95d937fd7de39f2c92a2"
11
+ "0.6.2": "2a1fc8e10cd2b65deb5c95d937fd7de39f2c92a2",
12
+ "0.7.0": "6589db36e1daa63a99ec595191db1cccf7373196"
12
13
  }