patchwork-os 1.2.0-beta.2.canary.764 → 1.2.0-beta.2.canary.765
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/deploymentFreshness.d.ts +18 -0
- package/dist/deploymentFreshness.js +94 -0
- package/dist/deploymentFreshness.js.map +1 -1
- package/dist/index.js +75 -102
- package/dist/index.js.map +1 -1
- package/dist/maintenance/prOutcomeLedger.d.ts +12 -1
- package/dist/maintenance/prOutcomeLedger.js +28 -0
- package/dist/maintenance/prOutcomeLedger.js.map +1 -1
- package/dist/maintenance/sweep.d.ts +98 -0
- package/dist/maintenance/sweep.js +197 -0
- package/dist/maintenance/sweep.js.map +1 -0
- package/dist/maintenance/sweepCollect.d.ts +33 -0
- package/dist/maintenance/sweepCollect.js +109 -0
- package/dist/maintenance/sweepCollect.js.map +1 -0
- package/dist/privacy/undeclaredSteps.d.ts +14 -0
- package/dist/privacy/undeclaredSteps.js +44 -0
- package/dist/privacy/undeclaredSteps.js.map +1 -1
- package/package.json +2 -2
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
* Rows carry `rv: 1` so a later schema change is distinguishable from an old
|
|
37
37
|
* row, for the same reason the gate ledger does.
|
|
38
38
|
*/
|
|
39
|
-
/** Schema version. A reader must be able to tell an old row from a new one. */
|
|
40
39
|
export declare const PR_OBSERVATION_RV = 1;
|
|
41
40
|
export type PrState = "OPEN" | "MERGED" | "CLOSED";
|
|
42
41
|
/** One pull request as the API described it at one moment. */
|
|
@@ -161,3 +160,15 @@ export declare function resolveRepoSlug(run: () => {
|
|
|
161
160
|
ok: false;
|
|
162
161
|
stderr: string;
|
|
163
162
|
}): RepoResolution;
|
|
163
|
+
/**
|
|
164
|
+
* Read the observation ledger. Missing file ⇒ empty, which is a true state on
|
|
165
|
+
* day one and not an error.
|
|
166
|
+
*
|
|
167
|
+
* Extracted from the `pr-outcomes show` handler so `patchwork sweep` reads the
|
|
168
|
+
* ledger through the same parser the verb uses — a second reader would drift,
|
|
169
|
+
* and a drifting row count is indistinguishable from a quiet week.
|
|
170
|
+
*
|
|
171
|
+
* A corrupt line is skipped, never repaired: rewriting an append-only evidence
|
|
172
|
+
* file to make it parse is how evidence quietly becomes fiction.
|
|
173
|
+
*/
|
|
174
|
+
export declare function readObservations(file: string): PrObservation[];
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
* row, for the same reason the gate ledger does.
|
|
38
38
|
*/
|
|
39
39
|
/** Schema version. A reader must be able to tell an old row from a new one. */
|
|
40
|
+
import * as nodeFs from "node:fs";
|
|
40
41
|
export const PR_OBSERVATION_RV = 1;
|
|
41
42
|
function num(v) {
|
|
42
43
|
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
@@ -218,4 +219,31 @@ export function resolveRepoSlug(run) {
|
|
|
218
219
|
}
|
|
219
220
|
return { repo: slug };
|
|
220
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Read the observation ledger. Missing file ⇒ empty, which is a true state on
|
|
224
|
+
* day one and not an error.
|
|
225
|
+
*
|
|
226
|
+
* Extracted from the `pr-outcomes show` handler so `patchwork sweep` reads the
|
|
227
|
+
* ledger through the same parser the verb uses — a second reader would drift,
|
|
228
|
+
* and a drifting row count is indistinguishable from a quiet week.
|
|
229
|
+
*
|
|
230
|
+
* A corrupt line is skipped, never repaired: rewriting an append-only evidence
|
|
231
|
+
* file to make it parse is how evidence quietly becomes fiction.
|
|
232
|
+
*/
|
|
233
|
+
export function readObservations(file) {
|
|
234
|
+
if (!nodeFs.existsSync(file))
|
|
235
|
+
return [];
|
|
236
|
+
const out = [];
|
|
237
|
+
for (const line of nodeFs.readFileSync(file, "utf-8").split("\n")) {
|
|
238
|
+
if (line.trim() === "")
|
|
239
|
+
continue;
|
|
240
|
+
try {
|
|
241
|
+
out.push(JSON.parse(line));
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// skipped, never repaired
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
221
249
|
//# sourceMappingURL=prOutcomeLedger.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prOutcomeLedger.js","sourceRoot":"","sources":["../../src/maintenance/prOutcomeLedger.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prOutcomeLedger.js","sourceRoot":"","sources":["../../src/maintenance/prOutcomeLedger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `patchwork sweep` — what CHANGED since the last sweep?
|
|
3
|
+
*
|
|
4
|
+
* Five read-only verbs already answer "what is true now": `doctor`,
|
|
5
|
+
* `workers validate`, `evidence`, `privacy undeclared`, `pr-outcomes show`.
|
|
6
|
+
* Running them by hand answers each question in isolation and answers none of
|
|
7
|
+
* the question an operator actually has on a Monday, which is *what moved*.
|
|
8
|
+
* A denominator that has not shifted in three weeks and a gate that flipped
|
|
9
|
+
* yesterday look identical when every verb is read fresh.
|
|
10
|
+
*
|
|
11
|
+
* ## It is a REPORT with two gates, not five
|
|
12
|
+
*
|
|
13
|
+
* Only two readings here are gates: is the running code the installed code, and
|
|
14
|
+
* does every worker manifest actually govern something. Those are binary, they
|
|
15
|
+
* are currently green, and a flip is unambiguously a regression — so a flip
|
|
16
|
+
* exits 1.
|
|
17
|
+
*
|
|
18
|
+
* Everything else is DRIFT and never fails the command. The evidence-spine
|
|
19
|
+
* ratios fall by construction as ledgers accrue rows faster than runs earn
|
|
20
|
+
* correlation ids; an undeclared agent step is the documented fail-soft default
|
|
21
|
+
* of ADR-0021, not a fault. Wiring those to an exit code would make `sweep`
|
|
22
|
+
* permanently red, and a permanently-red gate is exactly how a real warning
|
|
23
|
+
* gets ignored — the failure this repo has now recorded several times.
|
|
24
|
+
*
|
|
25
|
+
* ## A first run is a BASELINE, never "no changes"
|
|
26
|
+
*
|
|
27
|
+
* With no prior snapshot there is nothing to diff, and saying "nothing changed"
|
|
28
|
+
* would be a claim about a week nobody observed. That state is named
|
|
29
|
+
* `baseline`, printed as such, and carried in the JSON, for the same reason
|
|
30
|
+
* `evidence` reports an absent ledger as ABSENT rather than as zero rows.
|
|
31
|
+
*
|
|
32
|
+
* ## The snapshot holds COUNTS ONLY
|
|
33
|
+
*
|
|
34
|
+
* This is a constraint, not a formality. Two of the five inputs return operator
|
|
35
|
+
* data — `privacy undeclared` names real installed recipes, `doctor` carries
|
|
36
|
+
* workspace paths — and a snapshot that accumulated them would be a brand-new
|
|
37
|
+
* operator-data ledger created by a diagnostic, growing weekly, unnoticed
|
|
38
|
+
* because nobody thinks of a health check as a place secrets collect. So the
|
|
39
|
+
* reading is reduced to integers and booleans at the boundary, and a test
|
|
40
|
+
* asserts the serialised snapshot contains no path- or name-shaped string.
|
|
41
|
+
*
|
|
42
|
+
* `rv` marks the snapshot schema. A later reading with a new counter must stay
|
|
43
|
+
* distinguishable from an old reading that never had one — absence is a fact
|
|
44
|
+
* here as it is in the gate decision log, and is never backfilled.
|
|
45
|
+
*/
|
|
46
|
+
/** Snapshot schema version. Bump when a counter's MEANING changes. */
|
|
47
|
+
export declare const SWEEP_RV = 1;
|
|
48
|
+
/** File the snapshots are appended to, relative to `$PATCHWORK_HOME`. */
|
|
49
|
+
export declare const SWEEP_LEDGER = "sweep_snapshots.jsonl";
|
|
50
|
+
/**
|
|
51
|
+
* One sweep reading, reduced to scalars.
|
|
52
|
+
*
|
|
53
|
+
* `gates` are the only fields that can produce a regression verdict. `counts` is
|
|
54
|
+
* a flat integer bag deliberately: adding a metric must not be a schema change
|
|
55
|
+
* that strands every prior snapshot, and a reader that finds a key missing
|
|
56
|
+
* learns "this sweep predates that counter" rather than "it was zero".
|
|
57
|
+
*/
|
|
58
|
+
export interface SweepReading {
|
|
59
|
+
rv: number;
|
|
60
|
+
takenAt: number;
|
|
61
|
+
gates: Record<string, boolean>;
|
|
62
|
+
counts: Record<string, number>;
|
|
63
|
+
}
|
|
64
|
+
export interface GateDelta {
|
|
65
|
+
key: string;
|
|
66
|
+
before: boolean;
|
|
67
|
+
after: boolean;
|
|
68
|
+
/** true only for a true -> false flip. A false -> true flip is a recovery. */
|
|
69
|
+
regression: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface CountDelta {
|
|
72
|
+
key: string;
|
|
73
|
+
/** undefined when the prior snapshot predates this counter. */
|
|
74
|
+
before?: number;
|
|
75
|
+
after: number;
|
|
76
|
+
change: number;
|
|
77
|
+
}
|
|
78
|
+
export interface SweepDelta {
|
|
79
|
+
/** No prior snapshot existed. Never rendered as "nothing changed". */
|
|
80
|
+
baseline: boolean;
|
|
81
|
+
previousAt?: number;
|
|
82
|
+
gates: GateDelta[];
|
|
83
|
+
counts: CountDelta[];
|
|
84
|
+
/** Counters present in the prior reading and absent from this one. */
|
|
85
|
+
disappeared: string[];
|
|
86
|
+
/** True when any gate flipped true -> false. Drives the exit code. */
|
|
87
|
+
regressed: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Diff two readings. Pure — no clock, no filesystem, so a test can drive it.
|
|
91
|
+
*/
|
|
92
|
+
export declare function diffReadings(previous: SweepReading | undefined, current: SweepReading): SweepDelta;
|
|
93
|
+
/** Last snapshot in the ledger, or undefined when there is none. */
|
|
94
|
+
export declare function readLastSnapshot(dir: string): SweepReading | undefined;
|
|
95
|
+
export declare function appendSnapshot(dir: string, reading: SweepReading): void;
|
|
96
|
+
export declare function formatSweep(reading: SweepReading, delta: SweepDelta, opts: {
|
|
97
|
+
wrote: boolean;
|
|
98
|
+
}): string;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `patchwork sweep` — what CHANGED since the last sweep?
|
|
3
|
+
*
|
|
4
|
+
* Five read-only verbs already answer "what is true now": `doctor`,
|
|
5
|
+
* `workers validate`, `evidence`, `privacy undeclared`, `pr-outcomes show`.
|
|
6
|
+
* Running them by hand answers each question in isolation and answers none of
|
|
7
|
+
* the question an operator actually has on a Monday, which is *what moved*.
|
|
8
|
+
* A denominator that has not shifted in three weeks and a gate that flipped
|
|
9
|
+
* yesterday look identical when every verb is read fresh.
|
|
10
|
+
*
|
|
11
|
+
* ## It is a REPORT with two gates, not five
|
|
12
|
+
*
|
|
13
|
+
* Only two readings here are gates: is the running code the installed code, and
|
|
14
|
+
* does every worker manifest actually govern something. Those are binary, they
|
|
15
|
+
* are currently green, and a flip is unambiguously a regression — so a flip
|
|
16
|
+
* exits 1.
|
|
17
|
+
*
|
|
18
|
+
* Everything else is DRIFT and never fails the command. The evidence-spine
|
|
19
|
+
* ratios fall by construction as ledgers accrue rows faster than runs earn
|
|
20
|
+
* correlation ids; an undeclared agent step is the documented fail-soft default
|
|
21
|
+
* of ADR-0021, not a fault. Wiring those to an exit code would make `sweep`
|
|
22
|
+
* permanently red, and a permanently-red gate is exactly how a real warning
|
|
23
|
+
* gets ignored — the failure this repo has now recorded several times.
|
|
24
|
+
*
|
|
25
|
+
* ## A first run is a BASELINE, never "no changes"
|
|
26
|
+
*
|
|
27
|
+
* With no prior snapshot there is nothing to diff, and saying "nothing changed"
|
|
28
|
+
* would be a claim about a week nobody observed. That state is named
|
|
29
|
+
* `baseline`, printed as such, and carried in the JSON, for the same reason
|
|
30
|
+
* `evidence` reports an absent ledger as ABSENT rather than as zero rows.
|
|
31
|
+
*
|
|
32
|
+
* ## The snapshot holds COUNTS ONLY
|
|
33
|
+
*
|
|
34
|
+
* This is a constraint, not a formality. Two of the five inputs return operator
|
|
35
|
+
* data — `privacy undeclared` names real installed recipes, `doctor` carries
|
|
36
|
+
* workspace paths — and a snapshot that accumulated them would be a brand-new
|
|
37
|
+
* operator-data ledger created by a diagnostic, growing weekly, unnoticed
|
|
38
|
+
* because nobody thinks of a health check as a place secrets collect. So the
|
|
39
|
+
* reading is reduced to integers and booleans at the boundary, and a test
|
|
40
|
+
* asserts the serialised snapshot contains no path- or name-shaped string.
|
|
41
|
+
*
|
|
42
|
+
* `rv` marks the snapshot schema. A later reading with a new counter must stay
|
|
43
|
+
* distinguishable from an old reading that never had one — absence is a fact
|
|
44
|
+
* here as it is in the gate decision log, and is never backfilled.
|
|
45
|
+
*/
|
|
46
|
+
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
47
|
+
import path from "node:path";
|
|
48
|
+
/** Snapshot schema version. Bump when a counter's MEANING changes. */
|
|
49
|
+
export const SWEEP_RV = 1;
|
|
50
|
+
/** File the snapshots are appended to, relative to `$PATCHWORK_HOME`. */
|
|
51
|
+
export const SWEEP_LEDGER = "sweep_snapshots.jsonl";
|
|
52
|
+
/**
|
|
53
|
+
* Diff two readings. Pure — no clock, no filesystem, so a test can drive it.
|
|
54
|
+
*/
|
|
55
|
+
export function diffReadings(previous, current) {
|
|
56
|
+
if (!previous) {
|
|
57
|
+
return {
|
|
58
|
+
baseline: true,
|
|
59
|
+
gates: [],
|
|
60
|
+
counts: [],
|
|
61
|
+
disappeared: [],
|
|
62
|
+
regressed: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const gates = [];
|
|
66
|
+
for (const [key, after] of Object.entries(current.gates)) {
|
|
67
|
+
const before = previous.gates[key];
|
|
68
|
+
// A gate the previous reading did not carry is not a flip. Treating a
|
|
69
|
+
// missing key as `false` would report every newly-added gate as a
|
|
70
|
+
// regression on its first sweep.
|
|
71
|
+
if (typeof before !== "boolean" || before === after)
|
|
72
|
+
continue;
|
|
73
|
+
gates.push({ key, before, after, regression: before && !after });
|
|
74
|
+
}
|
|
75
|
+
const counts = [];
|
|
76
|
+
for (const [key, after] of Object.entries(current.counts)) {
|
|
77
|
+
const before = previous.counts[key];
|
|
78
|
+
if (typeof before !== "number") {
|
|
79
|
+
counts.push({ key, after, change: after });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (before === after)
|
|
83
|
+
continue;
|
|
84
|
+
counts.push({ key, before, after, change: after - before });
|
|
85
|
+
}
|
|
86
|
+
const disappeared = Object.keys(previous.counts).filter((k) => !(k in current.counts));
|
|
87
|
+
return {
|
|
88
|
+
baseline: false,
|
|
89
|
+
previousAt: previous.takenAt,
|
|
90
|
+
gates,
|
|
91
|
+
counts,
|
|
92
|
+
disappeared,
|
|
93
|
+
regressed: gates.some((g) => g.regression),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Last snapshot in the ledger, or undefined when there is none. */
|
|
97
|
+
export function readLastSnapshot(dir) {
|
|
98
|
+
const file = path.join(dir, SWEEP_LEDGER);
|
|
99
|
+
if (!existsSync(file))
|
|
100
|
+
return undefined;
|
|
101
|
+
let last;
|
|
102
|
+
for (const line of readFileSync(file, "utf-8").split("\n")) {
|
|
103
|
+
if (!line.trim())
|
|
104
|
+
continue;
|
|
105
|
+
try {
|
|
106
|
+
const row = JSON.parse(line);
|
|
107
|
+
// A row from a future schema is skipped rather than diffed against: its
|
|
108
|
+
// counters may share a name with a different meaning, and a delta built
|
|
109
|
+
// across that boundary would be a confident lie.
|
|
110
|
+
if (typeof row?.takenAt !== "number" || row.rv !== SWEEP_RV)
|
|
111
|
+
continue;
|
|
112
|
+
last = row;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// A corrupt line is skipped, never repaired. Rewriting an append-only
|
|
116
|
+
// evidence file to make it parse is how evidence stops being evidence.
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return last;
|
|
120
|
+
}
|
|
121
|
+
export function appendSnapshot(dir, reading) {
|
|
122
|
+
appendFileSync(path.join(dir, SWEEP_LEDGER), `${JSON.stringify(reading)}\n`, {
|
|
123
|
+
mode: 0o600,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const CHECK = "✓";
|
|
127
|
+
export function formatSweep(reading, delta, opts) {
|
|
128
|
+
const L = [];
|
|
129
|
+
L.push("[sweep] what moved since the last sweep?");
|
|
130
|
+
L.push("");
|
|
131
|
+
L.push(" gates");
|
|
132
|
+
for (const [key, ok] of Object.entries(reading.gates)) {
|
|
133
|
+
L.push(` ${ok ? `${CHECK} ` : "FAIL"} ${key}`);
|
|
134
|
+
}
|
|
135
|
+
L.push("");
|
|
136
|
+
if (delta.baseline) {
|
|
137
|
+
// Never "no changes". Nothing was observed to change; there was nothing to
|
|
138
|
+
// compare against, and those are different facts.
|
|
139
|
+
L.push(" BASELINE — no prior snapshot, so nothing is compared here.");
|
|
140
|
+
L.push(" Run it again later and this section becomes the deltas.");
|
|
141
|
+
L.push("");
|
|
142
|
+
L.push(` ${Object.keys(reading.counts).length} counter(s) recorded:`);
|
|
143
|
+
for (const [key, v] of Object.entries(reading.counts)) {
|
|
144
|
+
L.push(` ${String(v).padStart(7)} ${key}`);
|
|
145
|
+
}
|
|
146
|
+
L.push("");
|
|
147
|
+
L.push(opts.wrote
|
|
148
|
+
? ` Snapshot appended to ${SWEEP_LEDGER}.`
|
|
149
|
+
: " Not written (--no-write): the next sweep will still see no baseline.");
|
|
150
|
+
L.push("");
|
|
151
|
+
return `${L.join("\n")}\n`;
|
|
152
|
+
}
|
|
153
|
+
const since = delta.previousAt
|
|
154
|
+
? new Date(delta.previousAt).toISOString()
|
|
155
|
+
: "unknown";
|
|
156
|
+
L.push(` since ${since}`);
|
|
157
|
+
L.push("");
|
|
158
|
+
if (delta.gates.length === 0) {
|
|
159
|
+
L.push(" No gate changed state.");
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
for (const g of delta.gates) {
|
|
163
|
+
L.push(` ${g.regression ? "REGRESSED" : "recovered "} ${g.key}: ${g.before} -> ${g.after}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
L.push("");
|
|
167
|
+
if (delta.counts.length === 0) {
|
|
168
|
+
L.push(" No counter moved.");
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
L.push(" drift (reported, never fatal)");
|
|
172
|
+
for (const c of delta.counts) {
|
|
173
|
+
const before = c.before === undefined ? "new" : String(c.before);
|
|
174
|
+
const sign = c.change > 0 ? "+" : "";
|
|
175
|
+
L.push(` ${before.padStart(7)} -> ${String(c.after).padEnd(7)} ${sign}${c.change} ${c.key}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (delta.disappeared.length > 0) {
|
|
179
|
+
L.push("");
|
|
180
|
+
L.push(` ${delta.disappeared.length} counter(s) the previous sweep had are absent now:`);
|
|
181
|
+
for (const k of delta.disappeared)
|
|
182
|
+
L.push(` ${k}`);
|
|
183
|
+
L.push(" A counter that stops being reported is not a counter that hit 0.");
|
|
184
|
+
}
|
|
185
|
+
L.push("");
|
|
186
|
+
if (delta.regressed) {
|
|
187
|
+
L.push(" A gate flipped from healthy to unhealthy. Exiting 1.");
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
L.push(" No gate regressed. Drift above is information, not a failure.");
|
|
191
|
+
}
|
|
192
|
+
if (opts.wrote)
|
|
193
|
+
L.push(` Snapshot appended to ${SWEEP_LEDGER}.`);
|
|
194
|
+
L.push("");
|
|
195
|
+
return `${L.join("\n")}\n`;
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=sweep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sweep.js","sourceRoot":"","sources":["../../src/maintenance/sweep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collect one `SweepReading` from the five read-only verbs.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from `sweep.ts` so the diff stays a pure function a test can drive.
|
|
5
|
+
* This half touches the filesystem and every subsystem; that half decides what a
|
|
6
|
+
* regression is.
|
|
7
|
+
*
|
|
8
|
+
* ## The reduction to scalars happens HERE, on purpose
|
|
9
|
+
*
|
|
10
|
+
* Two of these five inputs return operator data: `scanRecipeDir` names real
|
|
11
|
+
* installed recipes, and a bridge lock carries a workspace path. Reducing at the
|
|
12
|
+
* boundary — rather than storing the reports and projecting later — means there
|
|
13
|
+
* is no version of the snapshot that ever held them. A privacy rule enforced by
|
|
14
|
+
* a downstream formatter is one refactor away from being lost.
|
|
15
|
+
*/
|
|
16
|
+
import { type SweepReading } from "./sweep.js";
|
|
17
|
+
export interface CollectOptions {
|
|
18
|
+
/** Ledger directory. Defaults to `$PATCHWORK_HOME`. */
|
|
19
|
+
dir?: string;
|
|
20
|
+
/** How many live bridges ought to be up. Absent ⇒ no expectation. */
|
|
21
|
+
expectRunning?: number;
|
|
22
|
+
/** ms epoch for the reading. Injected so a test is not at the mercy of a clock. */
|
|
23
|
+
now?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Readings that could not be taken are OMITTED, never recorded as zero.
|
|
27
|
+
*
|
|
28
|
+
* A recipes directory that cannot be read and a recipes directory holding zero
|
|
29
|
+
* agent steps are different facts, and the diff already distinguishes an absent
|
|
30
|
+
* counter ("this sweep predates it, or could not take it") from a moved one.
|
|
31
|
+
* Writing a 0 would turn an unavailable reading into a dramatic-looking drop.
|
|
32
|
+
*/
|
|
33
|
+
export declare function collectSweep(opts?: CollectOptions): SweepReading;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collect one `SweepReading` from the five read-only verbs.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from `sweep.ts` so the diff stays a pure function a test can drive.
|
|
5
|
+
* This half touches the filesystem and every subsystem; that half decides what a
|
|
6
|
+
* regression is.
|
|
7
|
+
*
|
|
8
|
+
* ## The reduction to scalars happens HERE, on purpose
|
|
9
|
+
*
|
|
10
|
+
* Two of these five inputs return operator data: `scanRecipeDir` names real
|
|
11
|
+
* installed recipes, and a bridge lock carries a workspace path. Reducing at the
|
|
12
|
+
* boundary — rather than storing the reports and projecting later — means there
|
|
13
|
+
* is no version of the snapshot that ever held them. A privacy rule enforced by
|
|
14
|
+
* a downstream formatter is one refactor away from being lost.
|
|
15
|
+
*/
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { assessDeploymentFreshness, discoverLocks, installedBuildTimeMs, } from "../deploymentFreshness.js";
|
|
18
|
+
import { evidenceCoverage } from "../evidenceCoverage.js";
|
|
19
|
+
import { patchworkHome } from "../patchworkHome.js";
|
|
20
|
+
import { scanRecipeDir } from "../privacy/undeclaredSteps.js";
|
|
21
|
+
import { defaultRecipesDir, defaultWorkersDir, validateWorkers, } from "../workers/workersCli.js";
|
|
22
|
+
import { readObservations, summarise } from "./prOutcomeLedger.js";
|
|
23
|
+
import { SWEEP_RV } from "./sweep.js";
|
|
24
|
+
/**
|
|
25
|
+
* Readings that could not be taken are OMITTED, never recorded as zero.
|
|
26
|
+
*
|
|
27
|
+
* A recipes directory that cannot be read and a recipes directory holding zero
|
|
28
|
+
* agent steps are different facts, and the diff already distinguishes an absent
|
|
29
|
+
* counter ("this sweep predates it, or could not take it") from a moved one.
|
|
30
|
+
* Writing a 0 would turn an unavailable reading into a dramatic-looking drop.
|
|
31
|
+
*/
|
|
32
|
+
export function collectSweep(opts = {}) {
|
|
33
|
+
const dir = opts.dir ?? patchworkHome();
|
|
34
|
+
const counts = {};
|
|
35
|
+
const gates = {};
|
|
36
|
+
// --- gate 1: is the running code the installed code? ---
|
|
37
|
+
const buildTimeMs = installedBuildTimeMs();
|
|
38
|
+
if (buildTimeMs !== undefined) {
|
|
39
|
+
const fresh = assessDeploymentFreshness({
|
|
40
|
+
locks: discoverLocks(),
|
|
41
|
+
buildTimeMs,
|
|
42
|
+
isAlive: (pid) => {
|
|
43
|
+
try {
|
|
44
|
+
process.kill(pid, 0);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
...(opts.expectRunning !== undefined
|
|
52
|
+
? { expectRunning: opts.expectRunning }
|
|
53
|
+
: {}),
|
|
54
|
+
});
|
|
55
|
+
gates["deployment"] = !fresh.unhealthy;
|
|
56
|
+
counts["bridges.running"] = fresh.running;
|
|
57
|
+
counts["bridges.findings"] = fresh.findings.length;
|
|
58
|
+
}
|
|
59
|
+
// --- gate 2: does every worker manifest actually govern something? ---
|
|
60
|
+
try {
|
|
61
|
+
const v = validateWorkers({
|
|
62
|
+
workersDir: defaultWorkersDir(),
|
|
63
|
+
recipesDir: defaultRecipesDir(),
|
|
64
|
+
});
|
|
65
|
+
gates["workers"] = v.healthy;
|
|
66
|
+
counts["workers.loaded"] = v.counts.loaded;
|
|
67
|
+
counts["workers.broken"] = v.counts.broken;
|
|
68
|
+
counts["workers.findings"] = v.findings.length;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// No workers directory is a legitimate install. Omit rather than zero.
|
|
72
|
+
}
|
|
73
|
+
// --- drift: evidence spine ---
|
|
74
|
+
const ev = evidenceCoverage(dir);
|
|
75
|
+
for (const l of ev.ledgers) {
|
|
76
|
+
// An absent ledger contributes NO counters. `evidence` reports absence as
|
|
77
|
+
// ABSENT rather than as `0 rows` for a reason — permission_exercises is
|
|
78
|
+
// absent because no standing permission has ever been granted, and that is
|
|
79
|
+
// correct, not a gap. A zero here would invite someone to go and fix it.
|
|
80
|
+
if (l.absent)
|
|
81
|
+
continue;
|
|
82
|
+
counts[`evidence.${l.file}.rows`] = l.rows;
|
|
83
|
+
counts[`evidence.${l.file}.joinable`] = l.joinable;
|
|
84
|
+
}
|
|
85
|
+
counts["evidence.runsInMoreThanOneLedger"] = ev.runsInMoreThanOneLedger;
|
|
86
|
+
// --- drift: undeclared agent steps (ADR-0021 fail-soft, never a gate) ---
|
|
87
|
+
try {
|
|
88
|
+
const u = scanRecipeDir(path.join(dir, "recipes"));
|
|
89
|
+
counts["privacy.agentSteps"] = u.agentSteps;
|
|
90
|
+
counts["privacy.declared"] = u.declared;
|
|
91
|
+
counts["privacy.undeclared"] = u.undeclared.length;
|
|
92
|
+
counts["privacy.unreadableRecipes"] = u.unreadable.length;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Unreadable recipes directory: omit, do not zero.
|
|
96
|
+
}
|
|
97
|
+
// --- drift: pull-request outcome ledger ---
|
|
98
|
+
const pr = summarise(readObservations(path.join(dir, "pr_outcomes.jsonl")));
|
|
99
|
+
counts["prOutcomes.rows"] = pr.rows;
|
|
100
|
+
counts["prOutcomes.distinctPrs"] = pr.distinctPrs;
|
|
101
|
+
counts["prOutcomes.withHistory"] = pr.prsWithHistory;
|
|
102
|
+
return {
|
|
103
|
+
rv: SWEEP_RV,
|
|
104
|
+
takenAt: opts.now ?? Date.now(),
|
|
105
|
+
gates,
|
|
106
|
+
counts,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=sweepCollect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sweepCollect.js","sourceRoot":"","sources":["../../src/maintenance/sweepCollect.ts"],"names":[],"mappings":"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"}
|
|
@@ -74,3 +74,17 @@ export declare function undeclaredInRecipe(recipeName: string, parsed: unknown):
|
|
|
74
74
|
steps: UndeclaredStep[];
|
|
75
75
|
};
|
|
76
76
|
export declare function formatUndeclared(r: UndeclaredReport): string;
|
|
77
|
+
/**
|
|
78
|
+
* Scan a directory of recipe YAML and aggregate `undeclaredInRecipe` across it.
|
|
79
|
+
*
|
|
80
|
+
* Extracted from the `privacy undeclared` handler so `patchwork sweep` reads the
|
|
81
|
+
* same number the verb prints. Two implementations of "how many agent steps
|
|
82
|
+
* declare a data_policy" would drift, and the drift is silent: the sweep would
|
|
83
|
+
* report a delta of zero while the verb reported a regression, or vice versa,
|
|
84
|
+
* and neither would look wrong.
|
|
85
|
+
*
|
|
86
|
+
* Throws only when the directory cannot be read. A recipe that will not parse is
|
|
87
|
+
* REPORTED in `unreadable`, never skipped silently — a file present and ignored
|
|
88
|
+
* is the failure mode this whole family of verbs exists to surface.
|
|
89
|
+
*/
|
|
90
|
+
export declare function scanRecipeDir(dir: string): UndeclaredReport;
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
* Read-only. Output is OPERATOR DATA — it names real installed recipes — so it
|
|
35
35
|
* is quoted as a measurement and never pasted into an issue or a fixture.
|
|
36
36
|
*/
|
|
37
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
38
|
+
import path from "node:path";
|
|
39
|
+
import { parse as parseYaml } from "yaml";
|
|
37
40
|
function isRecord(v) {
|
|
38
41
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
39
42
|
}
|
|
@@ -163,4 +166,45 @@ export function formatUndeclared(r) {
|
|
|
163
166
|
L.push("");
|
|
164
167
|
return `${L.join("\n")}\n`;
|
|
165
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Scan a directory of recipe YAML and aggregate `undeclaredInRecipe` across it.
|
|
171
|
+
*
|
|
172
|
+
* Extracted from the `privacy undeclared` handler so `patchwork sweep` reads the
|
|
173
|
+
* same number the verb prints. Two implementations of "how many agent steps
|
|
174
|
+
* declare a data_policy" would drift, and the drift is silent: the sweep would
|
|
175
|
+
* report a delta of zero while the verb reported a regression, or vice versa,
|
|
176
|
+
* and neither would look wrong.
|
|
177
|
+
*
|
|
178
|
+
* Throws only when the directory cannot be read. A recipe that will not parse is
|
|
179
|
+
* REPORTED in `unreadable`, never skipped silently — a file present and ignored
|
|
180
|
+
* is the failure mode this whole family of verbs exists to surface.
|
|
181
|
+
*/
|
|
182
|
+
export function scanRecipeDir(dir) {
|
|
183
|
+
const entries = readdirSync(dir).sort();
|
|
184
|
+
let recipesScanned = 0;
|
|
185
|
+
let agentSteps = 0;
|
|
186
|
+
let declared = 0;
|
|
187
|
+
const undeclared = [];
|
|
188
|
+
const unreadable = [];
|
|
189
|
+
for (const f of entries) {
|
|
190
|
+
if (!/\.ya?ml$/i.test(f))
|
|
191
|
+
continue;
|
|
192
|
+
recipesScanned++;
|
|
193
|
+
let parsed;
|
|
194
|
+
try {
|
|
195
|
+
parsed = parseYaml(readFileSync(path.join(dir, f), "utf-8"));
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
unreadable.push(f);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const declaredName = parsed?.name;
|
|
202
|
+
const name = typeof declaredName === "string" ? declaredName : f;
|
|
203
|
+
const r = undeclaredInRecipe(name, parsed);
|
|
204
|
+
agentSteps += r.agentSteps;
|
|
205
|
+
declared += r.declared;
|
|
206
|
+
undeclared.push(...r.steps);
|
|
207
|
+
}
|
|
208
|
+
return { recipesScanned, agentSteps, declared, undeclared, unreadable };
|
|
209
|
+
}
|
|
166
210
|
//# sourceMappingURL=undeclaredSteps.js.map
|