@codyswann/lisa 4.27.5 → 4.27.7

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.
Files changed (93) hide show
  1. package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +27 -4
  2. package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +507 -11
  3. package/dist/cli/doctor-enforcement-coverage.d.ts +14 -0
  4. package/dist/cli/doctor-enforcement-coverage.d.ts.map +1 -0
  5. package/dist/cli/doctor-enforcement-coverage.js +103 -0
  6. package/dist/cli/doctor-enforcement-coverage.js.map +1 -0
  7. package/dist/cli/doctor.d.ts.map +1 -1
  8. package/dist/cli/doctor.js +9 -0
  9. package/dist/cli/doctor.js.map +1 -1
  10. package/dist/core/enforcement-census-report.d.ts +19 -0
  11. package/dist/core/enforcement-census-report.d.ts.map +1 -0
  12. package/dist/core/enforcement-census-report.js +198 -0
  13. package/dist/core/enforcement-census-report.js.map +1 -0
  14. package/dist/core/enforcement-census.d.ts +177 -0
  15. package/dist/core/enforcement-census.d.ts.map +1 -0
  16. package/dist/core/enforcement-census.js +252 -0
  17. package/dist/core/enforcement-census.js.map +1 -0
  18. package/dist/core/enforcement-coverage.d.ts +168 -0
  19. package/dist/core/enforcement-coverage.d.ts.map +1 -0
  20. package/dist/core/enforcement-coverage.js +342 -0
  21. package/dist/core/enforcement-coverage.js.map +1 -0
  22. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  23. package/dist/core/lisa-owned-hash-ledger.js +8 -0
  24. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  25. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  26. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  27. package/dist/core/upstream-evidence-manifest.js +21 -5
  28. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  29. package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +180 -19
  30. package/package.json +4 -3
  31. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +23 -0
  34. package/plugins/lisa/hooks/block-direct-issue-create.sh +507 -11
  35. package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +23 -0
  36. package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +507 -11
  37. package/plugins/lisa-agy/plugin.json +1 -1
  38. package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +23 -0
  39. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  42. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +507 -11
  46. package/plugins/lisa-copilot/skills/lisa-setup-automations/SKILL.md +23 -0
  47. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +507 -11
  49. package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +23 -0
  50. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-expo-agy/plugin.json +1 -1
  53. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  58. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  63. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  68. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  72. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  73. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  77. package/plugins/lisa-rails-agy/plugin.json +1 -1
  78. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  82. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  83. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  87. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  88. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  90. package/plugins/src/base/hooks/block-direct-issue-create.sh +507 -11
  91. package/plugins/src/base/skills/lisa-setup-automations/SKILL.md +23 -0
  92. package/scripts/lisa-enforcement-census.mjs +168 -0
  93. package/scripts/lisa-enforcement-fallback.sh +27 -4
