patchwork-os 1.2.0-beta.2.canary.671 → 1.2.0-beta.2.canary.673

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.
@@ -0,0 +1,87 @@
1
+ import type { BoundaryDecision, Classification } from "./dataPolicy.js";
2
+ export declare const SHADOW_LOG_BASENAME = "privacy_shadow.jsonl";
3
+ /**
4
+ * The path the boundary DOES observe. Recorded on every row rather than
5
+ * assumed by the reader, so a future second observed path is a data change
6
+ * and not a silent redefinition of what the denominator counted.
7
+ */
8
+ export declare const OBSERVED_PATH = "recipe agent steps";
9
+ /**
10
+ * Paths that reach a model WITHOUT passing the decision point.
11
+ *
12
+ * This is not a caveat, it is part of the result. A crossing count computed
13
+ * over a partial surface reads as "your policy is fine" when it partly means
14
+ * "we did not look there" — and `agent` steps are ~3% of logged step volume,
15
+ * so the unobserved remainder is the larger part of the system.
16
+ *
17
+ * Kept in step with `src/__tests__/boundaryScope.test.ts`, which pins the
18
+ * orchestrator gap to the code.
19
+ */
20
+ export declare const UNOBSERVED_PATHS: readonly ["orchestrator task dispatch (runClaudeTask, automation hooks) — ADR-0021 scope, #1397"];
21
+ export interface PrivacyShadowRow {
22
+ at: number;
23
+ decision: BoundaryDecision;
24
+ classification: Classification;
25
+ /** Category NAMES only — never their contents. */
26
+ categories?: string[];
27
+ destinationId: string;
28
+ destinationType: "local" | "remote";
29
+ redactCategories?: string[];
30
+ reason: string;
31
+ recipeName?: string;
32
+ stepId?: string;
33
+ /**
34
+ * Whether an ENFORCING policy was also in force for this dispatch.
35
+ *
36
+ * A shadow row on a machine that already enforces means something different
37
+ * from one on a machine that does not, and the summary must not merge them:
38
+ * the first is "my live policy and my candidate policy disagree", the second
39
+ * is "here is what a policy would have done".
40
+ */
41
+ enforcing: boolean;
42
+ }
43
+ export interface ShadowLogOptions {
44
+ /** Defaults to `${PATCHWORK_HOME}`. */
45
+ dir?: string;
46
+ now?: () => number;
47
+ }
48
+ export declare function shadowLogPath(dir?: string): string;
49
+ /**
50
+ * Append one observation. NEVER throws.
51
+ *
52
+ * This runs inside the enforcement chokepoint, so a failure here must be
53
+ * incapable of disturbing a dispatch. An unwritable shadow ledger is a lost
54
+ * measurement; a shadow ledger that can abort a step is a privacy feature that
55
+ * breaks the system it observes.
56
+ */
57
+ export declare function recordPrivacyShadow(row: Omit<PrivacyShadowRow, "at">, opts?: ShadowLogOptions): void;
58
+ export interface PrivacyShadowSummary {
59
+ /** The denominator. Every observed dispatch, INCLUDING allowed ones. */
60
+ observed: number;
61
+ /** Non-ALLOW observations — what a candidate policy would have acted on. */
62
+ crossings: number;
63
+ byDecision: Record<string, number>;
64
+ /** Destination id → count, for "where would it have gone". */
65
+ byDestination: Record<string, number>;
66
+ earliest?: number;
67
+ latest?: number;
68
+ /** How many observations happened while a live policy was also enforcing. */
69
+ enforcingObservations: number;
70
+ observedPath: string;
71
+ unobservedPaths: readonly string[];
72
+ }
73
+ export interface SummariseOptions extends ShadowLogOptions {
74
+ /** Only count rows at or after this timestamp. */
75
+ since?: number;
76
+ }
77
+ export declare function summarisePrivacyShadow(opts?: SummariseOptions): PrivacyShadowSummary;
78
+ /**
79
+ * Render coverage FIRST, and never a bare crossing count.
80
+ *
81
+ * "3 crossings last week" invites exactly one reading — that the other
82
+ * dispatches were fine — and on this surface that reading is wrong twice over:
83
+ * `agent` steps are a small minority of step volume, and orchestrator dispatch
84
+ * is not observed at all. The denominator and the blind spots are therefore
85
+ * part of the finding, not a footnote under it.
86
+ */
87
+ export declare function formatPrivacyShadow(s: PrivacyShadowSummary): string;
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Privacy shadow ledger (ADR-0021).
3
+ *
4
+ * Answers "how often would the policy I am considering have stopped something?"
5
+ * WITHOUT enforcing it — the only part of the information-boundary design that
6
+ * produces evidence an operator can act on before adopting anything.
7
+ *
8
+ * ## Why this is observed live and not replayed
9
+ *
10
+ * `workers shadow` and the Butler outcome grader both replay `runs.jsonl`,
11
+ * and the obvious move was to copy them. It does not work here: a run-log
12
+ * `agent` step persists only
13
+ *
14
+ * durationMs, error, haltCategory, haltReason, id,
15
+ * inputTokens, judgeVerdict, outputTokens, status, tool
16
+ *
17
+ * — no `data_policy`, no driver, no destination. Those two replay a log that
18
+ * contains their inputs; this one's inputs are never written down. A replay
19
+ * tool would have to invent the classification and the destination, and a
20
+ * privacy report built on invented inputs is worse than no report, because it
21
+ * reads as measurement.
22
+ *
23
+ * So observation happens at the decision point, and this file is only the
24
+ * ledger and its summariser.
25
+ *
26
+ * ## What is deliberately not here
27
+ *
28
+ * No payload field, for the same reason `boundary_receipts.jsonl` has none: a
29
+ * privacy audit log containing the prompts would be the largest unclassified
30
+ * copy of exactly the material the boundary exists to protect. Only declared
31
+ * metadata — classification, category NAMES, destination, decision, reason.
32
+ *
33
+ * Separate file from `boundary_receipts.jsonl`, and nothing in the live system
34
+ * reads it. A shadow row is a hypothetical about a policy that is not in force;
35
+ * mixing it into the receipts would put unenforced decisions into the evidence
36
+ * trail that is supposed to record what actually happened.
37
+ */
38
+ import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
39
+ import path from "node:path";
40
+ import { patchworkPath } from "../patchworkHome.js";
41
+ export const SHADOW_LOG_BASENAME = "privacy_shadow.jsonl";
42
+ /** Clip so a runaway reason cannot bloat the ledger. */
43
+ const MAX_REASON = 500;
44
+ /**
45
+ * The path the boundary DOES observe. Recorded on every row rather than
46
+ * assumed by the reader, so a future second observed path is a data change
47
+ * and not a silent redefinition of what the denominator counted.
48
+ */
49
+ export const OBSERVED_PATH = "recipe agent steps";
50
+ /**
51
+ * Paths that reach a model WITHOUT passing the decision point.
52
+ *
53
+ * This is not a caveat, it is part of the result. A crossing count computed
54
+ * over a partial surface reads as "your policy is fine" when it partly means
55
+ * "we did not look there" — and `agent` steps are ~3% of logged step volume,
56
+ * so the unobserved remainder is the larger part of the system.
57
+ *
58
+ * Kept in step with `src/__tests__/boundaryScope.test.ts`, which pins the
59
+ * orchestrator gap to the code.
60
+ */
61
+ export const UNOBSERVED_PATHS = [
62
+ "orchestrator task dispatch (runClaudeTask, automation hooks) — ADR-0021 scope, #1397",
63
+ ];
64
+ export function shadowLogPath(dir) {
65
+ return dir
66
+ ? path.join(dir, SHADOW_LOG_BASENAME)
67
+ : patchworkPath(SHADOW_LOG_BASENAME);
68
+ }
69
+ /**
70
+ * Append one observation. NEVER throws.
71
+ *
72
+ * This runs inside the enforcement chokepoint, so a failure here must be
73
+ * incapable of disturbing a dispatch. An unwritable shadow ledger is a lost
74
+ * measurement; a shadow ledger that can abort a step is a privacy feature that
75
+ * breaks the system it observes.
76
+ */
77
+ export function recordPrivacyShadow(row, opts = {}) {
78
+ try {
79
+ const file = shadowLogPath(opts.dir);
80
+ mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
81
+ const full = {
82
+ at: (opts.now ?? Date.now)(),
83
+ ...row,
84
+ reason: row.reason.slice(0, MAX_REASON),
85
+ };
86
+ appendFileSync(file, `${JSON.stringify(full)}\n`);
87
+ }
88
+ catch {
89
+ // Observation only — never disturb the dispatch being observed.
90
+ }
91
+ }
92
+ export function summarisePrivacyShadow(opts = {}) {
93
+ const summary = {
94
+ observed: 0,
95
+ crossings: 0,
96
+ byDecision: {},
97
+ byDestination: {},
98
+ enforcingObservations: 0,
99
+ observedPath: OBSERVED_PATH,
100
+ unobservedPaths: UNOBSERVED_PATHS,
101
+ };
102
+ let text;
103
+ try {
104
+ text = readFileSync(shadowLogPath(opts.dir), "utf-8");
105
+ }
106
+ catch {
107
+ // No ledger yet. Reported as observed=0, which the formatter renders as
108
+ // "nothing observed" rather than "no crossings" — see formatPrivacyShadow.
109
+ return summary;
110
+ }
111
+ for (const line of text.split("\n")) {
112
+ const t = line.trim();
113
+ if (!t)
114
+ continue;
115
+ let row;
116
+ try {
117
+ row = JSON.parse(t);
118
+ }
119
+ catch {
120
+ continue;
121
+ }
122
+ if (typeof row.at !== "number" || typeof row.decision !== "string")
123
+ continue;
124
+ if (opts.since !== undefined && row.at < opts.since)
125
+ continue;
126
+ summary.observed += 1;
127
+ if (row.decision !== "ALLOW")
128
+ summary.crossings += 1;
129
+ if (row.enforcing)
130
+ summary.enforcingObservations += 1;
131
+ summary.byDecision[row.decision] =
132
+ (summary.byDecision[row.decision] ?? 0) + 1;
133
+ if (row.destinationId) {
134
+ summary.byDestination[row.destinationId] =
135
+ (summary.byDestination[row.destinationId] ?? 0) + 1;
136
+ }
137
+ if (summary.earliest === undefined || row.at < summary.earliest) {
138
+ summary.earliest = row.at;
139
+ }
140
+ if (summary.latest === undefined || row.at > summary.latest) {
141
+ summary.latest = row.at;
142
+ }
143
+ }
144
+ return summary;
145
+ }
146
+ /**
147
+ * Render coverage FIRST, and never a bare crossing count.
148
+ *
149
+ * "3 crossings last week" invites exactly one reading — that the other
150
+ * dispatches were fine — and on this surface that reading is wrong twice over:
151
+ * `agent` steps are a small minority of step volume, and orchestrator dispatch
152
+ * is not observed at all. The denominator and the blind spots are therefore
153
+ * part of the finding, not a footnote under it.
154
+ */
155
+ export function formatPrivacyShadow(s) {
156
+ const L = [];
157
+ L.push("[privacy-shadow] coverage");
158
+ L.push(` observed: ${s.observed} dispatch(es) on ${s.observedPath}`);
159
+ for (const p of s.unobservedPaths)
160
+ L.push(` NOT observed: ${p}`);
161
+ if (s.observed === 0) {
162
+ // Deliberately NOT "0 crossings" — that asserts a clean result from an
163
+ // empty ledger. Nothing was measured, which is a different statement.
164
+ L.push("");
165
+ L.push(" Nothing has been observed yet, so there is no result to report.");
166
+ L.push(" Configure `privacy.shadow.destinations` and run a recipe with an");
167
+ L.push(" agent step; every dispatch is then recorded, allowed or not.");
168
+ return L.join("\n");
169
+ }
170
+ const span = s.earliest !== undefined && s.latest !== undefined
171
+ ? `${new Date(s.earliest).toISOString()} → ${new Date(s.latest).toISOString()}`
172
+ : "(unknown)";
173
+ L.push(` window: ${span}`);
174
+ if (s.enforcingObservations > 0) {
175
+ L.push(` note: ${s.enforcingObservations} observed while a live policy was ALSO enforcing`);
176
+ }
177
+ L.push("");
178
+ const pct = ((s.crossings / s.observed) * 100).toFixed(1);
179
+ L.push(`[privacy-shadow] ${s.crossings} of ${s.observed} observed dispatch(es) (${pct}%) would have been stopped or altered`);
180
+ for (const [d, n] of Object.entries(s.byDecision).sort((a, b) => b[1] - a[1])) {
181
+ L.push(` ${d.padEnd(18)} ${n}`);
182
+ }
183
+ if (Object.keys(s.byDestination).length > 0) {
184
+ L.push("");
185
+ L.push(" destinations:");
186
+ for (const [d, n] of Object.entries(s.byDestination).sort((a, b) => b[1] - a[1])) {
187
+ L.push(` ${d.padEnd(16)} ${n}`);
188
+ }
189
+ }
190
+ L.push("");
191
+ L.push(" Shadow only — nothing was blocked, and no live policy changed. These are");
192
+ L.push(" hypotheticals about the candidate policy in `privacy.shadow`, over the");
193
+ L.push(" observed path only.");
194
+ return L.join("\n");
195
+ }
196
+ //# sourceMappingURL=shadowLog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadowLog.js","sourceRoot":"","sources":["../../src/privacy/shadowLog.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  * CHANGELOG: chained users with model:local in ~/.patchwork/config now route to
8
8
  * localFn (Ollama/LM Studio) instead of Anthropic API — opt-in behaviour change.
