@codyswann/lisa 4.53.1 → 4.53.2
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/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +4 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/withdrawn-rulings.mjs +76 -1
- package/plugins/lisa/rules/eager/withdrawn-rulings.md +22 -0
- package/plugins/lisa/rules/reference/withdrawn-rulings.md +34 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/withdrawn-rulings.mjs +76 -1
- package/plugins/lisa-copilot/rules/eager/withdrawn-rulings.md +22 -0
- package/plugins/lisa-copilot/rules/reference/withdrawn-rulings.md +34 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/withdrawn-rulings.mjs +76 -1
- package/plugins/lisa-cursor/rules/withdrawn-rulings-reference.mdc +34 -0
- package/plugins/lisa-cursor/rules/withdrawn-rulings.mdc +22 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/withdrawn-rulings.mjs +76 -1
- package/plugins/src/base/rules/eager/withdrawn-rulings.md +22 -0
- package/plugins/src/base/rules/reference/withdrawn-rulings.md +34 -0
package/package.json
CHANGED
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"zod-validation-error": "^4.0.0"
|
|
182
182
|
},
|
|
183
183
|
"name": "@codyswann/lisa",
|
|
184
|
-
"version": "4.53.
|
|
184
|
+
"version": "4.53.2",
|
|
185
185
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
186
186
|
"main": "dist/index.js",
|
|
187
187
|
"exports": {
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
"test": "tests"
|
|
330
330
|
},
|
|
331
331
|
"types": "./dist/index.d.ts",
|
|
332
|
-
"lisaReleaseCommit": "
|
|
333
|
-
"gitHead": "
|
|
334
|
-
"lisaReleaseTag": "v4.53.
|
|
332
|
+
"lisaReleaseCommit": "62bbaf063f9fcea7a9b443c4b8beddf4a9d5db18",
|
|
333
|
+
"gitHead": "62bbaf063f9fcea7a9b443c4b8beddf4a9d5db18",
|
|
334
|
+
"lisaReleaseTag": "v4.53.2"
|
|
335
335
|
}
|
|
@@ -62,9 +62,35 @@
|
|
|
62
62
|
* chore produced mid-task, after being corrected, when attention has moved on.
|
|
63
63
|
* A rule of the form "always record retractions durably" is a discipline, and
|
|
64
64
|
* this repository's own measurement is that prose rules land at roughly zero
|
|
65
|
-
* adoption. `--withdraw` is therefore one command with
|
|
65
|
+
* adoption. `--withdraw` is therefore one command with four required fields,
|
|
66
66
|
* and it refuses without the verbatim claim: a tombstone the holder of the
|
|
67
67
|
* claim cannot recognise reaches them without reaching them.
|
|
68
|
+
*
|
|
69
|
+
* ## Why the re-audit is a required FIELD and not a reminder
|
|
70
|
+
*
|
|
71
|
+
* Correcting a premise removes the belief and leaves every inference standing
|
|
72
|
+
* (CodySwannGT/lisa#3489). The observed instance: a session disproved a false
|
|
73
|
+
* claim that a credential CLI was unavailable, updated the fact, and two hours
|
|
74
|
+
* later still relayed to its human that a work item was blocked for a reason
|
|
75
|
+
* that rested on the retracted claim. Three facts sat in one context and were
|
|
76
|
+
* never joined. The retraction was recorded; the artifacts it invalidated were
|
|
77
|
+
* not revisited.
|
|
78
|
+
*
|
|
79
|
+
* This file already carried the obligation as prose — "if you already relayed
|
|
80
|
+
* or recorded one, correct it on the same surface" — which is the discipline
|
|
81
|
+
* shape this module exists to distrust. So `--derived` is required, and
|
|
82
|
+
* `--derived none` is the explicit empty answer. The obligation is to PRODUCE
|
|
83
|
+
* the list, not to find something wrong in it: an empty answer is fine, an
|
|
84
|
+
* absent one is not, and the difference between them is the whole control.
|
|
85
|
+
*
|
|
86
|
+
* It has to bind here because there is nowhere else it could. A scanner that
|
|
87
|
+
* went looking for artifacts resting on a retracted premise would find nothing
|
|
88
|
+
* to read: outside the learnings ledger's `provenance` refs, no artifact Lisa
|
|
89
|
+
* writes records the premise it rests on. A tracker item marked blocked, a
|
|
90
|
+
* statement relayed to a human, a message to a peer session — none carry a
|
|
91
|
+
* back-pointer. The retracting session is the only party that still holds the
|
|
92
|
+
* derivation, and the moment it runs this command is the only moment that
|
|
93
|
+
* knowledge is both present and being written down.
|
|
68
94
|
* @module withdrawn-rulings
|
|
69
95
|
*/
|
|
70
96
|
import * as fs from "node:fs";
|
|
@@ -220,6 +246,15 @@ export function formatNotice(entries, headline) {
|
|
|
220
246
|
lines.push(` superseded by: ${entry.supersededBy}`);
|
|
221
247
|
if (Array.isArray(entry.reached) && entry.reached.length > 0)
|
|
222
248
|
lines.push(` originally reached: ${entry.reached.join(", ")}`);
|
|
249
|
+
// Printed even when empty, and that is the useful case: it tells a reader
|
|
250
|
+
// the withdrawer looked, so nobody repeats the audit and nobody assumes it
|
|
251
|
+
// happened. An entry with no `derived` key at all says neither.
|
|
252
|
+
if (Array.isArray(entry.derived))
|
|
253
|
+
lines.push(
|
|
254
|
+
entry.derived.length > 0
|
|
255
|
+
? ` still standing on it: ${entry.derived.join(", ")}`
|
|
256
|
+
: ` still standing on it: nothing (audited by the withdrawer)`
|
|
257
|
+
);
|
|
223
258
|
return lines.join("\n");
|
|
224
259
|
});
|
|
225
260
|
return [
|
|
@@ -362,6 +397,24 @@ export function normalizeSuperseded(value) {
|
|
|
362
397
|
return trimmed === "" || trimmed.toLowerCase() === "none" ? null : trimmed;
|
|
363
398
|
}
|
|
364
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Normalise `--derived` values into the re-audit list.
|
|
402
|
+
*
|
|
403
|
+
* `none` is a real answer and must survive as one. A withdrawer who checked and
|
|
404
|
+
* found nothing standing has discharged the obligation exactly as fully as one
|
|
405
|
+
* who lists five artifacts, and the record has to be able to say so — otherwise
|
|
406
|
+
* the only way to satisfy the field is to invent an entry, which is worse than
|
|
407
|
+
* the gap it closes. Absent is refused elsewhere; `none` resolves here to an
|
|
408
|
+
* empty list, which reads back as "audited, nothing survives".
|
|
409
|
+
* @param {readonly string[]} values Raw `--derived` values, in order given.
|
|
410
|
+
* @returns {string[]} The artifacts still standing, possibly none.
|
|
411
|
+
*/
|
|
412
|
+
export function normalizeDerived(values) {
|
|
413
|
+
return values
|
|
414
|
+
.map(value => value.trim())
|
|
415
|
+
.filter(value => value !== "" && value.toLowerCase() !== "none");
|
|
416
|
+
}
|
|
417
|
+
|
|
365
418
|
/**
|
|
366
419
|
* Build the record `--withdraw` appends, refusing an unrecognisable tombstone.
|
|
367
420
|
* @param {readonly string[]} argv Arguments.
|
|
@@ -380,12 +433,25 @@ export function buildWithdrawal(argv, now) {
|
|
|
380
433
|
};
|
|
381
434
|
if (because === "")
|
|
382
435
|
return { error: "--because is required: say what disproved it" };
|
|
436
|
+
const derived = flagValues(argv, "--derived");
|
|
437
|
+
if (derived.length === 0)
|
|
438
|
+
return {
|
|
439
|
+
error:
|
|
440
|
+
"--derived is required: name what you asserted, filed or relayed while you " +
|
|
441
|
+
"believed this, so the retraction reaches those artifacts too. Correcting a " +
|
|
442
|
+
"premise leaves every inference standing. Pass `--derived none` if you checked " +
|
|
443
|
+
"and nothing survives — an empty answer is fine, an absent one is not.",
|
|
444
|
+
};
|
|
383
445
|
return {
|
|
384
446
|
record: {
|
|
385
447
|
id,
|
|
386
448
|
withdrawnAt: now,
|
|
387
449
|
claim,
|
|
388
450
|
because,
|
|
451
|
+
// Present even when empty: the difference between "audited, nothing
|
|
452
|
+
// survives" and "never audited" is the whole point of the field, and an
|
|
453
|
+
// omitted key cannot express the first.
|
|
454
|
+
derived: normalizeDerived(derived),
|
|
389
455
|
// "none" is what a person types when nothing replaced the claim, and a
|
|
390
456
|
// string "none" in the record would print as a superseding id that does
|
|
391
457
|
// not exist. Normalised here rather than at every reader.
|
|
@@ -452,6 +518,15 @@ export function runCheck(root) {
|
|
|
452
518
|
for (const entry of entries) {
|
|
453
519
|
if (typeof entry.claim !== "string" || entry.claim === "")
|
|
454
520
|
problems.push(`${entry.id}: no verbatim claim recorded`);
|
|
521
|
+
// An absent `derived` is a tombstone whose writer never said what rested on
|
|
522
|
+
// the claim. That is the CodySwannGT/lisa#3489 failure written into the
|
|
523
|
+
// ledger, and letting it through here would make the write-time refusal a
|
|
524
|
+
// formality anything could route around by editing the file.
|
|
525
|
+
if (!Array.isArray(entry.derived))
|
|
526
|
+
problems.push(
|
|
527
|
+
`${entry.id}: no re-audit recorded — say what was asserted, filed or relayed ` +
|
|
528
|
+
`while the claim was believed, or record an empty list to say nothing survives`
|
|
529
|
+
);
|
|
455
530
|
if (
|
|
456
531
|
typeof entry.withdrawnAt !== "string" ||
|
|
457
532
|
Number.isNaN(Date.parse(entry.withdrawnAt))
|
|
@@ -29,12 +29,34 @@ The result is one-directional propagation. **New guidance travels; corrections d
|
|
|
29
29
|
node "$CLAUDE_PLUGIN_ROOT/hooks/withdrawn-rulings.mjs" --withdraw <id> \
|
|
30
30
|
--claim "<the withdrawn claim, verbatim>" \
|
|
31
31
|
--because "<what disproved it>" \
|
|
32
|
+
--derived "<an artifact you left standing on it>" ... | --derived none \
|
|
32
33
|
[--superseded-by <id>] [--reached <channel> ...]
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
The verbatim claim is required and the command refuses without it: **a tombstone the holder of the
|
|
36
37
|
claim cannot recognise reaches them without reaching them.**
|
|
37
38
|
|
|
39
|
+
- **`--derived` is required too, because correcting a premise leaves every inference standing.**
|
|
40
|
+
Updating the fact is the easy half. The conclusions you already drew from it are the half that
|
|
41
|
+
reached other agents and the human, and they do not retract themselves. Before you can record the
|
|
42
|
+
withdrawal you must name what you asserted, filed, or relayed while you believed the claim —
|
|
43
|
+
tracker state you set, a statement you made to your operator, a message you sent a peer session.
|
|
44
|
+
|
|
45
|
+
**The obligation is to produce the list, not to find something wrong in it.** `--derived none` is
|
|
46
|
+
a complete answer when you checked and nothing survives, and the record keeps it as an empty list
|
|
47
|
+
so a later reader can tell "audited, nothing standing" from "never audited". An absent answer is
|
|
48
|
+
refused; an empty one is not.
|
|
49
|
+
|
|
50
|
+
Measured instance: a session disproved a false claim that a credential CLI was unavailable,
|
|
51
|
+
updated the fact, and **two hours later still relayed to its human that a work item was blocked**
|
|
52
|
+
for a reason resting on the retracted claim. Three facts sat in one context and were never joined.
|
|
53
|
+
|
|
54
|
+
It binds here because nowhere else can carry it. Outside the learnings ledger's `provenance` refs,
|
|
55
|
+
**no artifact Lisa writes records the premise it rests on** — a blocked label, a relayed sentence,
|
|
56
|
+
a peer message carry no back-pointer. Nothing can go looking afterwards for what rested on a
|
|
57
|
+
retracted claim. You are the only party still holding that, at the only moment it is being
|
|
58
|
+
written down.
|
|
59
|
+
|
|
38
60
|
- **The ledger is the retraction path, precisely because it is cheap.** Writing a finding feels
|
|
39
61
|
productive; writing a retraction is a chore produced mid-task, after being corrected, when
|
|
40
62
|
attention has moved on. A rule saying "always record retractions durably" is a discipline, and
|
|
@@ -75,6 +75,40 @@ JSONL rather than prose: each line is independent, so concurrent appends from ma
|
|
|
75
75
|
collide and `merge=union` reconstructs the union exactly. A prose ledger would need a bespoke merge
|
|
76
76
|
driver to survive the same traffic.
|
|
77
77
|
|
|
78
|
+
## The re-audit field, and why it is required (CodySwannGT/lisa#3489)
|
|
79
|
+
|
|
80
|
+
A tombstone answers "this claim is dead". It does not answer **"what did I build while it was
|
|
81
|
+
alive?"** — and that is the half that reached other agents and the human. Correcting a premise
|
|
82
|
+
removes the belief and leaves every inference standing.
|
|
83
|
+
|
|
84
|
+
The observed instance: a session spent part of a day believing a readiness preflight's false claim
|
|
85
|
+
that a credential CLI was unavailable. A delegate probed the binary and disproved it, the session
|
|
86
|
+
updated the fact, and carried on. It never asked what it had concluded while believing the false
|
|
87
|
+
thing. Two hours later it relayed to its human that a work item was blocked because an SSO session
|
|
88
|
+
had expired and a deployed template could not be read — relayed unexamined, while already knowing
|
|
89
|
+
the vault worked, and while its own config had listed the relevant bootstrap secret the whole time.
|
|
90
|
+
Three facts sat in one context and were never joined.
|
|
91
|
+
|
|
92
|
+
So `--derived` is a required field on every withdrawal, and `--derived none` is the explicit empty
|
|
93
|
+
answer. The record always carries the key: an empty list means *audited, nothing survives*, and an
|
|
94
|
+
absent key means *never audited*. Distinguishing those two is the entire purpose, which is why the
|
|
95
|
+
push gate refuses a ledger entry that omits it — otherwise the write-time refusal would be a
|
|
96
|
+
formality anyone could route around by editing the file.
|
|
97
|
+
|
|
98
|
+
**Why it cannot be a scanner instead.** Re-auditing after the fact would require asking "which
|
|
99
|
+
artifacts rest on premise P". Nothing can answer that. Learnings-ledger entries carry `provenance`
|
|
100
|
+
refs and are genuinely queryable that way, but they are the only artifact class that is: a tracker
|
|
101
|
+
item marked blocked, a sentence relayed to an operator, a message to a peer session, an evidence
|
|
102
|
+
file — none record the premise they rest on. **Re-auditing those is not merely unenforced; for
|
|
103
|
+
anyone but the session still holding the context, it is impossible.** The obligation therefore has
|
|
104
|
+
to bind at the one moment that knowledge is both present and being written down.
|
|
105
|
+
|
|
106
|
+
Note the division of labour with `--reached`, which is a different question. `--reached` records the
|
|
107
|
+
**audience** — who received the claim, which is #3752's problem of delivery. `--derived` records the
|
|
108
|
+
**derivation** — what now stands on it, which is #3489's problem. A tombstone can reach every holder
|
|
109
|
+
and still leave a wrong tracker state behind, and it can enumerate its inferences while reaching
|
|
110
|
+
nobody. Both fields, or neither half is covered.
|
|
111
|
+
|
|
78
112
|
## How a running session is reached
|
|
79
113
|
|
|
80
114
|
`--session-start` stamps a per-session mark recording the ids the session was born already knowing.
|
|
@@ -62,9 +62,35 @@
|
|
|
62
62
|
* chore produced mid-task, after being corrected, when attention has moved on.
|
|
63
63
|
* A rule of the form "always record retractions durably" is a discipline, and
|
|
64
64
|
* this repository's own measurement is that prose rules land at roughly zero
|
|
65
|
-
* adoption. `--withdraw` is therefore one command with
|
|
65
|
+
* adoption. `--withdraw` is therefore one command with four required fields,
|
|
66
66
|
* and it refuses without the verbatim claim: a tombstone the holder of the
|
|
67
67
|
* claim cannot recognise reaches them without reaching them.
|
|
68
|
+
*
|
|
69
|
+
* ## Why the re-audit is a required FIELD and not a reminder
|
|
70
|
+
*
|
|
71
|
+
* Correcting a premise removes the belief and leaves every inference standing
|
|
72
|
+
* (CodySwannGT/lisa#3489). The observed instance: a session disproved a false
|
|
73
|
+
* claim that a credential CLI was unavailable, updated the fact, and two hours
|
|
74
|
+
* later still relayed to its human that a work item was blocked for a reason
|
|
75
|
+
* that rested on the retracted claim. Three facts sat in one context and were
|
|
76
|
+
* never joined. The retraction was recorded; the artifacts it invalidated were
|
|
77
|
+
* not revisited.
|
|
78
|
+
*
|
|
79
|
+
* This file already carried the obligation as prose — "if you already relayed
|
|
80
|
+
* or recorded one, correct it on the same surface" — which is the discipline
|
|
81
|
+
* shape this module exists to distrust. So `--derived` is required, and
|
|
82
|
+
* `--derived none` is the explicit empty answer. The obligation is to PRODUCE
|
|
83
|
+
* the list, not to find something wrong in it: an empty answer is fine, an
|
|
84
|
+
* absent one is not, and the difference between them is the whole control.
|
|
85
|
+
*
|
|
86
|
+
* It has to bind here because there is nowhere else it could. A scanner that
|
|
87
|
+
* went looking for artifacts resting on a retracted premise would find nothing
|
|
88
|
+
* to read: outside the learnings ledger's `provenance` refs, no artifact Lisa
|
|
89
|
+
* writes records the premise it rests on. A tracker item marked blocked, a
|
|
90
|
+
* statement relayed to a human, a message to a peer session — none carry a
|
|
91
|
+
* back-pointer. The retracting session is the only party that still holds the
|
|
92
|
+
* derivation, and the moment it runs this command is the only moment that
|
|
93
|
+
* knowledge is both present and being written down.
|
|
68
94
|
* @module withdrawn-rulings
|
|
69
95
|
*/
|
|
70
96
|
import * as fs from "node:fs";
|
|
@@ -220,6 +246,15 @@ export function formatNotice(entries, headline) {
|
|
|
220
246
|
lines.push(` superseded by: ${entry.supersededBy}`);
|
|
221
247
|
if (Array.isArray(entry.reached) && entry.reached.length > 0)
|
|
222
248
|
lines.push(` originally reached: ${entry.reached.join(", ")}`);
|
|
249
|
+
// Printed even when empty, and that is the useful case: it tells a reader
|
|
250
|
+
// the withdrawer looked, so nobody repeats the audit and nobody assumes it
|
|
251
|
+
// happened. An entry with no `derived` key at all says neither.
|
|
252
|
+
if (Array.isArray(entry.derived))
|
|
253
|
+
lines.push(
|
|
254
|
+
entry.derived.length > 0
|
|
255
|
+
? ` still standing on it: ${entry.derived.join(", ")}`
|
|
256
|
+
: ` still standing on it: nothing (audited by the withdrawer)`
|
|
257
|
+
);
|
|
223
258
|
return lines.join("\n");
|
|
224
259
|
});
|
|
225
260
|
return [
|
|
@@ -362,6 +397,24 @@ export function normalizeSuperseded(value) {
|
|
|
362
397
|
return trimmed === "" || trimmed.toLowerCase() === "none" ? null : trimmed;
|
|
363
398
|
}
|
|
364
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Normalise `--derived` values into the re-audit list.
|
|
402
|
+
*
|
|
403
|
+
* `none` is a real answer and must survive as one. A withdrawer who checked and
|
|
404
|
+
* found nothing standing has discharged the obligation exactly as fully as one
|
|
405
|
+
* who lists five artifacts, and the record has to be able to say so — otherwise
|
|
406
|
+
* the only way to satisfy the field is to invent an entry, which is worse than
|
|
407
|
+
* the gap it closes. Absent is refused elsewhere; `none` resolves here to an
|
|
408
|
+
* empty list, which reads back as "audited, nothing survives".
|
|
409
|
+
* @param {readonly string[]} values Raw `--derived` values, in order given.
|
|
410
|
+
* @returns {string[]} The artifacts still standing, possibly none.
|
|
411
|
+
*/
|
|
412
|
+
export function normalizeDerived(values) {
|
|
413
|
+
return values
|
|
414
|
+
.map(value => value.trim())
|
|
415
|
+
.filter(value => value !== "" && value.toLowerCase() !== "none");
|
|
416
|
+
}
|
|
417
|
+
|
|
365
418
|
/**
|
|
366
419
|
* Build the record `--withdraw` appends, refusing an unrecognisable tombstone.
|
|
367
420
|
* @param {readonly string[]} argv Arguments.
|
|
@@ -380,12 +433,25 @@ export function buildWithdrawal(argv, now) {
|
|
|
380
433
|
};
|
|
381
434
|
if (because === "")
|
|
382
435
|
return { error: "--because is required: say what disproved it" };
|
|
436
|
+
const derived = flagValues(argv, "--derived");
|
|
437
|
+
if (derived.length === 0)
|
|
438
|
+
return {
|
|
439
|
+
error:
|
|
440
|
+
"--derived is required: name what you asserted, filed or relayed while you " +
|
|
441
|
+
"believed this, so the retraction reaches those artifacts too. Correcting a " +
|
|
442
|
+
"premise leaves every inference standing. Pass `--derived none` if you checked " +
|
|
443
|
+
"and nothing survives — an empty answer is fine, an absent one is not.",
|
|
444
|
+
};
|
|
383
445
|
return {
|
|
384
446
|
record: {
|
|
385
447
|
id,
|
|
386
448
|
withdrawnAt: now,
|
|
387
449
|
claim,
|
|
388
450
|
because,
|
|
451
|
+
// Present even when empty: the difference between "audited, nothing
|
|
452
|
+
// survives" and "never audited" is the whole point of the field, and an
|
|
453
|
+
// omitted key cannot express the first.
|
|
454
|
+
derived: normalizeDerived(derived),
|
|
389
455
|
// "none" is what a person types when nothing replaced the claim, and a
|
|
390
456
|
// string "none" in the record would print as a superseding id that does
|
|
391
457
|
// not exist. Normalised here rather than at every reader.
|
|
@@ -452,6 +518,15 @@ export function runCheck(root) {
|
|
|
452
518
|
for (const entry of entries) {
|
|
453
519
|
if (typeof entry.claim !== "string" || entry.claim === "")
|
|
454
520
|
problems.push(`${entry.id}: no verbatim claim recorded`);
|
|
521
|
+
// An absent `derived` is a tombstone whose writer never said what rested on
|
|
522
|
+
// the claim. That is the CodySwannGT/lisa#3489 failure written into the
|
|
523
|
+
// ledger, and letting it through here would make the write-time refusal a
|
|
524
|
+
// formality anything could route around by editing the file.
|
|
525
|
+
if (!Array.isArray(entry.derived))
|
|
526
|
+
problems.push(
|
|
527
|
+
`${entry.id}: no re-audit recorded — say what was asserted, filed or relayed ` +
|
|
528
|
+
`while the claim was believed, or record an empty list to say nothing survives`
|
|
529
|
+
);
|
|
455
530
|
if (
|
|
456
531
|
typeof entry.withdrawnAt !== "string" ||
|
|
457
532
|
Number.isNaN(Date.parse(entry.withdrawnAt))
|
|
@@ -29,12 +29,34 @@ The result is one-directional propagation. **New guidance travels; corrections d
|
|
|
29
29
|
node "$CLAUDE_PLUGIN_ROOT/hooks/withdrawn-rulings.mjs" --withdraw <id> \
|
|
30
30
|
--claim "<the withdrawn claim, verbatim>" \
|
|
31
31
|
--because "<what disproved it>" \
|
|
32
|
+
--derived "<an artifact you left standing on it>" ... | --derived none \
|
|
32
33
|
[--superseded-by <id>] [--reached <channel> ...]
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
The verbatim claim is required and the command refuses without it: **a tombstone the holder of the
|
|
36
37
|
claim cannot recognise reaches them without reaching them.**
|
|
37
38
|
|
|
39
|
+
- **`--derived` is required too, because correcting a premise leaves every inference standing.**
|
|
40
|
+
Updating the fact is the easy half. The conclusions you already drew from it are the half that
|
|
41
|
+
reached other agents and the human, and they do not retract themselves. Before you can record the
|
|
42
|
+
withdrawal you must name what you asserted, filed, or relayed while you believed the claim —
|
|
43
|
+
tracker state you set, a statement you made to your operator, a message you sent a peer session.
|
|
44
|
+
|
|
45
|
+
**The obligation is to produce the list, not to find something wrong in it.** `--derived none` is
|
|
46
|
+
a complete answer when you checked and nothing survives, and the record keeps it as an empty list
|
|
47
|
+
so a later reader can tell "audited, nothing standing" from "never audited". An absent answer is
|
|
48
|
+
refused; an empty one is not.
|
|
49
|
+
|
|
50
|
+
Measured instance: a session disproved a false claim that a credential CLI was unavailable,
|
|
51
|
+
updated the fact, and **two hours later still relayed to its human that a work item was blocked**
|
|
52
|
+
for a reason resting on the retracted claim. Three facts sat in one context and were never joined.
|
|
53
|
+
|
|
54
|
+
It binds here because nowhere else can carry it. Outside the learnings ledger's `provenance` refs,
|
|
55
|
+
**no artifact Lisa writes records the premise it rests on** — a blocked label, a relayed sentence,
|
|
56
|
+
a peer message carry no back-pointer. Nothing can go looking afterwards for what rested on a
|
|
57
|
+
retracted claim. You are the only party still holding that, at the only moment it is being
|
|
58
|
+
written down.
|
|
59
|
+
|
|
38
60
|
- **The ledger is the retraction path, precisely because it is cheap.** Writing a finding feels
|
|
39
61
|
productive; writing a retraction is a chore produced mid-task, after being corrected, when
|
|
40
62
|
attention has moved on. A rule saying "always record retractions durably" is a discipline, and
|
|
@@ -75,6 +75,40 @@ JSONL rather than prose: each line is independent, so concurrent appends from ma
|
|
|
75
75
|
collide and `merge=union` reconstructs the union exactly. A prose ledger would need a bespoke merge
|
|
76
76
|
driver to survive the same traffic.
|
|
77
77
|
|
|
78
|
+
## The re-audit field, and why it is required (CodySwannGT/lisa#3489)
|
|
79
|
+
|
|
80
|
+
A tombstone answers "this claim is dead". It does not answer **"what did I build while it was
|
|
81
|
+
alive?"** — and that is the half that reached other agents and the human. Correcting a premise
|
|
82
|
+
removes the belief and leaves every inference standing.
|
|
83
|
+
|
|
84
|
+
The observed instance: a session spent part of a day believing a readiness preflight's false claim
|
|
85
|
+
that a credential CLI was unavailable. A delegate probed the binary and disproved it, the session
|
|
86
|
+
updated the fact, and carried on. It never asked what it had concluded while believing the false
|
|
87
|
+
thing. Two hours later it relayed to its human that a work item was blocked because an SSO session
|
|
88
|
+
had expired and a deployed template could not be read — relayed unexamined, while already knowing
|
|
89
|
+
the vault worked, and while its own config had listed the relevant bootstrap secret the whole time.
|
|
90
|
+
Three facts sat in one context and were never joined.
|
|
91
|
+
|
|
92
|
+
So `--derived` is a required field on every withdrawal, and `--derived none` is the explicit empty
|
|
93
|
+
answer. The record always carries the key: an empty list means *audited, nothing survives*, and an
|
|
94
|
+
absent key means *never audited*. Distinguishing those two is the entire purpose, which is why the
|
|
95
|
+
push gate refuses a ledger entry that omits it — otherwise the write-time refusal would be a
|
|
96
|
+
formality anyone could route around by editing the file.
|
|
97
|
+
|
|
98
|
+
**Why it cannot be a scanner instead.** Re-auditing after the fact would require asking "which
|
|
99
|
+
artifacts rest on premise P". Nothing can answer that. Learnings-ledger entries carry `provenance`
|
|
100
|
+
refs and are genuinely queryable that way, but they are the only artifact class that is: a tracker
|
|
101
|
+
item marked blocked, a sentence relayed to an operator, a message to a peer session, an evidence
|
|
102
|
+
file — none record the premise they rest on. **Re-auditing those is not merely unenforced; for
|
|
103
|
+
anyone but the session still holding the context, it is impossible.** The obligation therefore has
|
|
104
|
+
to bind at the one moment that knowledge is both present and being written down.
|
|
105
|
+
|
|
106
|
+
Note the division of labour with `--reached`, which is a different question. `--reached` records the
|
|
107
|
+
**audience** — who received the claim, which is #3752's problem of delivery. `--derived` records the
|
|
108
|
+
**derivation** — what now stands on it, which is #3489's problem. A tombstone can reach every holder
|
|
109
|
+
and still leave a wrong tracker state behind, and it can enumerate its inferences while reaching
|
|
110
|
+
nobody. Both fields, or neither half is covered.
|
|
111
|
+
|
|
78
112
|
## How a running session is reached
|
|
79
113
|
|
|
80
114
|
`--session-start` stamps a per-session mark recording the ids the session was born already knowing.
|
|
@@ -62,9 +62,35 @@
|
|
|
62
62
|
* chore produced mid-task, after being corrected, when attention has moved on.
|
|
63
63
|
* A rule of the form "always record retractions durably" is a discipline, and
|
|
64
64
|
* this repository's own measurement is that prose rules land at roughly zero
|
|
65
|
-
* adoption. `--withdraw` is therefore one command with
|
|
65
|
+
* adoption. `--withdraw` is therefore one command with four required fields,
|
|
66
66
|
* and it refuses without the verbatim claim: a tombstone the holder of the
|
|
67
67
|
* claim cannot recognise reaches them without reaching them.
|
|
68
|
+
*
|
|
69
|
+
* ## Why the re-audit is a required FIELD and not a reminder
|
|
70
|
+
*
|
|
71
|
+
* Correcting a premise removes the belief and leaves every inference standing
|
|
72
|
+
* (CodySwannGT/lisa#3489). The observed instance: a session disproved a false
|
|
73
|
+
* claim that a credential CLI was unavailable, updated the fact, and two hours
|
|
74
|
+
* later still relayed to its human that a work item was blocked for a reason
|
|
75
|
+
* that rested on the retracted claim. Three facts sat in one context and were
|
|
76
|
+
* never joined. The retraction was recorded; the artifacts it invalidated were
|
|
77
|
+
* not revisited.
|
|
78
|
+
*
|
|
79
|
+
* This file already carried the obligation as prose — "if you already relayed
|
|
80
|
+
* or recorded one, correct it on the same surface" — which is the discipline
|
|
81
|
+
* shape this module exists to distrust. So `--derived` is required, and
|
|
82
|
+
* `--derived none` is the explicit empty answer. The obligation is to PRODUCE
|
|
83
|
+
* the list, not to find something wrong in it: an empty answer is fine, an
|
|
84
|
+
* absent one is not, and the difference between them is the whole control.
|
|
85
|
+
*
|
|
86
|
+
* It has to bind here because there is nowhere else it could. A scanner that
|
|
87
|
+
* went looking for artifacts resting on a retracted premise would find nothing
|
|
88
|
+
* to read: outside the learnings ledger's `provenance` refs, no artifact Lisa
|
|
89
|
+
* writes records the premise it rests on. A tracker item marked blocked, a
|
|
90
|
+
* statement relayed to a human, a message to a peer session — none carry a
|
|
91
|
+
* back-pointer. The retracting session is the only party that still holds the
|
|
92
|
+
* derivation, and the moment it runs this command is the only moment that
|
|
93
|
+
* knowledge is both present and being written down.
|
|
68
94
|
* @module withdrawn-rulings
|
|
69
95
|
*/
|
|
70
96
|
import * as fs from "node:fs";
|
|
@@ -220,6 +246,15 @@ export function formatNotice(entries, headline) {
|
|
|
220
246
|
lines.push(` superseded by: ${entry.supersededBy}`);
|
|
221
247
|
if (Array.isArray(entry.reached) && entry.reached.length > 0)
|
|
222
248
|
lines.push(` originally reached: ${entry.reached.join(", ")}`);
|
|
249
|
+
// Printed even when empty, and that is the useful case: it tells a reader
|
|
250
|
+
// the withdrawer looked, so nobody repeats the audit and nobody assumes it
|
|
251
|
+
// happened. An entry with no `derived` key at all says neither.
|
|
252
|
+
if (Array.isArray(entry.derived))
|
|
253
|
+
lines.push(
|
|
254
|
+
entry.derived.length > 0
|
|
255
|
+
? ` still standing on it: ${entry.derived.join(", ")}`
|
|
256
|
+
: ` still standing on it: nothing (audited by the withdrawer)`
|
|
257
|
+
);
|
|
223
258
|
return lines.join("\n");
|
|
224
259
|
});
|
|
225
260
|
return [
|
|
@@ -362,6 +397,24 @@ export function normalizeSuperseded(value) {
|
|
|
362
397
|
return trimmed === "" || trimmed.toLowerCase() === "none" ? null : trimmed;
|
|
363
398
|
}
|
|
364
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Normalise `--derived` values into the re-audit list.
|
|
402
|
+
*
|
|
403
|
+
* `none` is a real answer and must survive as one. A withdrawer who checked and
|
|
404
|
+
* found nothing standing has discharged the obligation exactly as fully as one
|
|
405
|
+
* who lists five artifacts, and the record has to be able to say so — otherwise
|
|
406
|
+
* the only way to satisfy the field is to invent an entry, which is worse than
|
|
407
|
+
* the gap it closes. Absent is refused elsewhere; `none` resolves here to an
|
|
408
|
+
* empty list, which reads back as "audited, nothing survives".
|
|
409
|
+
* @param {readonly string[]} values Raw `--derived` values, in order given.
|
|
410
|
+
* @returns {string[]} The artifacts still standing, possibly none.
|
|
411
|
+
*/
|
|
412
|
+
export function normalizeDerived(values) {
|
|
413
|
+
return values
|
|
414
|
+
.map(value => value.trim())
|
|
415
|
+
.filter(value => value !== "" && value.toLowerCase() !== "none");
|
|
416
|
+
}
|
|
417
|
+
|
|
365
418
|
/**
|
|
366
419
|
* Build the record `--withdraw` appends, refusing an unrecognisable tombstone.
|
|
367
420
|
* @param {readonly string[]} argv Arguments.
|
|
@@ -380,12 +433,25 @@ export function buildWithdrawal(argv, now) {
|
|
|
380
433
|
};
|
|
381
434
|
if (because === "")
|
|
382
435
|
return { error: "--because is required: say what disproved it" };
|
|
436
|
+
const derived = flagValues(argv, "--derived");
|
|
437
|
+
if (derived.length === 0)
|
|
438
|
+
return {
|
|
439
|
+
error:
|
|
440
|
+
"--derived is required: name what you asserted, filed or relayed while you " +
|
|
441
|
+
"believed this, so the retraction reaches those artifacts too. Correcting a " +
|
|
442
|
+
"premise leaves every inference standing. Pass `--derived none` if you checked " +
|
|
443
|
+
"and nothing survives — an empty answer is fine, an absent one is not.",
|
|
444
|
+
};
|
|
383
445
|
return {
|
|
384
446
|
record: {
|
|
385
447
|
id,
|
|
386
448
|
withdrawnAt: now,
|
|
387
449
|
claim,
|
|
388
450
|
because,
|
|
451
|
+
// Present even when empty: the difference between "audited, nothing
|
|
452
|
+
// survives" and "never audited" is the whole point of the field, and an
|
|
453
|
+
// omitted key cannot express the first.
|
|
454
|
+
derived: normalizeDerived(derived),
|
|
389
455
|
// "none" is what a person types when nothing replaced the claim, and a
|
|
390
456
|
// string "none" in the record would print as a superseding id that does
|
|
391
457
|
// not exist. Normalised here rather than at every reader.
|
|
@@ -452,6 +518,15 @@ export function runCheck(root) {
|
|
|
452
518
|
for (const entry of entries) {
|
|
453
519
|
if (typeof entry.claim !== "string" || entry.claim === "")
|
|
454
520
|
problems.push(`${entry.id}: no verbatim claim recorded`);
|
|
521
|
+
// An absent `derived` is a tombstone whose writer never said what rested on
|
|
522
|
+
// the claim. That is the CodySwannGT/lisa#3489 failure written into the
|
|
523
|
+
// ledger, and letting it through here would make the write-time refusal a
|
|
524
|
+
// formality anything could route around by editing the file.
|
|
525
|
+
if (!Array.isArray(entry.derived))
|
|
526
|
+
problems.push(
|
|
527
|
+
`${entry.id}: no re-audit recorded — say what was asserted, filed or relayed ` +
|
|
528
|
+
`while the claim was believed, or record an empty list to say nothing survives`
|
|
529
|
+
);
|
|
455
530
|
if (
|
|
456
531
|
typeof entry.withdrawnAt !== "string" ||
|
|
457
532
|
Number.isNaN(Date.parse(entry.withdrawnAt))
|