@@ -0,0 +1,252 @@
1
+ /**
2
+ * The fleet census: how many checkouts resolve enforcement, re-derived on
3
+ * every run.
4
+ *
5
+ * The per-session staleness notice reports **this** checkout to **this**
6
+ * session, and it is right to do so — it keeps running while stale, and it
7
+ * prints once per session rather than per call, both deliberately. The gap it
8
+ * leaves is that nobody was looking at the fleet, so the only fleet-wide number
9
+ * that ever existed was hand-taken and frozen in a code comment
10
+ * (CodySwannGT/lisa#3490).
11
+ *
12
+ * Two rules shape everything below, and both are learned from that notice:
13
+ *
14
+ * - **Report, do not gate.** No finding about the fleet may change the exit
15
+ * status. Reddening a build because someone else's checkout is stale would
16
+ * be a worse control than the one it replaces, and it would be routed
17
+ * around — and a guard routed around protects nothing.
18
+ * - **"Resolves nothing" and "resolves something old" are different
19
+ * findings.** They have different remedies, and folding them into one "N
20
+ * stale" number loses the serious half — which is exactly how the
21
+ * unenforced checkouts stayed easy to miss. They are separate
22
+ * fields here, and `countStale` is defined over the resolving set alone, so
23
+ * folding them takes a code change that a named test refuses.
24
+ *
25
+ * Nothing in this module stores a count. The roster names where to look, the
26
+ * checkouts on disk answer, and the reference version is a maximum over the
27
+ * evidence found during the same run. A census that restated today's number in
28
+ * a different file would go stale exactly the way the comment did.
29
+ * @module core/enforcement-census
30
+ */
31
+ import { createHash } from "node:crypto";
32
+ import { readdir, realpath } from "node:fs/promises";
33
+ import * as os from "node:os";
34
+ import * as path from "node:path";
35
+ import { applyReference, collectCheckoutCoverage, isOlderVersion, versionEvidence, } from "./enforcement-coverage.js";
36
+ import { readJsonOrNull } from "../utils/json-utils.js";
37
+ import { pathExists } from "../utils/file-operations.js";
38
+ /** The fleet roster `scripts/lisa-update-local.sh` already maintains. */
39
+ export const DEFAULT_ROSTER_FILE = ".lisa.workspaces.json";
40
+ /**
41
+ * Expand a leading `~` the way the roster's other reader does.
42
+ * @param value - A path that may start with `~`
43
+ * @returns The expanded path
44
+ */
45
+ function expandHome(value) {
46
+ return value.startsWith("~")
47
+ ? path.join(os.homedir(), value.slice(1))
48
+ : value;
49
+ }
50
+ /**
51
+ * Read a fleet roster file.
52
+ *
53
+ * The primary shape is the one already on disk: an object mapping checkout path
54
+ * to target branch, which `scripts/lisa-update-local.sh` reads. An array of
55
+ * paths is accepted too, so a census can be pointed at an ad-hoc list without
56
+ * inventing a branch for each entry.
57
+ * @param rosterPath - Path to the roster file
58
+ * @returns The checkouts it names, or null when it cannot be read
59
+ */
60
+ export async function readFleetRoster(rosterPath) {
61
+ const parsed = await readJsonOrNull(rosterPath);
62
+ if (parsed === null || typeof parsed !== "object")
63
+ return null;
64
+ const raw = Array.isArray(parsed)
65
+ ? parsed.filter((entry) => typeof entry === "string")
66
+ : Object.keys(parsed);
67
+ return raw.map(entry => ({
68
+ label: entry,
69
+ checkoutPath: path.resolve(expandHome(entry)),
70
+ source: "roster",
71
+ }));
72
+ }
73
+ /**
74
+ * Find checkouts under a directory, without a roster.
75
+ *
76
+ * Discovery exists because a hand-maintained roster can go stale the same way
77
+ * the frozen comment did: a checkout nobody added is a checkout the census
78
+ * cannot see, and an unseen checkout reads as coverage it does not have. A
79
+ * candidate is any directory containing a `.git` entry, found no deeper than
80
+ * `depth` levels so the walk stays bounded on a large disk.
81
+ * @param root - Directory to search
82
+ * @param depth - How many levels below `root` to descend
83
+ * @returns The checkouts found, in listing order
84
+ */
85
+ export async function scanForCheckouts(root, depth) {
86
+ const resolved = path.resolve(expandHome(root));
87
+ if (depth < 0 || !(await pathExists(resolved)))
88
+ return [];
89
+ if (await pathExists(path.join(resolved, ".git"))) {
90
+ return [{ label: resolved, checkoutPath: resolved, source: "scan" }];
91
+ }
92
+ const entries = await readdir(resolved, { withFileTypes: true }).catch(() => []);
93
+ const children = entries
94
+ .filter(entry => entry.isDirectory() && !entry.name.startsWith("."))
95
+ .filter(entry => entry.name !== "node_modules")
96
+ .map(entry => path.join(resolved, entry.name));
97
+ const found = await Promise.all(children.map(child => scanForCheckouts(child, depth - 1)));
98
+ return found.flat();
99
+ }
100
+ /**
101
+ * Collapse entries that name the same checkout twice.
102
+ *
103
+ * Two roster spellings of one directory, or a roster entry a scan also found,
104
+ * would otherwise be counted twice — inflating both the numerator and the
105
+ * denominator and making the census disagree with itself.
106
+ * @param entries - Entries from every source
107
+ * @returns One entry per distinct checkout, first spelling wins
108
+ */
109
+ export async function dedupeRoster(entries) {
110
+ const keyed = await Promise.all(entries.map(async (entry) => ({
111
+ entry,
112
+ key: await realpath(entry.checkoutPath).catch(() => entry.checkoutPath),
113
+ })));
114
+ return keyed
115
+ .filter((candidate, index) => keyed.findIndex(other => other.key === candidate.key) === index)
116
+ .map(candidate => candidate.entry);
117
+ }
118
+ /**
119
+ * Count the resolving checkouts whose governing copy is behind.
120
+ *
121
+ * Defined over the resolving set on purpose, and named so that folding the
122
+ * unguarded class into it is a visible edit rather than an accident. A checkout
123
+ * that resolves nothing has no copy to be behind: calling it stale describes a
124
+ * guard that would refuse things, and there is none.
125
+ * @param checkouts - Every record in the run
126
+ * @returns How many resolving checkouts are behind the reference
127
+ */
128
+ export function countStale(checkouts) {
129
+ return checkouts.filter(entry => (entry.resolution === "partial" || entry.resolution === "full") &&
130
+ entry.vintage === "behind").length;
131
+ }
132
+ /**
133
+ * Reduce a run to its disjoint classes.
134
+ * @param checkouts - Every record in the run
135
+ * @returns The summary
136
+ */
137
+ export function summarizeFleetCensus(checkouts) {
138
+ const resolving = checkouts.filter(entry => entry.resolution === "partial" || entry.resolution === "full");
139
+ const count = (list, predicate) => list.filter(predicate).length;
140
+ return {
141
+ total: checkouts.length,
142
+ unreadable: count(checkouts, entry => entry.resolution === "unreadable"),
143
+ unguarded: count(checkouts, entry => entry.resolution === "none"),
144
+ partial: count(checkouts, entry => entry.resolution === "partial"),
145
+ full: count(checkouts, entry => entry.resolution === "full"),
146
+ resolving: resolving.length,
147
+ behind: countStale(checkouts),
148
+ undateable: count(resolving, entry => entry.vintage === "undateable"),
149
+ current: count(resolving, entry => entry.vintage === "current"),
150
+ withoutReceipt: count(resolving, entry => !entry.receipt.present),
151
+ installBehindDeclared: count(checkouts, entry => isInstallBehindDeclared(entry)),
152
+ covered: count(checkouts, entry => entry.resolution === "full" &&
153
+ entry.vintage === "current" &&
154
+ entry.receipt.present),
155
+ };
156
+ }
157
+ /**
158
+ * Whether a checkout's installed Lisa is older than the one it declares.
159
+ *
160
+ * The comparison is against the range's own floor, read as a plain version once
161
+ * the usual range punctuation is stripped. A range this cannot parse yields
162
+ * `false` — absence of proof never becomes proof of drift.
163
+ * @param coverage - One record
164
+ * @returns Whether the installed copy is behind the declared one
165
+ */
166
+ export function isInstallBehindDeclared(coverage) {
167
+ const { declared, installed } = coverage.install;
168
+ if (declared === null || installed === null)
169
+ return false;
170
+ // Bounded quantifiers rather than `\d+`: a range string arrives from a
171
+ // manifest this repository does not own, and an unbounded pattern over
172
+ // untrusted input is the shape that backtracks pathologically.
173
+ const floor = /(\d{1,9}\.\d{1,9}\.\d{1,9})/u.exec(declared)?.[1];
174
+ return floor !== undefined && isOlderVersion(installed, floor);
175
+ }
176
+ /**
177
+ * Resolve the newest Lisa this run can point at.
178
+ *
179
+ * A maximum over evidence found on the same disk during the same run, never a
180
+ * network lookup and never a nominated authority. A copy is reported behind
181
+ * only when something newer can actually be pointed at, so the worst case is
182
+ * silence rather than a fleet-wide false alarm.
183
+ * @param checkouts - Records collected with no reference
184
+ * @param seed - A version the caller can vouch for, such as its own manifest
185
+ * @returns The reference version and what proved it
186
+ */
187
+ export function resolveReference(checkouts, seed) {
188
+ const candidates = checkouts.flatMap(entry => versionEvidence(entry).map(version => ({
189
+ version,
190
+ source: entry.label,
191
+ })));
192
+ const all = seed === null
193
+ ? candidates
194
+ : [
195
+ { version: seed, source: "this checkout's own manifest" },
196
+ ...candidates,
197
+ ];
198
+ const best = all.reduce((winner, candidate) => winner === null || isOlderVersion(winner.version, candidate.version)
199
+ ? candidate
200
+ : winner, null);
201
+ return best === null
202
+ ? { version: null, source: "no Lisa version could be found on this disk" }
203
+ : best;
204
+ }
205
+ /**
206
+ * Run the census.
207
+ *
208
+ * Never throws for a fleet finding and never returns a status: the caller
209
+ * prints this and exits 0. The only thing that can fail here is the census
210
+ * itself, and a checkout it cannot read comes back as `unreadable` rather than
211
+ * as an exception, because one unreachable directory must not stop the other
212
+ * twenty-six from being measured.
213
+ * @param options - Roster and reference inputs
214
+ * @returns The measured fleet
215
+ */
216
+ export async function runFleetCensus(options) {
217
+ const undated = await Promise.all(options.roster.map(entry => collectCheckoutCoverage({
218
+ label: entry.label,
219
+ checkoutPath: entry.checkoutPath,
220
+ reference: null,
221
+ })));
222
+ const resolved = options.reference != null && options.reference !== ""
223
+ ? { version: options.reference, source: "supplied by the caller" }
224
+ : resolveReference(undated, options.seedVersion ?? null);
225
+ const checkouts = undated.map(entry => applyReference(entry, resolved.version));
226
+ return {
227
+ measuredAt: (options.now?.() ?? new Date()).toISOString(),
228
+ reference: resolved.version,
229
+ referenceSource: resolved.source,
230
+ rosterOrigin: options.rosterOrigin,
231
+ checkouts,
232
+ summary: summarizeFleetCensus(checkouts),
233
+ };
234
+ }
235
+ /**
236
+ * A stable, path-free label for a checkout.
237
+ *
238
+ * The census names real local checkouts, and those paths embed client and
239
+ * product names that must never reach a public issue or PR. Redaction keeps the
240
+ * counts and the per-checkout rows — which is all a fleet finding needs — while
241
+ * making the report safe to paste. It is derived from the path, so the same
242
+ * checkout carries the same label between runs and can still be discussed.
243
+ * @param checkoutPath - The real path
244
+ * @returns A stable anonymous label
245
+ */
246
+ export function redactedLabel(checkoutPath) {
247
+ return `checkout-${createHash("sha256")
248
+ .update(checkoutPath)
249
+ .digest("hex")
250
+ .slice(0, 8)}`;
251
+ }
252
+ //# sourceMappingURL=enforcement-census.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforcement-census.js","sourceRoot":"","sources":["../../src/core/enforcement-census.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * The guards the dispatcher runs, in its own order.
3
+ *
4
+ * Kept as data rather than inferred from the directory listing: a census that
5
+ * asks "which guard files are here" cannot notice a guard that is missing
6
+ * everywhere, which is the case it most needs to report.
7
+ */
8
+ export declare const ENFORCEMENT_GUARDS: readonly ["block-no-verify", "parity-safety-net", "block-shell-json-parsing", "block-instruction-file-edits", "block-direct-issue-create", "block-managed-file-edits"];
9
+ /** Which of the two trees a resolved guard came from. */
10
+ export type GuardTree = "host" | "plugin";
11
+ /**
12
+ * How much enforcement a checkout resolves.
13
+ *
14
+ * `unreadable` is a first-class answer and never a synonym for any of the
15
+ * others: a checkout the census could not look into has not been shown to
16
+ * resolve anything, and has not been shown to resolve nothing either.
17
+ */
18
+ export type CoverageResolution = "unreadable" | "none" | "partial" | "full";
19
+ /**
20
+ * How old the copy in force is.
21
+ *
22
+ * `not-applicable` belongs to checkouts that resolve nothing — they have no
23
+ * copy to be old. That value is what makes it structurally impossible to count
24
+ * an unguarded checkout as a stale one.
25
+ */
26
+ export type CoverageVintage = "current" | "behind" | "undateable" | "not-applicable";
27
+ /** One guard and the copy that would run for it, if any. */
28
+ export interface GuardResolution {
29
+ /** Guard name, as the dispatcher spells it. */
30
+ readonly guard: string;
31
+ /** Tree the running copy comes from, or null when nothing resolved. */
32
+ readonly tree: GuardTree | null;
33
+ /** Absolute path of the copy that would run, or null. */
34
+ readonly script: string | null;
35
+ /** Whether a second copy exists that this one shadows. */
36
+ readonly shadows: boolean;
37
+ }
38
+ /** One guard tree, and the evidence that dates it. */
39
+ export interface TreeVintage {
40
+ /** Which tree this is. */
41
+ readonly tree: GuardTree;
42
+ /** Absolute path of the tree. */
43
+ readonly root: string;
44
+ /** Lisa version that produced it, or null when it cannot be dated. */
45
+ readonly version: string | null;
46
+ /** The file that dated it, or the file whose absence left it undateable. */
47
+ readonly datedBy: string;
48
+ }
49
+ /** What `lisa apply` left behind, if anything. */
50
+ export interface ReceiptSummary {
51
+ /** Whether a readable receipt exists. */
52
+ readonly present: boolean;
53
+ /** Lisa version that completed the apply. */
54
+ readonly version: string | null;
55
+ /** When that apply completed. */
56
+ readonly appliedAt: string | null;
57
+ /** Whether it was a full apply or the postinstall-safe subset. */
58
+ readonly mode: string | null;
59
+ }
60
+ /**
61
+ * What the checkout's own manifest asks for versus what is installed.
62
+ *
63
+ * Its own axis, because it fails in the affirmative: a checkout whose
64
+ * `node_modules` is majors behind its `package.json` resolves guards, runs
65
+ * them, and answers every question confidently about a version nobody is using.
66
+ */
67
+ export interface InstallSummary {
68
+ /** Range the checkout's manifest declares, or null when it declares none. */
69
+ readonly declared: string | null;
70
+ /** Version actually present under `node_modules`, or null. */
71
+ readonly installed: string | null;
72
+ }
73
+ /** Everything one checkout resolves, right now. */
74
+ export interface CheckoutCoverage {
75
+ /** Operator-facing label — the roster key, or the path when scanned. */
76
+ readonly label: string;
77
+ /** Absolute path of the checkout. */
78
+ readonly checkoutPath: string;
79
+ /** How much enforcement it resolves. */
80
+ readonly resolution: CoverageResolution;
81
+ /** Why it could not be read, when `resolution` is `unreadable`. */
82
+ readonly unreadableReason: string | null;
83
+ /** Per-guard resolution. Empty when the checkout could not be read. */
84
+ readonly guards: readonly GuardResolution[];
85
+ /** Guards no copy of which was found. */
86
+ readonly unresolvedGuards: readonly string[];
87
+ /** Every tree that contributes at least one running guard. */
88
+ readonly trees: readonly TreeVintage[];
89
+ /** The oldest tree in use — the copy that governs. Null when none is. */
90
+ readonly governing: TreeVintage | null;
91
+ /** Age of the governing copy against the reference version. */
92
+ readonly vintage: CoverageVintage;
93
+ /** The apply receipt, or its absence. */
94
+ readonly receipt: ReceiptSummary;
95
+ /** Declared versus installed Lisa in this checkout. */
96
+ readonly install: InstallSummary;
97
+ }
98
+ /**
99
+ * Read a dotted version's release fields, or refuse to read it at all.
100
+ *
101
+ * Prerelease and build suffixes are ignored. A string whose release fields are
102
+ * not all plain numbers yields `null` rather than a best guess: reading an
103
+ * unparseable field as 0 makes it sort BELOW every real version, which turns
104
+ * "this does not parse" into the confident claim "this is behind" — a
105
+ * measurement invented out of an absence, which is the shape of defect this
106
+ * whole module exists to end.
107
+ * @param value - A version string from a manifest or receipt this repo does not own
108
+ * @returns The release fields, or null when the string is not a version
109
+ */
110
+ export declare function versionFields(value: string): readonly number[] | null;
111
+ /**
112
+ * Compare two dotted versions on their release fields only.
113
+ *
114
+ * A version either side cannot parse yields `false`: absence of proof never
115
+ * becomes proof of drift, the same rule `isInstallBehindDeclared` follows. The
116
+ * caller that needs to distinguish "not older" from "could not tell" asks
117
+ * {@link versionFields} directly — `classifyVintage` does, and reports an
118
+ * unparseable copy as undateable rather than letting it fall through to
119
+ * "current".
120
+ * @param left - Candidate version
121
+ * @param right - Version to compare against
122
+ * @returns Whether `left` names an older Lisa than `right`
123
+ */
124
+ export declare function isOlderVersion(left: string, right: string): boolean;
125
+ /** Everything the caller must supply to measure one checkout. */
126
+ export interface CoverageRequest {
127
+ /** Operator-facing label for the checkout. */
128
+ readonly label: string;
129
+ /** Path to the checkout, already expanded and resolved. */
130
+ readonly checkoutPath: string;
131
+ /** Newest Lisa the census can point at, or null when nothing dates it. */
132
+ readonly reference: string | null;
133
+ }
134
+ /**
135
+ * Measure what one checkout resolves right now.
136
+ *
137
+ * Every failure to look is reported as a failure to look. A checkout that is
138
+ * not there, or not readable, is never quietly dropped from the fleet and never
139
+ * counted as covered: a census that reports better coverage than exists is
140
+ * worse than no census, because it retires the question.
141
+ * @param request - The checkout to measure
142
+ * @returns What that checkout resolves
143
+ */
144
+ export declare function collectCheckoutCoverage(request: CoverageRequest): Promise<CheckoutCoverage>;
145
+ /**
146
+ * Re-classify a record's vintage against a reference resolved later.
147
+ *
148
+ * The census cannot know the newest Lisa on the disk until it has read every
149
+ * checkout, and the reference is a maximum over exactly that evidence. So
150
+ * records are collected first with no reference and dated afterwards, rather
151
+ * than the census guessing a reference up front and being wrong about the one
152
+ * checkout that carries the newest copy.
153
+ * @param coverage - A record collected with no reference
154
+ * @param reference - Newest Lisa the census can point at
155
+ * @returns The same record, dated
156
+ */
157
+ export declare function applyReference(coverage: CheckoutCoverage, reference: string | null): CheckoutCoverage;
158
+ /**
159
+ * Every Lisa version this checkout proves exists somewhere on the disk.
160
+ *
161
+ * Used to build the reference the way the dispatcher builds its own: a maximum
162
+ * over evidence on the same disk, never a nominated authority and never a
163
+ * network lookup. A copy is behind only when something newer can be pointed at.
164
+ * @param coverage - A collected record
165
+ * @returns The versions it can vouch for
166
+ */
167
+ export declare function versionEvidence(coverage: CheckoutCoverage): readonly string[];
168
+ //# sourceMappingURL=enforcement-coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforcement-coverage.d.ts","sourceRoot":"","sources":["../../src/core/enforcement-coverage.ts"],"names":[],"mappings":"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"}