9
9
  */
10
- import { type Destination } from "../privacy/dataPolicy.js";
10
+ import { type BoundaryDecision, type Classification, type Destination } from "../privacy/dataPolicy.js";
11
11
  import { type PrivacyConfig } from "../privacy/destinationRegistry.js";
12
12
  /**
13
13
  * Token usage reported by an adapter. Both fields are integers; absent
@@ -64,6 +64,26 @@ export interface AgentExecutorDeps {
64
64
  categories?: string[];
65
65
  redactCategories?: string[];
66
66
  }) => void;
67
+ /**
68
+ * CANDIDATE policy for shadow mode (ADR-0021), deliberately a SEPARATE
69
+ * loader from `loadPrivacyConfigFn`.
70
+ *
71
+ * Two keys, not one, because the absence of `privacy` is what keeps the
72
+ * boundary inert: if shadow reused the enforcing config, switching shadow on
73
+ * would switch enforcement on with it, and an operator asking "what would
74
+ * this policy do?" would find out by having it applied.
75
+ */
76
+ loadPrivacyShadowConfigFn?: () => PrivacyConfig | undefined;
77
+ recordPrivacyShadowFn?: (r: {
78
+ decision: BoundaryDecision;
79
+ reason: string;
80
+ destinationId: string;
81
+ destinationType: "local" | "remote";
82
+ classification: Classification;
83
+ categories?: string[];
84
+ redactCategories?: string[];
85
+ enforcing: boolean;
86
+ }) => void;
67
87
  anthropicFn: (prompt: string, model: string) => Promise<AgentResult>;
