patchwork-os 1.2.0-beta.2.canary.714 → 1.2.0-beta.2.canary.721

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.
@@ -89,7 +89,30 @@ export interface ShadowSummary {
89
89
  unknown: number;
90
90
  /** Rows that would have become evidence had the grader been live. */
91
91
  wouldCount: number;
92
+ /**
93
+ * Rows per grading reason.
94
+ *
95
+ * The disposition counts alone cannot answer the only question an operator
96
+ * has when `unknown` dominates: is this channel WORKING on errands that are
97
+ * simply still in flight, or is it seeing nothing at all? Those are
98
+ * `open-recent` and `not-observed`, and they collapse into the same
99
+ * `unknown` bucket while calling for opposite responses — wait, versus go
100
+ * and fix the observation path.
101
+ *
102
+ * The rows have always carried `reason`; only the summary discarded it. Same
103
+ * defect as #1469, where the shadow report named a defaulted-classification
104
+ * count and gave no way to find WHICH recipes to go and label.
105
+ */
106
+ byReason: Record<GradedOutcome["reason"], number>;
92
107
  }
108
+ /** Every reason, so a summary always reports the full space rather than only what it saw. */
109
+ export declare const SHADOW_REASONS: ReadonlyArray<GradedOutcome["reason"]>;
110
+ /**
111
+ * Reasons that mean "the observation channel could not answer", as opposed to
112
+ * "it answered, and the errand is not finished yet". Exported so the formatter
113
+ * and its tests cannot drift on which is which.
114
+ */
115
+ export declare const UNOBSERVED_REASONS: ReadonlyArray<GradedOutcome["reason"]>;
93
116
  /**
94
117
  * Summarise the shadow ledger — the number this phase exists to produce.
95
118
  *
@@ -104,6 +104,22 @@ export function firstSeenByRef(opts = {}) {
104
104
  }
105
105
  return out;
106
106
  }
107
+ /** Every reason, so a summary always reports the full space rather than only what it saw. */
108
+ export const SHADOW_REASONS = [
109
+ "completed",
110
+ "deleted",
111
+ "stale-unactioned",
112
+ "open-recent",
113
+ "not-observed",
114
+ ];
115
+ /**
116
+ * Reasons that mean "the observation channel could not answer", as opposed to
117
+ * "it answered, and the errand is not finished yet". Exported so the formatter
118
+ * and its tests cannot drift on which is which.
119
+ */
120
+ export const UNOBSERVED_REASONS = [
121
+ "not-observed",
122
+ ];
107
123
  /**
108
124
  * Summarise the shadow ledger — the number this phase exists to produce.
109
125
  *
@@ -112,12 +128,14 @@ export function firstSeenByRef(opts = {}) {
112
128
  * count someone is about to make a decision on.
113
129
  */
