patchwork-os 1.2.0-beta.2.canary.747 → 1.2.0-beta.2.canary.749

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,76 @@
1
+ /**
2
+ * Which agent steps carry no `data_policy`, and what actually feeds them?
3
+ *
4
+ * ADR-0021 is fail-soft: a step with no `data_policy` is classified `internal`
5
+ * and the boundary lets it through. That is the correct default — it is what
6
+ * keeps the boundary inert on upgrade — but it means an undeclared step is
7
+ * invisible in exactly the way a declared one is not. Measured 2026-08-26 on
8
+ * the reference install: **132 of 214** privacy-shadow observations carried
9
+ * `labelSource: "assumed"`, and installed recipes declared `data_policy` on
10
+ * **20 of 87** agent steps against **22 of 22** in the shipped templates.
11
+ *
12
+ * ## Why this reports the FEEDING TOOLS and not just the step
13
+ *
14
+ * A classification describes what the step HANDLES, "including whatever its
15
+ * tools return" — that rule is in CLAUDE.md and it is the whole difficulty. A
16
+ * prompt whose text mentions nothing sensitive can still be handed a mailbox by
17
+ * the step above it, which is precisely why `morning-brief` declares `personal`
18
+ * while its prompt only says how to go and fetch things.
19
+ *
20
+ * So a report that listed undeclared steps would tell an operator WHERE to look
21
+ * and nothing about WHAT to write. This resolves each `{{ref}}` in the prompt
22
+ * back to the step that produced it and names that step's tool, which is the
23
+ * evidence the classification actually rests on.
24
+ *
25
+ * ## It suggests nothing
26
+ *
27
+ * Deliberately no recommended classification, not even a conservative one.
28
+ * Guessing a label and presenting it as a starting point is how an unexamined
29
+ * claim ends up declared — and a declared-but-wrong label is strictly worse
30
+ * than an assumed one, because it stops looking like a gap. The operator reads
31
+ * the tools and decides. Same reason `privacy suggest` emits `privacy.shadow`
32
+ * and never the enforcing `privacy.destinations` key.
33
+ *
34
+ * Read-only. Output is OPERATOR DATA — it names real installed recipes — so it
35
+ * is quoted as a measurement and never pasted into an issue or a fixture.
36
+ */
37
+ /** A `{{ref}}` in a prompt, resolved back to whatever produced it. */
38
+ export interface FeedingOutput {
39
+ /** The `into:` key referenced by the prompt. */
40
+ ref: string;
41
+ /** Tool that produced it, or undefined when nothing in this recipe does. */
42
+ tool?: string;
43
+ /** True when the producing step is itself an agent. */
44
+ fromAgent?: boolean;
45
+ }
46
+ export interface UndeclaredStep {
47
+ recipe: string;
48
+ /** Step id, or a positional label when the step declares none. */
49
+ stepId: string;
50
+ /** Resolved `{{ref}}`s, so the reader can classify from what flows in. */
51
+ feeds: FeedingOutput[];
52
+ /** Refs the prompt uses that no step in this recipe produces. */
53
+ unresolvedRefs: string[];
54
+ }
55
+ export interface UndeclaredReport {
56
+ /** Recipes examined — the denominator. */
57
+ recipesScanned: number;
58
+ /** Every agent step found, declared or not. The other denominator. */
59
+ agentSteps: number;
60
+ declared: number;
61
+ undeclared: UndeclaredStep[];
62
+ /** Recipes that could not be parsed. Reported, never silently dropped. */
63
+ unreadable: string[];
64
+ }
65
+ /** `{{a.b}}` / `{{ a }}` → the root key. Filters template helpers and literals. */
66
+ export declare function refsIn(prompt: string): string[];
67
+ /**
68
+ * Analyse one already-parsed recipe. Takes parsed YAML rather than a path so
69
+ * the caller owns file access and this stays trivially testable.
70
+ */
71
+ export declare function undeclaredInRecipe(recipeName: string, parsed: unknown): {
72
+ agentSteps: number;
73
+ declared: number;
74
+ steps: UndeclaredStep[];
75
+ };
76
+ export declare function formatUndeclared(r: UndeclaredReport): string;
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Which agent steps carry no `data_policy`, and what actually feeds them?
3
+ *
4
+ * ADR-0021 is fail-soft: a step with no `data_policy` is classified `internal`
5
+ * and the boundary lets it through. That is the correct default — it is what
6
+ * keeps the boundary inert on upgrade — but it means an undeclared step is
7
+ * invisible in exactly the way a declared one is not. Measured 2026-08-26 on
8
+ * the reference install: **132 of 214** privacy-shadow observations carried
9
+ * `labelSource: "assumed"`, and installed recipes declared `data_policy` on
10
+ * **20 of 87** agent steps against **22 of 22** in the shipped templates.
11
+ *
12
+ * ## Why this reports the FEEDING TOOLS and not just the step
13
+ *
14
+ * A classification describes what the step HANDLES, "including whatever its
15
+ * tools return" — that rule is in CLAUDE.md and it is the whole difficulty. A
16
+ * prompt whose text mentions nothing sensitive can still be handed a mailbox by
17
+ * the step above it, which is precisely why `morning-brief` declares `personal`
18
+ * while its prompt only says how to go and fetch things.
19
+ *
20
+ * So a report that listed undeclared steps would tell an operator WHERE to look
21
+ * and nothing about WHAT to write. This resolves each `{{ref}}` in the prompt
22
+ * back to the step that produced it and names that step's tool, which is the
23
+ * evidence the classification actually rests on.
24
+ *
25
+ * ## It suggests nothing
26
+ *
27
+ * Deliberately no recommended classification, not even a conservative one.
28
+ * Guessing a label and presenting it as a starting point is how an unexamined
29
+ * claim ends up declared — and a declared-but-wrong label is strictly worse
30
+ * than an assumed one, because it stops looking like a gap. The operator reads
31
+ * the tools and decides. Same reason `privacy suggest` emits `privacy.shadow`
32
+ * and never the enforcing `privacy.destinations` key.
33
+ *
34
+ * Read-only. Output is OPERATOR DATA — it names real installed recipes — so it
35
+ * is quoted as a measurement and never pasted into an issue or a fixture.
36
+ */
37
+ function isRecord(v) {
38
+ return typeof v === "object" && v !== null && !Array.isArray(v);
39
+ }
40
+ /** `{{a.b}}` / `{{ a }}` → the root key. Filters template helpers and literals. */
41
+ export function refsIn(prompt) {
42
+ const out = new Set();
43
+ for (const m of prompt.matchAll(/\{\{\s*([A-Za-z_][\w.-]*)/g)) {
44
+ const root = m[1].split(".")[0];
45
+ out.add(root);
46
+ }
47
+ return [...out];
48
+ }
49
+ /**
50
+ * Analyse one already-parsed recipe. Takes parsed YAML rather than a path so
51
+ * the caller owns file access and this stays trivially testable.
52
+ */
53
+ export function undeclaredInRecipe(recipeName, parsed) {
54
+ if (!isRecord(parsed) || !Array.isArray(parsed.steps)) {
55
+ return { agentSteps: 0, declared: 0, steps: [] };
56
+ }
57
+ const raw = parsed.steps;
58
+ // into-key -> producing tool, built first so a prompt can be resolved against
59
+ // steps that appear before OR after it. Order is the runner's problem, not
60
+ // this report's.
61
+ const producers = new Map();
62
+ for (const s of raw) {
63
+ if (!isRecord(s))
64
+ continue;
65
+ const agent = isRecord(s.agent) ? s.agent : undefined;
66
+ const into = typeof s.into === "string"
67
+ ? s.into
68
+ : agent && typeof agent.into === "string"
69
+ ? agent.into
70
+ : undefined;
71
+ if (!into)
72
+ continue;
73
+ producers.set(into, {
74
+ ...(typeof s.tool === "string" ? { tool: s.tool } : {}),
75
+ fromAgent: agent !== undefined,
76
+ });
77
+ }
78
+ let agentSteps = 0;
79
+ let declared = 0;
80
+ const steps = [];
81
+ for (let i = 0; i < raw.length; i++) {
82
+ const s = raw[i];
83
+ if (!isRecord(s))
84
+ continue;
85
+ const agent = isRecord(s.agent) ? s.agent : undefined;
86
+ if (!agent)
87
+ continue;
88
+ agentSteps++;
89
+ // `data_policy` sits on the agent block in shipped templates; accept it at
90
+ // step level too rather than reporting a false positive over a spelling.
91
+ if (agent.data_policy !== undefined || s.data_policy !== undefined) {
92
+ declared++;
93
+ continue;
94
+ }
95
+ const prompt = typeof agent.prompt === "string" ? agent.prompt : "";
96
+ const feeds = [];
97
+ const unresolved = [];
98
+ for (const ref of refsIn(prompt)) {
99
+ const p = producers.get(ref);
100
+ if (p) {
101
+ feeds.push({
102
+ ref,
103
+ ...(p.tool ? { tool: p.tool } : {}),
104
+ ...(p.fromAgent ? { fromAgent: true } : {}),
105
+ });
106
+ }
107
+ else {
108
+ unresolved.push(ref);
109
+ }
110
+ }
111
+ steps.push({
112
+ recipe: recipeName,
113
+ stepId: typeof s.id === "string"
114
+ ? s.id
115
+ : typeof s.into === "string"
116
+ ? s.into
117
+ : `step_${i}`,
118
+ feeds,
119
+ unresolvedRefs: unresolved,
120
+ });
121
+ }
122
+ return { agentSteps, declared, steps };
123
+ }
124
+ export function formatUndeclared(r) {
125
+ const L = [];
126
+ L.push("[privacy] agent steps with no data_policy");
127
+ // Denominator first, always: "67 undeclared" reads as an alarm, "67 of 87"
128
+ // is the fact.
129
+ L.push(` ${r.undeclared.length} of ${r.agentSteps} agent step(s) across ${r.recipesScanned} recipe(s) declare none`);
130
+ if (r.unreadable.length > 0) {
131
+ L.push(` ${r.unreadable.length} recipe(s) could not be parsed`);
132
+ }
133
+ L.push("");
134
+ if (r.undeclared.length === 0) {
135
+ L.push(r.agentSteps === 0
136
+ ? " no agent steps found — nothing to classify"
137
+ : " ✓ every agent step declares a data_policy");
138
+ L.push("");
139
+ return `${L.join("\n")}\n`;
140
+ }
141
+ let current = "";
142
+ for (const s of r.undeclared) {
143
+ if (s.recipe !== current) {
144
+ current = s.recipe;
145
+ L.push(` ${current}`);
146
+ }
147
+ const fed = s.feeds.length === 0
148
+ ? "no step output flows in"
149
+ : s.feeds
150
+ .map((f) => `${f.ref}${f.tool ? ` <- ${f.tool}` : f.fromAgent ? " <- agent" : ""}`)
151
+ .join(", ");
152
+ L.push(` ${s.stepId}: ${fed}`);
153
+ if (s.unresolvedRefs.length > 0) {
154
+ L.push(` (unresolved refs: ${s.unresolvedRefs.join(", ")})`);
155
+ }
156
+ }
157
+ L.push("");
158
+ L.push(" Classify by what the step HANDLES, including whatever its tools return —");
159
+ L.push(" a prompt that mentions nothing sensitive can still be handed a mailbox by");
160
+ L.push(" the step above it. No classification is suggested here on purpose: a");
161
+ L.push(" declared-but-wrong label is worse than an assumed one, because it stops");
162
+ L.push(" looking like a gap.");
163
+ L.push("");
164
+ return `${L.join("\n")}\n`;
165
+ }
166
+ //# sourceMappingURL=undeclaredSteps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"undeclaredSteps.js","sourceRoot":"","sources":["../../src/privacy/undeclaredSteps.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchwork-os",
3
- "version": "1.2.0-beta.2.canary.747",
3
+ "version": "1.2.0-beta.2.canary.749",
4
4
  "description": "Your personal AI runtime, local-first. Patchwork OS gives any AI model a consistent set of tools, YAML recipes, a delegation policy with approval queue, and a durable trace memory — all on your machine, all under your policy.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",