@codyswann/lisa 4.27.4 → 4.27.6
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/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +27 -4
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +167 -2
- package/dist/cli/doctor-enforcement-coverage.d.ts +14 -0
- package/dist/cli/doctor-enforcement-coverage.d.ts.map +1 -0
- package/dist/cli/doctor-enforcement-coverage.js +103 -0
- package/dist/cli/doctor-enforcement-coverage.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +9 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/core/enforcement-census-report.d.ts +19 -0
- package/dist/core/enforcement-census-report.d.ts.map +1 -0
- package/dist/core/enforcement-census-report.js +198 -0
- package/dist/core/enforcement-census-report.js.map +1 -0
- package/dist/core/enforcement-census.d.ts +177 -0
- package/dist/core/enforcement-census.d.ts.map +1 -0
- package/dist/core/enforcement-census.js +252 -0
- package/dist/core/enforcement-census.js.map +1 -0
- package/dist/core/enforcement-coverage.d.ts +168 -0
- package/dist/core/enforcement-coverage.d.ts.map +1 -0
- package/dist/core/enforcement-coverage.js +342 -0
- package/dist/core/enforcement-coverage.js.map +1 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +10 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- 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 +25 -7
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/ensure-push-default-safe.d.ts +50 -0
- package/dist/migrations/ensure-push-default-safe.d.ts.map +1 -0
- package/dist/migrations/ensure-push-default-safe.js +159 -0
- package/dist/migrations/ensure-push-default-safe.js.map +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +3 -0
- package/dist/migrations/index.js.map +1 -1
- package/package.json +4 -3
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +23 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-sync-down/SKILL.md +8 -1
- package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +23 -0
- package/plugins/lisa/skills/lisa-sync-down/SKILL.md +8 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +23 -0
- package/plugins/lisa-agy/skills/lisa-sync-down/SKILL.md +8 -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/skills/lisa-setup-automations/SKILL.md +23 -0
- package/plugins/lisa-copilot/skills/lisa-sync-down/SKILL.md +8 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +23 -0
- package/plugins/lisa-cursor/skills/lisa-sync-down/SKILL.md +8 -1
- 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/skills/lisa-setup-automations/SKILL.md +23 -0
- package/plugins/src/base/skills/lisa-sync-down/SKILL.md +8 -1
- package/scripts/lisa-commit-and-pr-local.sh +22 -6
- package/scripts/lisa-enforcement-census.mjs +168 -0
- package/scripts/lisa-enforcement-fallback.sh +27 -4
- package/typescript/copy-contents/.husky/pre-push +70 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { isInstallBehindDeclared, redactedLabel, } from "./enforcement-census.js";
|
|
2
|
+
/** Where a host tree lives inside a checkout. */
|
|
3
|
+
const HOST_TREE = "scripts/lisa-hooks";
|
|
4
|
+
/** Where the monorepo's own tree lives inside a checkout. */
|
|
5
|
+
const PLUGIN_TREE = "plugins/lisa/hooks";
|
|
6
|
+
/** What dates a host tree. */
|
|
7
|
+
const HOST_DATED_BY = ".lisa/apply-receipt.json";
|
|
8
|
+
/** What dates a plugin tree. */
|
|
9
|
+
const PLUGIN_DATED_BY = "plugins/lisa/.claude-plugin/plugin.json";
|
|
10
|
+
/** What a redacted field says instead of a real path. */
|
|
11
|
+
const REDACTED = "redacted";
|
|
12
|
+
/**
|
|
13
|
+
* Replace every real path in a census with a stable anonymous label.
|
|
14
|
+
*
|
|
15
|
+
* The paths this census reads name real local checkouts, and they embed client
|
|
16
|
+
* and product names that must never reach a public issue, PR, or commit. The
|
|
17
|
+
* counts and the per-checkout rows survive redaction intact, so a fleet finding
|
|
18
|
+
* loses nothing by being quoted safely.
|
|
19
|
+
* @param census - A measured fleet
|
|
20
|
+
* @returns The same fleet with paths replaced by derived labels
|
|
21
|
+
*/
|
|
22
|
+
export function redactCensus(census) {
|
|
23
|
+
const scrubTree = (tree, label) => ({
|
|
24
|
+
...tree,
|
|
25
|
+
root: `${label}/${tree.tree === "host" ? HOST_TREE : PLUGIN_TREE}`,
|
|
26
|
+
datedBy: `${label}/${tree.tree === "host" ? HOST_DATED_BY : PLUGIN_DATED_BY}`,
|
|
27
|
+
});
|
|
28
|
+
const scrub = (coverage) => {
|
|
29
|
+
const label = redactedLabel(coverage.checkoutPath);
|
|
30
|
+
return {
|
|
31
|
+
...coverage,
|
|
32
|
+
label,
|
|
33
|
+
checkoutPath: label,
|
|
34
|
+
guards: coverage.guards.map(guard => ({ ...guard, script: null })),
|
|
35
|
+
trees: coverage.trees.map(tree => scrubTree(tree, label)),
|
|
36
|
+
governing: coverage.governing === null
|
|
37
|
+
? null
|
|
38
|
+
: scrubTree(coverage.governing, label),
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
...census,
|
|
43
|
+
rosterOrigin: REDACTED,
|
|
44
|
+
referenceSource: REDACTED,
|
|
45
|
+
checkouts: census.checkouts.map(scrub),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Describe which copy governs a checkout and how old it is.
|
|
50
|
+
* @param coverage - One record
|
|
51
|
+
* @param reference - Newest Lisa the run could point at
|
|
52
|
+
* @returns One operator-readable clause
|
|
53
|
+
*/
|
|
54
|
+
function describeGoverning(coverage, reference) {
|
|
55
|
+
const governing = coverage.governing;
|
|
56
|
+
if (governing === null)
|
|
57
|
+
return "no copy in force";
|
|
58
|
+
const where = governing.tree === "host" ? HOST_TREE : PLUGIN_TREE;
|
|
59
|
+
if (governing.version === null) {
|
|
60
|
+
return `governed by ${where} — vintage unknown, so it cannot be shown current`;
|
|
61
|
+
}
|
|
62
|
+
if (coverage.vintage === "behind") {
|
|
63
|
+
return `governed by ${where} — lisa ${governing.version}, behind ${reference ?? "the newest on this disk"}`;
|
|
64
|
+
}
|
|
65
|
+
return `governed by ${where} — lisa ${governing.version}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Say what a checkout has installed against what it declares.
|
|
69
|
+
* @param coverage - One record
|
|
70
|
+
* @returns One operator-readable clause
|
|
71
|
+
*/
|
|
72
|
+
function describeInstallDrift(coverage) {
|
|
73
|
+
return `installed ${coverage.install.installed ?? "?"}, declares ${coverage.install.declared ?? "?"}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* One row for a checkout that resolves at least one guard.
|
|
77
|
+
* @param coverage - One record
|
|
78
|
+
* @param reference - Newest Lisa the run could point at
|
|
79
|
+
* @returns The row
|
|
80
|
+
*/
|
|
81
|
+
function resolvingRow(coverage, reference) {
|
|
82
|
+
const parts = [
|
|
83
|
+
describeGoverning(coverage, reference),
|
|
84
|
+
coverage.receipt.present ? null : "no apply receipt",
|
|
85
|
+
coverage.resolution === "partial"
|
|
86
|
+
? `unresolved: ${coverage.unresolvedGuards.join(", ")}`
|
|
87
|
+
: null,
|
|
88
|
+
isInstallBehindDeclared(coverage) ? describeInstallDrift(coverage) : null,
|
|
89
|
+
].filter((part) => part !== null);
|
|
90
|
+
return ` - ${coverage.label} — ${parts.join("; ")}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build the section for checkouts that resolve nothing.
|
|
94
|
+
*
|
|
95
|
+
* First in the report, and never merged into the stale section below it. These
|
|
96
|
+
* are not behind — they are unenforced, and the remedy is different.
|
|
97
|
+
* @param census - A measured fleet
|
|
98
|
+
* @returns The section
|
|
99
|
+
*/
|
|
100
|
+
function unguardedSection(census) {
|
|
101
|
+
const rows = census.checkouts
|
|
102
|
+
.filter(entry => entry.resolution === "none")
|
|
103
|
+
.map(entry => ` - ${entry.label}`);
|
|
104
|
+
return {
|
|
105
|
+
heading: `NOT ENFORCING — ${rows.length} of ${census.summary.total} resolve no guard at all`,
|
|
106
|
+
note: "An agent working in one of these is checked by nothing, and nothing in the session says so. Repair: run `npx @codyswann/lisa apply` in that checkout.",
|
|
107
|
+
rows,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Build the section for checkouts the census could not read.
|
|
112
|
+
* @param census - A measured fleet
|
|
113
|
+
* @returns The section
|
|
114
|
+
*/
|
|
115
|
+
function unreadableSection(census) {
|
|
116
|
+
const rows = census.checkouts
|
|
117
|
+
.filter(entry => entry.resolution === "unreadable")
|
|
118
|
+
.map(entry => ` - ${entry.label} — ${entry.unreadableReason ?? "unknown"}`);
|
|
119
|
+
return {
|
|
120
|
+
heading: `COULD NOT LOOK — ${rows.length} of ${census.summary.total}`,
|
|
121
|
+
note: "Not counted as covered and not counted as unenforced: the census does not know. Repair: fix the path on the roster, or remove it.",
|
|
122
|
+
rows,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build the section for checkouts that resolve at least one guard.
|
|
127
|
+
* @param census - A measured fleet
|
|
128
|
+
* @returns The section
|
|
129
|
+
*/
|
|
130
|
+
function resolvingSection(census) {
|
|
131
|
+
const { summary } = census;
|
|
132
|
+
const rows = census.checkouts
|
|
133
|
+
.filter(entry => entry.resolution === "partial" || entry.resolution === "full")
|
|
134
|
+
.map(entry => resolvingRow(entry, census.reference));
|
|
135
|
+
return {
|
|
136
|
+
heading: `ENFORCING — ${summary.resolving} of ${summary.total} (${summary.partial} of them only partly)`,
|
|
137
|
+
note: `behind: ${summary.behind} · vintage unknown: ${summary.undateable} · current: ${summary.current} · no apply receipt: ${summary.withoutReceipt}`,
|
|
138
|
+
rows,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Build the section for checkouts whose installed Lisa is behind their own
|
|
143
|
+
* manifest.
|
|
144
|
+
*
|
|
145
|
+
* Its own section because it fails in the affirmative: these checkouts resolve
|
|
146
|
+
* guards, run them, and answer confidently about a version nobody is using.
|
|
147
|
+
* @param census - A measured fleet
|
|
148
|
+
* @returns The section
|
|
149
|
+
*/
|
|
150
|
+
function installDriftSection(census) {
|
|
151
|
+
const rows = census.checkouts
|
|
152
|
+
.filter(entry => isInstallBehindDeclared(entry))
|
|
153
|
+
.map(entry => ` - ${entry.label} — ${describeInstallDrift(entry)}`);
|
|
154
|
+
return {
|
|
155
|
+
heading: `INSTALLED BEHIND DECLARED — ${rows.length} of ${census.summary.total}`,
|
|
156
|
+
note: "These answer every question confidently about a Lisa version nobody is running. Repair: reinstall dependencies in that checkout.",
|
|
157
|
+
rows,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Render one section, or nothing when it is empty and uninteresting.
|
|
162
|
+
* @param section - The section
|
|
163
|
+
* @param always - Whether to print it even when empty
|
|
164
|
+
* @returns The rendered lines
|
|
165
|
+
*/
|
|
166
|
+
function renderSection(section, always) {
|
|
167
|
+
if (section.rows.length === 0 && !always)
|
|
168
|
+
return [];
|
|
169
|
+
return [
|
|
170
|
+
"",
|
|
171
|
+
section.heading,
|
|
172
|
+
` ${section.note}`,
|
|
173
|
+
...(section.rows.length === 0 ? [" (none)"] : section.rows),
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Render the whole census for a terminal.
|
|
178
|
+
* @param census - A measured fleet
|
|
179
|
+
* @returns The report
|
|
180
|
+
*/
|
|
181
|
+
export function renderFleetCensusReport(census) {
|
|
182
|
+
const { summary } = census;
|
|
183
|
+
const lines = [
|
|
184
|
+
`Lisa enforcement fleet census — ${census.measuredAt}`,
|
|
185
|
+
`Roster: ${census.rosterOrigin} (${summary.total} checkout${summary.total === 1 ? "" : "s"})`,
|
|
186
|
+
`Newest Lisa found: ${census.reference ?? "none"} (${census.referenceSource})`,
|
|
187
|
+
...renderSection(unguardedSection(census), true),
|
|
188
|
+
...renderSection(unreadableSection(census), true),
|
|
189
|
+
...renderSection(resolvingSection(census), true),
|
|
190
|
+
...renderSection(installDriftSection(census), false),
|
|
191
|
+
"",
|
|
192
|
+
`Covered — all six guards, a current copy, and an apply receipt: ${summary.covered} of ${summary.total}.`,
|
|
193
|
+
"This census reports and never gates: its exit status is 0 whatever it finds.",
|
|
194
|
+
"A checkout not on the roster was not measured, which is not the same as covered.",
|
|
195
|
+
];
|
|
196
|
+
return `${lines.join("\n")}\n`;
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=enforcement-census-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enforcement-census-report.js","sourceRoot":"","sources":["../../src/core/enforcement-census-report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { CheckoutCoverage } from "./enforcement-coverage.js";
|
|
2
|
+
/** Where a checkout on the roster came from. */
|
|
3
|
+
export type RosterSource = "roster" | "scan" | "argument";
|
|
4
|
+
/** One checkout the census intends to look at. */
|
|
5
|
+
export interface RosterEntry {
|
|
6
|
+
/** Operator-facing label. */
|
|
7
|
+
readonly label: string;
|
|
8
|
+
/** Absolute path to look at. */
|
|
9
|
+
readonly checkoutPath: string;
|
|
10
|
+
/** How this entry reached the roster. */
|
|
11
|
+
readonly source: RosterSource;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The disjoint classes a fleet falls into.
|
|
15
|
+
*
|
|
16
|
+
* `unreadable`, `unguarded`, `partial` and `full` partition the roster — they
|
|
17
|
+
* sum to `total` by construction. `behind`, `undateable` and `current`
|
|
18
|
+
* partition the *resolving* subset only, and never see an unguarded or
|
|
19
|
+
* unreadable checkout.
|
|
20
|
+
*/
|
|
21
|
+
export interface FleetCensusSummary {
|
|
22
|
+
/** Checkouts the census set out to look at. */
|
|
23
|
+
readonly total: number;
|
|
24
|
+
/** Checkouts it could not look into. Never counted as covered. */
|
|
25
|
+
readonly unreadable: number;
|
|
26
|
+
/** Checkouts that resolve NO guard. Not stale — unenforced. */
|
|
27
|
+
readonly unguarded: number;
|
|
28
|
+
/** Checkouts that resolve some guards but not all six. */
|
|
29
|
+
readonly partial: number;
|
|
30
|
+
/** Checkouts that resolve all six guards. */
|
|
31
|
+
readonly full: number;
|
|
32
|
+
/** Checkouts resolving at least one guard. */
|
|
33
|
+
readonly resolving: number;
|
|
34
|
+
/** Resolving checkouts whose governing copy is behind the reference. */
|
|
35
|
+
readonly behind: number;
|
|
36
|
+
/** Resolving checkouts whose governing copy cannot be dated at all. */
|
|
37
|
+
readonly undateable: number;
|
|
38
|
+
/** Resolving checkouts whose governing copy is current. */
|
|
39
|
+
readonly current: number;
|
|
40
|
+
/** Resolving checkouts carrying no apply receipt. */
|
|
41
|
+
readonly withoutReceipt: number;
|
|
42
|
+
/** Checkouts whose installed Lisa is behind the version they declare. */
|
|
43
|
+
readonly installBehindDeclared: number;
|
|
44
|
+
/** All six guards, current copy, apply receipt present. */
|
|
45
|
+
readonly covered: number;
|
|
46
|
+
}
|
|
47
|
+
/** One complete census run. */
|
|
48
|
+
export interface FleetCensus {
|
|
49
|
+
/** When the census ran. */
|
|
50
|
+
readonly measuredAt: string;
|
|
51
|
+
/** Newest Lisa the run could point at, and what proved it. */
|
|
52
|
+
readonly reference: string | null;
|
|
53
|
+
/** Where the reference came from, in the operator's language. */
|
|
54
|
+
readonly referenceSource: string;
|
|
55
|
+
/** Where the roster came from. */
|
|
56
|
+
readonly rosterOrigin: string;
|
|
57
|
+
/** One record per checkout, in roster order. */
|
|
58
|
+
readonly checkouts: readonly CheckoutCoverage[];
|
|
59
|
+
/** The disjoint classes. */
|
|
60
|
+
readonly summary: FleetCensusSummary;
|
|
61
|
+
}
|
|
62
|
+
/** The fleet roster `scripts/lisa-update-local.sh` already maintains. */
|
|
63
|
+
export declare const DEFAULT_ROSTER_FILE = ".lisa.workspaces.json";
|
|
64
|
+
/**
|
|
65
|
+
* Read a fleet roster file.
|
|
66
|
+
*
|
|
67
|
+
* The primary shape is the one already on disk: an object mapping checkout path
|
|
68
|
+
* to target branch, which `scripts/lisa-update-local.sh` reads. An array of
|
|
69
|
+
* paths is accepted too, so a census can be pointed at an ad-hoc list without
|
|
70
|
+
* inventing a branch for each entry.
|
|
71
|
+
* @param rosterPath - Path to the roster file
|
|
72
|
+
* @returns The checkouts it names, or null when it cannot be read
|
|
73
|
+
*/
|
|
74
|
+
export declare function readFleetRoster(rosterPath: string): Promise<readonly RosterEntry[] | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Find checkouts under a directory, without a roster.
|
|
77
|
+
*
|
|
78
|
+
* Discovery exists because a hand-maintained roster can go stale the same way
|
|
79
|
+
* the frozen comment did: a checkout nobody added is a checkout the census
|
|
80
|
+
* cannot see, and an unseen checkout reads as coverage it does not have. A
|
|
81
|
+
* candidate is any directory containing a `.git` entry, found no deeper than
|
|
82
|
+
* `depth` levels so the walk stays bounded on a large disk.
|
|
83
|
+
* @param root - Directory to search
|
|
84
|
+
* @param depth - How many levels below `root` to descend
|
|
85
|
+
* @returns The checkouts found, in listing order
|
|
86
|
+
*/
|
|
87
|
+
export declare function scanForCheckouts(root: string, depth: number): Promise<readonly RosterEntry[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Collapse entries that name the same checkout twice.
|
|
90
|
+
*
|
|
91
|
+
* Two roster spellings of one directory, or a roster entry a scan also found,
|
|
92
|
+
* would otherwise be counted twice — inflating both the numerator and the
|
|
93
|
+
* denominator and making the census disagree with itself.
|
|
94
|
+
* @param entries - Entries from every source
|
|
95
|
+
* @returns One entry per distinct checkout, first spelling wins
|
|
96
|
+
*/
|
|
97
|
+
export declare function dedupeRoster(entries: readonly RosterEntry[]): Promise<readonly RosterEntry[]>;
|
|
98
|
+
/**
|
|
99
|
+
* Count the resolving checkouts whose governing copy is behind.
|
|
100
|
+
*
|
|
101
|
+
* Defined over the resolving set on purpose, and named so that folding the
|
|
102
|
+
* unguarded class into it is a visible edit rather than an accident. A checkout
|
|
103
|
+
* that resolves nothing has no copy to be behind: calling it stale describes a
|
|
104
|
+
* guard that would refuse things, and there is none.
|
|
105
|
+
* @param checkouts - Every record in the run
|
|
106
|
+
* @returns How many resolving checkouts are behind the reference
|
|
107
|
+
*/
|
|
108
|
+
export declare function countStale(checkouts: readonly CheckoutCoverage[]): number;
|
|
109
|
+
/**
|
|
110
|
+
* Reduce a run to its disjoint classes.
|
|
111
|
+
* @param checkouts - Every record in the run
|
|
112
|
+
* @returns The summary
|
|
113
|
+
*/
|
|
114
|
+
export declare function summarizeFleetCensus(checkouts: readonly CheckoutCoverage[]): FleetCensusSummary;
|
|
115
|
+
/**
|
|
116
|
+
* Whether a checkout's installed Lisa is older than the one it declares.
|
|
117
|
+
*
|
|
118
|
+
* The comparison is against the range's own floor, read as a plain version once
|
|
119
|
+
* the usual range punctuation is stripped. A range this cannot parse yields
|
|
120
|
+
* `false` — absence of proof never becomes proof of drift.
|
|
121
|
+
* @param coverage - One record
|
|
122
|
+
* @returns Whether the installed copy is behind the declared one
|
|
123
|
+
*/
|
|
124
|
+
export declare function isInstallBehindDeclared(coverage: CheckoutCoverage): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the newest Lisa this run can point at.
|
|
127
|
+
*
|
|
128
|
+
* A maximum over evidence found on the same disk during the same run, never a
|
|
129
|
+
* network lookup and never a nominated authority. A copy is reported behind
|
|
130
|
+
* only when something newer can actually be pointed at, so the worst case is
|
|
131
|
+
* silence rather than a fleet-wide false alarm.
|
|
132
|
+
* @param checkouts - Records collected with no reference
|
|
133
|
+
* @param seed - A version the caller can vouch for, such as its own manifest
|
|
134
|
+
* @returns The reference version and what proved it
|
|
135
|
+
*/
|
|
136
|
+
export declare function resolveReference(checkouts: readonly CheckoutCoverage[], seed: string | null): {
|
|
137
|
+
readonly version: string | null;
|
|
138
|
+
readonly source: string;
|
|
139
|
+
};
|
|
140
|
+
/** Everything a census run needs. */
|
|
141
|
+
export interface FleetCensusOptions {
|
|
142
|
+
/** Checkouts to look at, already deduped. */
|
|
143
|
+
readonly roster: readonly RosterEntry[];
|
|
144
|
+
/** Where that roster came from, for the report header. */
|
|
145
|
+
readonly rosterOrigin: string;
|
|
146
|
+
/** A version the caller can vouch for, folded into the reference. */
|
|
147
|
+
readonly seedVersion?: string | null;
|
|
148
|
+
/** An explicit reference, which suppresses the maximum entirely. */
|
|
149
|
+
readonly reference?: string | null;
|
|
150
|
+
/** Clock seam, so the report is reproducible under test. */
|
|
151
|
+
readonly now?: () => Date;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Run the census.
|
|
155
|
+
*
|
|
156
|
+
* Never throws for a fleet finding and never returns a status: the caller
|
|
157
|
+
* prints this and exits 0. The only thing that can fail here is the census
|
|
158
|
+
* itself, and a checkout it cannot read comes back as `unreadable` rather than
|
|
159
|
+
* as an exception, because one unreachable directory must not stop the other
|
|
160
|
+
* twenty-six from being measured.
|
|
161
|
+
* @param options - Roster and reference inputs
|
|
162
|
+
* @returns The measured fleet
|
|
163
|
+
*/
|
|
164
|
+
export declare function runFleetCensus(options: FleetCensusOptions): Promise<FleetCensus>;
|
|
165
|
+
/**
|
|
166
|
+
* A stable, path-free label for a checkout.
|
|
167
|
+
*
|
|
168
|
+
* The census names real local checkouts, and those paths embed client and
|
|
169
|
+
* product names that must never reach a public issue or PR. Redaction keeps the
|
|
170
|
+
* counts and the per-checkout rows — which is all a fleet finding needs — while
|
|
171
|
+
* making the report safe to paste. It is derived from the path, so the same
|
|
172
|
+
* checkout carries the same label between runs and can still be discussed.
|
|
173
|
+
* @param checkoutPath - The real path
|
|
174
|
+
* @returns A stable anonymous label
|
|
175
|
+
*/
|
|
176
|
+
export declare function redactedLabel(checkoutPath: string): string;
|
|
177
|
+
//# sourceMappingURL=enforcement-census.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enforcement-census.d.ts","sourceRoot":"","sources":["../../src/core/enforcement-census.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|