114
130
  export function summariseShadowLog(opts = {}) {
131
+ const emptyByReason = () => Object.fromEntries(SHADOW_REASONS.map((r) => [r, 0]));
115
132
  const empty = {
116
133
  total: 0,
117
134
  confirmed: 0,
118
135
  junk: 0,
119
136
  unknown: 0,
120
137
  wouldCount: 0,
138
+ byReason: emptyByReason(),
121
139
  };
122
140
  const p = shadowLogPath(opts.dir);
123
141
  if (!existsSync(p))
@@ -129,12 +147,23 @@ export function summariseShadowLog(opts = {}) {
129
147
  catch {
130
148
  return empty;
131
149
  }
132
- const out = { ...empty };
150
+ // Fresh `byReason` rather than sharing `empty`'s. NOT a live bug — `empty` is
151
+ // built per call, so the spread aliasing its object harms nothing today. It is
152
+ // here so that hoisting `empty` to module scope, an obvious tidy-up, cannot
153
+ // silently make every summary cumulative. Deliberately NOT covered by a test:
154
+ // no test can distinguish the two while `empty` stays local, and a test that
155
+ // passes either way would be worse than none.
156
+ const out = { ...empty, byReason: emptyByReason() };
133
157
  for (const row of parseShadowRows(text)) {
134
158
  out.total++;
135
159
  out[row.disposition]++;
136
160
  if (row.wouldCountAsEvidence)
137
161
  out.wouldCount++;
162
+ // A row whose reason is outside the known space is counted in `total` but
163
+ // not attributed. Silently coercing it into a known bucket would put a
164
+ // number a person acts on behind a guess.
165
+ if (row.reason in out.byReason)
166
+ out.byReason[row.reason]++;
138
167
  }
139
168
  return out;
140
169
  }
@@ -1 +1 @@
1
- {"version":3,"file":"outcomeShadowLog.js","sourceRoot":"","sources":["../../src/butler/outcomeShadowLog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"outcomeShadowLog.js","sourceRoot":"","sources":["../../src/butler/outcomeShadowLog.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,20 @@
31
31
  */
32
32
  export declare const SESSION_COOKIE_NAME = "patchwork_session";
33
33
  export declare const SESSION_TTL_MS: number;
34
+ /**
35
+ * Injection point for the Node host. Underscore-prefixed because it is not part
36
+ * of the session API — nothing but the bridge's startup and tests should call it.
37
+ */
38
+ export declare function __setSessionSecretFallback(secret: string | undefined): void;
39
+ /**
40
+ * Whether a secret is reachable at all, by either route.
41
+ *
42
+ * Exported so the bridge's wiring condition and this module cannot disagree
43
+ * about what "attribution is available" means. They were two separate reads of
44
+ * `process.env` before, which is precisely how one site gains a fallback and
45
+ * its neighbour silently does not.
46
+ */
47
+ export declare function hasSessionSecret(): boolean;
34
48
  /**
35
49
  * Member ids that are safe inside a dot-delimited cookie payload.
36
50
  *
@@ -31,8 +31,44 @@
31
31
  */
32
32
  export const SESSION_COOKIE_NAME = "patchwork_session";
33
33
  export const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
34
+ /**
35
+ * Secret supplied by the Node-side host when the environment does not carry
36
+ * one. Set by the bridge from `$PATCHWORK_HOME/.env` — see
37
+ * `sessionSecretFile.ts` for why that read cannot happen in this module.
38
+ *
39
+ * A plain variable, deliberately: no `node:` import may appear here, because
40
+ * the dashboard's Edge middleware imports this file and a build failure there
41
+ * locks every user out of the dashboard.
42
+ *
43
+ * The Edge consumer never calls the setter, so its behaviour is unchanged —
44
+ * `undefined` fallback, environment only.
45
+ */
46
+ let sessionSecretFallback;
47
+ /**
48
+ * Injection point for the Node host. Underscore-prefixed because it is not part
49
+ * of the session API — nothing but the bridge's startup and tests should call it.
50
+ */
51
+ export function __setSessionSecretFallback(secret) {
52
+ sessionSecretFallback = secret;
53
+ }
54
+ /**
55
+ * Whether a secret is reachable at all, by either route.
56
+ *
57
+ * Exported so the bridge's wiring condition and this module cannot disagree
58
+ * about what "attribution is available" means. They were two separate reads of
59
+ * `process.env` before, which is precisely how one site gains a fallback and
60
+ * its neighbour silently does not.
61
+ */
62
+ export function hasSessionSecret() {
63
+ return getSecret() !== "";
64
+ }
34
65
  function getSecret() {
35
- return process.env.DASHBOARD_SESSION_SECRET ?? "";
66
+ // Environment wins. An operator who exports the variable is making a
67
+ // deliberate choice, and a file on disk must not silently override it.
68
+ const fromEnv = process.env.DASHBOARD_SESSION_SECRET;
69
+ if (fromEnv !== undefined && fromEnv !== "")
70
+ return fromEnv;
71
+ return sessionSecretFallback ?? "";
36
72
  }
37
73
  function base64url(bytes) {
38
74
  const buf = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
@@ -1 +1 @@
1
- {"version":3,"file":"dashboardSession.js","sourceRoot":"","sources":["../../src/identity/dashboardSession.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dashboardSession.js","sourceRoot":"","sources":["../../src/identity/dashboardSession.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Read `DASHBOARD_SESSION_SECRET` out of `$PATCHWORK_HOME/.env` — the file
3
+ * `patchwork init` writes and the bridge never read.
4
+ *
5
+ * ## Why this module exists at all
6
+ *
7
+ * ADR-0020 attribution shipped and was unreachable on most launch paths. The
8
+ * dotenv loader in `src/index.ts` builds its candidates from `import.meta.url`,
9
+ * so it reads `<install-dir>/.env` — a path `npm run install:global` destroys
10
+ * on every reinstall — and never `$PATCHWORK_HOME/.env`. Measured on the live
11
+ * machine 2026-08-24: all three running bridges lacked the variable while the
12
+ * secret sat correctly on disk, so every approval they recorded was
13
+ * unattributed despite the operator having done exactly the right setup.
14
+ *
15
+ * `scripts/start-all.sh --no-tmux` is the one path that works, and only because
16
+ * it sources the file into the shell before launching. launchd, the tmux path
17
+ * and a bare `patchwork start` all leave attribution off.
18
+ *
19
+ * ## Why it reads ONE key rather than loading the file
20
+ *
21
+ * `claudeDriver.ts:295` spawns agent subprocesses with `{ ...process.env }`.
22
+ * Loading this file into the environment would hand `DASHBOARD_PASSWORD` and
23
+ * whatever else it holds to every agent the bridge runs, in order to deliver
24
+ * one unrelated value. So this returns the secret to its caller and writes
25
+ * nothing to `process.env`.
26
+ *
27
+ * ## Why it is not in `dashboardSession.ts`
28
+ *
29
+ * That module is imported by the dashboard's Edge middleware
30
+ * (`dashboard/src/lib/session.ts:43`), where `node:` imports are unavailable.
31
+ * A `node:fs` import there breaks the dashboard build in middleware, which is
32
+ * the one place a failure locks every user out. The read is Node-only, so it
33
+ * lives here and is injected.
34
+ */
35
+ /**
36
+ * Returns the secret, or `undefined` when the file, the key or the value is
37
+ * absent.
38
+ *
39
+ * Fail-soft by construction: an unreadable or malformed file yields
40
+ * `undefined`, which leaves attribution exactly where it was — off, with
41
+ * decisions recorded unattributed. That is the pre-existing behaviour and the
42
+ * correct one, since an absent actor already means "nobody recorded this" and
43
+ * must never be filled in with a guess.
44
+ */
45
+ export declare function readSessionSecretFromHome(dir?: string): string | undefined;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Read `DASHBOARD_SESSION_SECRET` out of `$PATCHWORK_HOME/.env` — the file
3
+ * `patchwork init` writes and the bridge never read.
4
+ *
5
+ * ## Why this module exists at all
6
+ *
7
+ * ADR-0020 attribution shipped and was unreachable on most launch paths. The
8
+ * dotenv loader in `src/index.ts` builds its candidates from `import.meta.url`,
9
+ * so it reads `<install-dir>/.env` — a path `npm run install:global` destroys
10
+ * on every reinstall — and never `$PATCHWORK_HOME/.env`. Measured on the live
11
+ * machine 2026-08-24: all three running bridges lacked the variable while the
12
+ * secret sat correctly on disk, so every approval they recorded was
13
+ * unattributed despite the operator having done exactly the right setup.
14
+ *
15
+ * `scripts/start-all.sh --no-tmux` is the one path that works, and only because
16
+ * it sources the file into the shell before launching. launchd, the tmux path
17
+ * and a bare `patchwork start` all leave attribution off.
18
+ *
19
+ * ## Why it reads ONE key rather than loading the file
20
+ *
21
+ * `claudeDriver.ts:295` spawns agent subprocesses with `{ ...process.env }`.
22
+ * Loading this file into the environment would hand `DASHBOARD_PASSWORD` and
23
+ * whatever else it holds to every agent the bridge runs, in order to deliver
24
+ * one unrelated value. So this returns the secret to its caller and writes
25
+ * nothing to `process.env`.
26
+ *
27
+ * ## Why it is not in `dashboardSession.ts`
28
+ *
29
+ * That module is imported by the dashboard's Edge middleware
30
+ * (`dashboard/src/lib/session.ts:43`), where `node:` imports are unavailable.
31
+ * A `node:fs` import there breaks the dashboard build in middleware, which is
32
+ * the one place a failure locks every user out. The read is Node-only, so it
33
+ * lives here and is injected.
34
+ */
35
+ import { existsSync, readFileSync } from "node:fs";
36
+ import path from "node:path";
37
+ import { patchworkHome } from "../patchworkHome.js";
38
+ const KEY = "DASHBOARD_SESSION_SECRET";
39
+ /**
40
+ * Returns the secret, or `undefined` when the file, the key or the value is
41
+ * absent.
42
+ *
43
+ * Fail-soft by construction: an unreadable or malformed file yields
44
+ * `undefined`, which leaves attribution exactly where it was — off, with
45
+ * decisions recorded unattributed. That is the pre-existing behaviour and the
46
+ * correct one, since an absent actor already means "nobody recorded this" and
47
+ * must never be filled in with a guess.
48
+ */
49
+ export function readSessionSecretFromHome(dir) {
50
+ const file = path.join(dir ?? patchworkHome(), ".env");
51
+ if (!existsSync(file))
52
+ return undefined;
53
+ let text;
54
+ try {
55
+ text = readFileSync(file, "utf-8");
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ for (const line of text.split("\n")) {
61
+ const trimmed = line.trim();
62
+ // A commented-out key is not a key. Reading one would silently arm
63
+ // attribution with a value the operator had deliberately disabled.
64
+ if (!trimmed || trimmed.startsWith("#"))
65
+ continue;
66
+ const eq = trimmed.indexOf("=");
67
+ if (eq <= 0)
68
+ continue;
69
+ if (trimmed.slice(0, eq).trim() !== KEY)
70
+ continue;
71
+ // Quote handling mirrors the loader in `src/index.ts` so the same file
72
+ // means the same thing to both readers. Deliberately does NOT strip inline
73
+ // comments: a `#` inside a generated hex secret is not a comment, and
74
+ // truncating a secret silently produces a value that verifies nothing.
75
+ const value = trimmed
76
+ .slice(eq + 1)
77
+ .trim()
78
+ .replace(/^["']|["']$/g, "");
79
+ return value === "" ? undefined : value;
80
+ }
81
+ return undefined;
82
+ }
83
+ //# sourceMappingURL=sessionSecretFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionSecretFile.js","sourceRoot":"","sources":["../../src/identity/sessionSecretFile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,GAAG,GAAG,0BAA0B,CAAC;AAEvC;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAY;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,aAAa,EAAE,EAAE,MAAM,CAAC,CAAC;IACvD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAExC,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,mEAAmE;QACnE,mEAAmE;QACnE,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAElD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS;QACtB,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,GAAG;YAAE,SAAS;QAElD,uEAAuE;QACvE,2EAA2E;QAC3E,sEAAsE;QACtE,uEAAuE;QACvE,MAAM,KAAK,GAAG,OAAO;aAClB,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;aACb,IAAI,EAAE;aACN,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;QAC/B,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
package/dist/index.js CHANGED
@@ -297,17 +297,30 @@ if (process.argv[2] === "--help" ||
297
297
  `Operate\n` +
298
298
  ` start [--port N] [--workspace <dir>] Start a single bridge (no tmux)\n` +
299
299
  ` status One-line bridge status (port, uptime)\n` +
300
+ ` dashboard Launch the local dashboard\n` +
301
+ ` members [list|set-password <id>] Workspace roster + per-member credentials\n` +
300
302
  ` tools [list|search <q>] [--slim] [--json] List tools the bridge would register\n` +
301
303
  ` analytics <show|configure|clear|test> Manage opt-in telemetry config\n` +
302
304
  ` launchd <install|uninstall|status> Manage the macOS auto-start LaunchAgent\n\n` +
303
305
  `Diagnose\n` +
304
306
  ` halts [--window 1h|24h|overnight|7d] Morning summary of recent recipe halts\n` +
305
307
  ` judgments [--window ...] [--recipe N] Recent judge-step verdicts across runs\n` +
308
+ ` privacy receipts [--since-days N] What the LIVE information boundary actually decided\n` +
306
309
  ` privacy shadow [--since-days N] What a candidate privacy policy would have stopped\n` +
307
310
  ` suggest [--since-days N] Recipe + unused-tool suggestions\n` +
308
311
  ` token-efficiency benchmark Measure token cost across tool sets\n` +
309
312
  ` traces export Bundle approval / recipe / decision traces\n` +
310
- ` print-token [--port N] Print the active bridge auth token\n\n` +
313
+ ` print-token [--port N] Print the active bridge auth token\n` +
314
+ ` runstore <backfill|compare> [--json] Durable run-store maintenance + drift check\n` +
315
+ ` shadow-scan [--since <d>] [--json] Reclassification scan over the run log\n\n` +
316
+ `Review\n` +
317
+ ` approvals [--window 1h|24h|7d] [--json] Recent approval decisions\n` +
318
+ ` approve <callId> / reject <callId> Decide one queued approval\n\n` +
319
+ `Butler\n` +
320
+ ` butler shadow [--json] Summarise the graded shadow ledger\n` +
321
+ ` butler observe [--stale-after-days N] Discover errands, look up live state, grade\n` +
322
+ ` butler ingest [--file <path>|-] Grade a JSON array of observations\n` +
323
+ ` butler promote [--dry-run] Fold grades into the trust ledger (flagged)\n\n` +
311
324
  `Safety\n` +
312
325
  ` kill-switch <engage|release|status> Block / resume write-tier tools across bridges\n` +
313
326
  ` panic [--reason "..."] Shorthand for kill-switch engage\n\n` +
@@ -319,7 +332,7 @@ if (process.argv[2] === "--help" ||
319
332
  ` --version, -v Print package version\n` +
320
333
  ` shim stdio↔WebSocket shim (used by MCP clients)\n` +
321
334
  ` notify <event> Notify a running bridge of a CC hook event\n\n` +
322
- `Bridge-daemon flags: run \`${binName} --workspace . --help-flags\` for the full list,\n` +
335
+ `Bridge-daemon flags: run \`${binName} --workspace . --help\` for the full list,\n` +
323
336
  `or see https://github.com/Oolab-labs/patchwork-os#readme.\n`);
324
337
  process.exit(0);
325
338
  }
@@ -4574,8 +4587,28 @@ if (process.argv[2] === "privacy") {
4574
4587
  }
4575
4588
  process.exit(0);
4576
4589
  }
4590
+ if (args[0] === "receipts") {
4591
+ // The ENFORCING ledger's reader. `shadow` answers "what would a candidate
4592
+ // policy have stopped"; this answers "what did the live policy actually
4593
+ // do" — the question the boundary was built to make answerable, and the
4594
+ // one nothing in this repo could answer until now (ADR-0019:88-92).
4595
+ const { summariseBoundaryReceipts, formatBoundaryReceipts } = await import("./privacy/boundaryReceipts.js");
4596
+ const i = args.indexOf("--since-days");
4597
+ const n = i !== -1 ? Number(args[i + 1]) : undefined;
4598
+ const summary = summariseBoundaryReceipts(n !== undefined && Number.isFinite(n) && n > 0
4599
+ ? { since: Date.now() - n * 86_400_000 }
4600
+ : {});
4601
+ if (args.includes("--json")) {
4602
+ process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
4603
+ }
4604
+ else {
4605
+ process.stdout.write(`${formatBoundaryReceipts(summary)}\n`);
4606
+ }
4607
+ process.exit(0);
4608
+ }
4577
4609
  if (args[0] !== "shadow") {
4578
- process.stderr.write("Usage: patchwork privacy shadow [--since-days N] [--json]\n" +
4610
+ process.stderr.write("Usage: patchwork privacy receipts [--since-days N] [--json]\n" +
4611
+ " patchwork privacy shadow [--since-days N] [--json]\n" +
4579
4612
  " patchwork privacy suggest [--json]\n" +
4580
4613
  "\n" +
4581
4614
  " Observes boundary decisions WITHOUT enforcing them. Configure a\n" +