patchwork-os 1.2.0-beta.2.canary.733 → 1.2.0-beta.2.canary.735

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.
@@ -39,7 +39,7 @@
39
39
  * rows the grader has not earned the right to be evidence.
40
40
  */
41
41
  import { type GradeOptions, type ObservedErrandArtifact } from "./errandOutcomeGrader.js";
42
- import { type ShadowSummary } from "./outcomeShadowLog.js";
42
+ import { type ShadowOutcomeRow, type ShadowSummary } from "./outcomeShadowLog.js";
43
43
  /** One artifact to grade, plus the key it joins on. */
44
44
  export interface ErrandObservation extends ObservedErrandArtifact {
45
45
  /**
@@ -92,4 +92,32 @@ export declare function ingestErrandOutcomes(observations: readonly ErrandObserv
92
92
  * next to the number, because the number's whole purpose is to be weighed
93
93
  * against it.
94
94
  */
95
+ /**
96
+ * Render the individual graded rows for review.
97
+ *
98
+ * `formatShadowSummary` ends by telling the operator to check a sample against
99
+ * the real errands before promoting. Until this existed there was no way to do
100
+ * that: `butler shadow` printed the aggregate, `--json` printed the same
101
+ * aggregate, and the only caller of `readShadowRows` in the tree was
102
+ * `promoteShadowOutcomes` — the irreversible step. The single piece of code
103
+ * that read the rows was the one that acts on them.
104
+ *
105
+ * That is worth more than a missing flag because promotion is ONE-WAY: trust
106
+ * replay absorbs a folded row into a checkpoint that deleting the row does not
107
+ * undo. "Review before promoting" is the entire safety property of the shadow
108
+ * phase, and it was advice with no surface behind it.
109
+ *
110
+ * Rows that WOULD become evidence are printed first, because those are the only
111
+ * ones promotion acts on — sorting them under a wall of withheld `unknown` rows
112
+ * (8 of 9 on the real ledger when this was written) buries the decision.
113
+ *
114
+ * ## Output is operator data
115
+ *
116
+ * Same rule as `runstore compare` and `privacy receipts`: rows name real
117
+ * installed recipes and carry external record ids for the operator's own
118
+ * errands. Quote a measurement; never paste the rows.
119
+ */
120
+ export declare function formatShadowRows(rows: readonly ShadowOutcomeRow[], opts?: {
121
+ limit?: number;
122
+ }): string;
95
123
  export declare function formatShadowSummary(s: ShadowSummary): string;
@@ -99,6 +99,81 @@ export function ingestErrandOutcomes(observations, opts) {
99
99
  * next to the number, because the number's whole purpose is to be weighed
100
100
  * against it.
101
101
  */
102
+ /**
103
+ * Render the individual graded rows for review.
104
+ *
105
+ * `formatShadowSummary` ends by telling the operator to check a sample against
106
+ * the real errands before promoting. Until this existed there was no way to do
107
+ * that: `butler shadow` printed the aggregate, `--json` printed the same
108
+ * aggregate, and the only caller of `readShadowRows` in the tree was
109
+ * `promoteShadowOutcomes` — the irreversible step. The single piece of code
110
+ * that read the rows was the one that acts on them.
111
+ *
112
+ * That is worth more than a missing flag because promotion is ONE-WAY: trust
113
+ * replay absorbs a folded row into a checkpoint that deleting the row does not
114
+ * undo. "Review before promoting" is the entire safety property of the shadow
115
+ * phase, and it was advice with no surface behind it.
116
+ *
117
+ * Rows that WOULD become evidence are printed first, because those are the only
118
+ * ones promotion acts on — sorting them under a wall of withheld `unknown` rows
119
+ * (8 of 9 on the real ledger when this was written) buries the decision.
120
+ *
121
+ * ## Output is operator data
122
+ *
123
+ * Same rule as `runstore compare` and `privacy receipts`: rows name real
124
+ * installed recipes and carry external record ids for the operator's own
125
+ * errands. Quote a measurement; never paste the rows.
126
+ */
127
+ export function formatShadowRows(rows, opts = {}) {
128
+ if (rows.length === 0) {
129
+ return [
130
+ "[butler-shadow] no graded rows to review.",
131
+ "",
132
+ " Nothing has been measured, so there is nothing to check and nothing",
133
+ " may be promoted.",
134
+ ].join("\n");
135
+ }
136
+ // Evidence-bearing rows first, then most recently graded. Within the two
137
+ // groups the order is the ledger's own, so a reviewer can find a row again.
138
+ const ordered = [...rows].sort((a, b) => {
139
+ if (a.wouldCountAsEvidence !== b.wouldCountAsEvidence) {
140
+ return a.wouldCountAsEvidence ? -1 : 1;
141
+ }
142
+ return a.gradedAt - b.gradedAt;
143
+ });
144
+ const limit = opts.limit ?? ordered.length;
145
+ const shown = ordered.slice(0, Math.max(0, limit));
146
+ const hidden = ordered.length - shown.length;
147
+ const evidence = rows.filter((r) => r.wouldCountAsEvidence).length;
148
+ const lines = shown.map((r) => {
149
+ const when = new Date(r.gradedAt).toISOString().replace(".000Z", "Z");
150
+ // In words, not by colour or position alone — a reviewer piping this to a
151
+ // file must still be able to tell the two apart.
152
+ const mark = r.wouldCountAsEvidence
153
+ ? "would become evidence"
154
+ : "withheld ";
155
+ // `recipe` is optional. No placeholder that reads like a name: attributing
156
+ // an errand to something that did not file it is worse than saying nothing.
157
+ const who = r.recipe ? ` recipe=${r.recipe}` : "";
158
+ return ` ${when} ${mark} ${r.disposition.padEnd(9)} ${r.reason.padEnd(18)} ${r.ref}${who}`;
159
+ });
160
+ return [
161
+ `[butler-shadow] ${rows.length} graded row(s); ${evidence} would become evidence.`,
162
+ "",
163
+ ...lines,
164
+ ...(hidden > 0
165
+ ? [
166
+ "",
167
+ ` ${hidden} more row(s) not shown — raise --rows to see them. A sample`,
168
+ " that looks representative because it was truncated is not one.",
169
+ ]
170
+ : []),
171
+ "",
172
+ " These rows are OPERATOR DATA, not a diagnostic blob: they name real",
173
+ " installed recipes and carry external record ids. Quote a measurement if",
174
+ " you need to; never paste the rows into an issue, a PR or a fixture.",
175
+ ].join("\n");
176
+ }
102
177
  export function formatShadowSummary(s) {
103
178
  if (s.total === 0) {
104
179
  return [
@@ -1 +1 @@
1
- {"version":3,"file":"outcomeIngester.js","sourceRoot":"","sources":["../../src/butler/outcomeIngester.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"outcomeIngester.js","sourceRoot":"","sources":["../../src/butler/outcomeIngester.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -4638,6 +4638,38 @@ if (process.argv[2] === "butler") {
4638
4638
  const { formatShadowSummary, ingestErrandOutcomes } = await import("./butler/outcomeIngester.js");
4639
4639
  const { summariseShadowLog } = await import("./butler/outcomeShadowLog.js");
4640
4640
  if (args[0] === "shadow") {
4641
+ // `--rows` is the review surface the summary's own closing advice
4642
+ // requires ("check a sample against the real errands they describe").
4643
+ // Before it existed the only caller of `readShadowRows` was
4644
+ // `promoteShadowOutcomes` — the irreversible step — so the one piece of
4645
+ // code that read the rows was the one that acts on them.
4646
+ const rowsIdx = args.findIndex((a) => a === "--rows");
4647
+ if (rowsIdx !== -1) {
4648
+ const { formatShadowRows } = await import("./butler/outcomeIngester.js");
4649
+ const { readShadowRows } = await import("./butler/outcomeShadowLog.js");
4650
+ const rows = readShadowRows();
4651
+ // `--rows` alone shows every row; `--rows N` caps it. A bad N is
4652
+ // rejected rather than silently treated as "all", which would show
4653
+ // more of the operator's data than they asked for.
4654
+ const raw = args[rowsIdx + 1];
4655
+ let limit;
4656
+ if (raw !== undefined && !raw.startsWith("--")) {
4657
+ const n = Number(raw);
4658
+ if (!Number.isInteger(n) || n <= 0) {
4659
+ process.stderr.write(`[butler-shadow] --rows expects a positive integer, got "${raw}"\n`);
4660
+ process.exit(1);
4661
+ }
4662
+ limit = n;
4663
+ }
4664
+ if (args.includes("--json")) {
4665
+ const out = limit === undefined ? rows : rows.slice(0, limit);
4666
+ process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
4667
+ }
4668
+ else {
4669
+ process.stdout.write(`${formatShadowRows(rows, limit === undefined ? {} : { limit })}\n`);
4670
+ }
4671
+ process.exit(0);
4672
+ }
4641
4673
  const summary = summariseShadowLog();
4642
4674
  if (args.includes("--json")) {
4643
4675
  process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);