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.
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -1
- package/dist/privacy/shadowLog.d.ts +87 -0
- package/dist/privacy/shadowLog.js +196 -0
- package/dist/privacy/shadowLog.js.map +1 -0
- package/dist/recipes/agentExecutor.d.ts +21 -1
- package/dist/recipes/agentExecutor.js +57 -0
- package/dist/recipes/agentExecutor.js.map +1 -1
- package/dist/recipes/yamlRunner.js +21 -0
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/package.json +1 -1
- package/scripts/mcp-stdio-shim.cjs +27 -1
|
@@ -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":"AAAA;;;;;;;;GAQG;AAEH,OAAO,
|
|
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.
|