68
88
  /** Handles openai, grok, gemini, gemini-api, codex — passes driver name through. */
69
89
  providerDriverFn: (driver: "openai" | "grok" | "gemini" | "gemini-api" | "codex", prompt: string, model: string | undefined,
@@ -104,6 +104,50 @@ const CODEX_WORKER_SANDBOX_LOCKDOWN = {
104
104
  networkAccess: false,
105
105
  webSearch: false,
106
106
  };
107
+ /**
108
+ * Evaluate the CANDIDATE policy and record what it would have done.
109
+ *
110
+ * Reuses `evaluateBoundary` with a different config loader rather than
111
+ * reimplementing the decision: a shadow that computed its own answer would
112
+ * drift from the enforcer, and a report that disagrees with the thing it
113
+ * predicts is worse than no report.
114
+ *
115
+ * `destination` is stripped from the context on purpose. When a caller passes
116
+ * one explicitly, `evaluateBoundary` skips the registry — correct for
117
+ * enforcement, wrong here, because the whole question is what the CANDIDATE
118
+ * registry would have chosen.
119
+ */
120
+ function observeShadowBoundary(input, resolvedDriver, enforced, deps) {
121
+ try {
122
+ if (!deps.loadPrivacyShadowConfigFn || !deps.recordPrivacyShadowFn)
123
+ return;
124
+ const ctx = input.boundary
125
+ ? { ...input.boundary, destination: undefined }
126
+ : undefined;
127
+ const shadow = evaluateBoundary(ctx, resolvedDriver, deps.loadPrivacyShadowConfigFn, isLocalFamilyDriver(resolvedDriver)
128
+ ? () => resolveLocalEndpoint(deps)
129
+ : undefined);
130
+ if (!shadow)
131
+ return;
132
+ deps.recordPrivacyShadowFn({
133
+ decision: shadow.decision,
134
+ reason: shadow.reason,
135
+ destinationId: shadow.destination.id,
136
+ destinationType: shadow.destination.type,
137
+ classification: shadow.classification,
138
+ ...(shadow.categories && { categories: shadow.categories }),
139
+ ...(shadow.redactCategories && {
140
+ redactCategories: shadow.redactCategories,
141
+ }),
142
+ // Distinguishes "my candidate policy disagrees with my live one" from
143
+ // "here is what a policy would have done on an ungoverned machine".
144
+ enforcing: enforced !== null,
145
+ });
146
+ }
147
+ catch {
148
+ // Observation must never disturb the dispatch it observes.
149
+ }
150
+ }
107
151
  function evaluateBoundary(ctx, driver, loadPrivacyConfig,
108
152
  // A GETTER, not a value: resolving it reads `config.json`, and a caller that
109
153
  // passes an explicit destination never needs it. Eager resolution would add
@@ -195,6 +239,19 @@ export async function executeAgent(input, deps) {
195
239
  isLocalFamilyDriver(resolvedDriver)
196
240
  ? () => resolveLocalEndpoint(deps)
197
241
  : undefined);
242
+ // ── PRIVACY SHADOW (ADR-0021) ───────────────────────────────────────────
243
+ // Observation only. Placed HERE — after the enforced decision is computed but
244
+ // before it is acted on — so every agent dispatch is recorded, including the
245
+ // allowed ones. Recording only refusals would make the denominator the
246
+ // refusals themselves, and a coverage report whose denominator is its own
247
+ // numerator always reads 100%.
248
+ //
249
+ // It returns void and swallows everything. That is the load-bearing property:
250
+ // this runs inside the enforcement chokepoint, so it must be structurally
251
+ // incapable of changing whether or where a step dispatches. A test asserts
252
+ // the enforced outcome is identical with and without a shadow policy that
253
+ // would DENY.
254
+ observeShadowBoundary(input, resolvedDriver, boundary, deps);
198
255
  if (boundary && boundary.decision !== "ALLOW") {
199
256
  deps.recordBoundaryDecisionFn?.({
200
257
  decision: boundary.decision,
@@ -1 +1 @@
1
- {"version":3,"file":"agentExecutor.js","sourceRoot":"","sources":["../../src/recipes/agentExecutor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agentExecutor.js","sourceRoot":"","sources":["../../src/recipes/agentExecutor.ts"],"names":[],"mappings":"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"}
@@ -68,6 +68,7 @@ import { resolveWorkspaceRoot } from "./workspaceRoot.js";
68
68
  import "./tools/index.js";
69
69
  import { patchworkPath } from "../patchworkHome.js";
70
70
  import { BoundaryReceiptLog } from "../privacy/boundaryReceiptLog.js";
71
+ import { recordPrivacyShadow } from "../privacy/shadowLog.js";
71
72
  /**
72
73
  * Bundled-templates directory used as a third allowed root for nested-recipe
73
74
  * lookups (`recipe:` references with explicit paths). Resolved once at module
@@ -2566,6 +2567,26 @@ function buildAgentExecutorDeps(stepDeps, runnerDeps, claudeCodeFnOverride) {
2566
2567
  // `privacy.destinations` must take effect without a bridge restart, and
2567
2568
  // `loadConfig` is already mtime-gated so this is cheap.
2568
2569
  loadPrivacyConfigFn: () => loadPatchworkConfigSync().privacy,
2570
+ // Shadow mode (ADR-0021). Wired here for the same reason as the pair above:
2571
+ // deps declared and supplied nowhere are indistinguishable at runtime from
2572
+ // a feature that was never built.
2573
+ //
2574
+ // Reads `privacy.shadow`, NOT `privacy` — a separate key so that turning
2575
+ // shadow on cannot turn enforcement on. An operator asking "what would this
2576
+ // policy do?" must not find out by having it applied.
2577
+ loadPrivacyShadowConfigFn: () => loadPatchworkConfigSync().privacy?.shadow,
2578
+ recordPrivacyShadowFn: (r) => {
2579
+ recordPrivacyShadow({
2580
+ decision: r.decision,
2581
+ reason: r.reason,
2582
+ destinationId: r.destinationId,
2583
+ destinationType: r.destinationType,
2584
+ classification: r.classification,
2585
+ enforcing: r.enforcing,
2586
+ ...(r.categories && { categories: r.categories }),
2587
+ ...(r.redactCategories && { redactCategories: r.redactCategories }),
2588
+ });
2589
+ },
2569
2590
  recordBoundaryDecisionFn: (r) => {
2570
2591
  // Fail-soft: a receipt that cannot be written must never affect the
2571
2592
  // decision it describes, which has already been made and enforced.