patchwork-os 1.2.0-beta.2.canary.752 → 1.2.0-beta.2.canary.755
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/commands/recipe.js +32 -3
- package/dist/commands/recipe.js.map +1 -1
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/privacy/describeDestinations.d.ts +76 -0
- package/dist/privacy/describeDestinations.js +132 -0
- package/dist/privacy/describeDestinations.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the destination registry as something an operator can act on.
|
|
3
|
+
*
|
|
4
|
+
* The registry is already the place the choice lives: clearing a remote
|
|
5
|
+
* destination for `personal` is one line in `~/.patchwork/config.json`, and has
|
|
6
|
+
* been possible since the boundary shipped. Nothing here adds a new policy
|
|
7
|
+
* primitive, deliberately — a recipe-scoped allow-list would put recipe
|
|
8
|
+
* identity into the decision point, which `recordBoundaryDecisionFn` explicitly
|
|
9
|
+
* keeps out of it, and would smuggle in "purpose" ahead of the per-field
|
|
10
|
+
* labels ADR-0021 reserves it for.
|
|
11
|
+
*
|
|
12
|
+
* What was missing is not the mechanism. It is that the choice was invisible:
|
|
13
|
+
* no surface told an operator which destinations leave the machine, what each
|
|
14
|
+
* is cleared to receive, or that clearing one for `personal` sends their data
|
|
15
|
+
* to somebody else's servers.
|
|
16
|
+
*
|
|
17
|
+
* ## The disclosure states only what this runtime can verify
|
|
18
|
+
*
|
|
19
|
+
* A sentence about a provider's retention or training policy rots without
|
|
20
|
+
* anyone touching the code — API retention moved 30 days → 7 in Sept 2025 and
|
|
21
|
+
* a further change was announced in Aug 2026. A claim the code cannot keep is
|
|
22
|
+
* worse than no claim, because it is believed.
|
|
23
|
+
*
|
|
24
|
+
* So the disclosure is confined to what is true by construction: the prompt
|
|
25
|
+
* leaves this machine, over the network, to the named destination. Retention,
|
|
26
|
+
* training and deletion are the provider's claims, not ours. An operator may
|
|
27
|
+
* record those in `note` / `noteReviewedOn`, and a note with no date, or a
|
|
28
|
+
* stale one, is reported AS stale rather than shown as authoritative — the
|
|
29
|
+
* same rule the receipts follow by refusing to hold a payload: record what is
|
|
30
|
+
* known, decline to assert what is not.
|
|
31
|
+
*/
|
|
32
|
+
import { type Classification, type Destination } from "./dataPolicy.js";
|
|
33
|
+
export interface DestinationNote {
|
|
34
|
+
/** Operator's own words about this destination. Never asserted by us. */
|
|
35
|
+
note?: string;
|
|
36
|
+
/** ISO date the operator last checked `note` against the provider. */
|
|
37
|
+
noteReviewedOn?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface DescribedDestination {
|
|
40
|
+
id: string;
|
|
41
|
+
type: "local" | "remote";
|
|
42
|
+
/** Classifications this destination is cleared to receive, ordered. */
|
|
43
|
+
cleared: Classification[];
|
|
44
|
+
/** Driver names routed here. */
|
|
45
|
+
drivers: string[];
|
|
46
|
+
/**
|
|
47
|
+
* True when a REMOTE destination is cleared for `personal` or above. This is
|
|
48
|
+
* the line worth surfacing: it is a legitimate operator choice, and it is
|
|
49
|
+
* the one that sends data an operator may not expect to leave the machine.
|
|
50
|
+
*/
|
|
51
|
+
sendsSensitiveOffMachine: boolean;
|
|
52
|
+
note?: string;
|
|
53
|
+
noteReviewedOn?: string;
|
|
54
|
+
/** True when a note exists but carries no reviewed date, or an unparseable one. */
|
|
55
|
+
noteUndated: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface DescribeOptions {
|
|
58
|
+
notes?: Record<string, DestinationNote>;
|
|
59
|
+
/** Today, for staleness. Injected so the report is testable. */
|
|
60
|
+
now?: Date;
|
|
61
|
+
/** A note older than this many days is reported as stale. */
|
|
62
|
+
staleAfterDays?: number;
|
|
63
|
+
}
|
|
64
|
+
export declare function describeDestinations(destinations: Destination[], driversFor: Map<string, string[]>, opts?: DescribeOptions): DescribedDestination[];
|
|
65
|
+
/** True when a dated note is older than the staleness window. */
|
|
66
|
+
export declare function noteIsStale(d: DescribedDestination, opts?: DescribeOptions): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The one sentence this runtime can stand behind permanently.
|
|
69
|
+
*
|
|
70
|
+
* Names no retention period, no training policy and no deletion promise: those
|
|
71
|
+
* belong to the provider and change without warning.
|
|
72
|
+
*/
|
|
73
|
+
export declare function disclosureFor(d: DescribedDestination): string;
|
|
74
|
+
export declare function formatDestinationsReport(described: DescribedDestination[], opts?: DescribeOptions): string;
|
|
75
|
+
/** Every classification, ordered — for help text that must not drift. */
|
|
76
|
+
export declare const ALL_CLASSIFICATIONS: ("public" | "internal" | "personal" | "confidential" | "restricted")[];
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the destination registry as something an operator can act on.
|
|
3
|
+
*
|
|
4
|
+
* The registry is already the place the choice lives: clearing a remote
|
|
5
|
+
* destination for `personal` is one line in `~/.patchwork/config.json`, and has
|
|
6
|
+
* been possible since the boundary shipped. Nothing here adds a new policy
|
|
7
|
+
* primitive, deliberately — a recipe-scoped allow-list would put recipe
|
|
8
|
+
* identity into the decision point, which `recordBoundaryDecisionFn` explicitly
|
|
9
|
+
* keeps out of it, and would smuggle in "purpose" ahead of the per-field
|
|
10
|
+
* labels ADR-0021 reserves it for.
|
|
11
|
+
*
|
|
12
|
+
* What was missing is not the mechanism. It is that the choice was invisible:
|
|
13
|
+
* no surface told an operator which destinations leave the machine, what each
|
|
14
|
+
* is cleared to receive, or that clearing one for `personal` sends their data
|
|
15
|
+
* to somebody else's servers.
|
|
16
|
+
*
|
|
17
|
+
* ## The disclosure states only what this runtime can verify
|
|
18
|
+
*
|
|
19
|
+
* A sentence about a provider's retention or training policy rots without
|
|
20
|
+
* anyone touching the code — API retention moved 30 days → 7 in Sept 2025 and
|
|
21
|
+
* a further change was announced in Aug 2026. A claim the code cannot keep is
|
|
22
|
+
* worse than no claim, because it is believed.
|
|
23
|
+
*
|
|
24
|
+
* So the disclosure is confined to what is true by construction: the prompt
|
|
25
|
+
* leaves this machine, over the network, to the named destination. Retention,
|
|
26
|
+
* training and deletion are the provider's claims, not ours. An operator may
|
|
27
|
+
* record those in `note` / `noteReviewedOn`, and a note with no date, or a
|
|
28
|
+
* stale one, is reported AS stale rather than shown as authoritative — the
|
|
29
|
+
* same rule the receipts follow by refusing to hold a payload: record what is
|
|
30
|
+
* known, decline to assert what is not.
|
|
31
|
+
*/
|
|
32
|
+
import { CLASSIFICATIONS, classificationRank, } from "./dataPolicy.js";
|
|
33
|
+
/** Anything above `internal` is data an operator would not expect to travel. */
|
|
34
|
+
const SENSITIVE_FLOOR = "personal";
|
|
35
|
+
const DEFAULT_STALE_DAYS = 180;
|
|
36
|
+
export function describeDestinations(destinations, driversFor, opts = {}) {
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const d of destinations) {
|
|
39
|
+
const note = opts.notes?.[d.id];
|
|
40
|
+
const cleared = [...d.classifications].sort((a, b) => classificationRank(a) - classificationRank(b));
|
|
41
|
+
const sensitive = cleared.some((c) => classificationRank(c) >= classificationRank(SENSITIVE_FLOOR));
|
|
42
|
+
const dated = note?.noteReviewedOn !== undefined &&
|
|
43
|
+
!Number.isNaN(Date.parse(note.noteReviewedOn));
|
|
44
|
+
out.push({
|
|
45
|
+
id: d.id,
|
|
46
|
+
type: d.type,
|
|
47
|
+
cleared,
|
|
48
|
+
drivers: driversFor.get(d.id) ?? [],
|
|
49
|
+
sendsSensitiveOffMachine: d.type === "remote" && sensitive,
|
|
50
|
+
...(note?.note !== undefined && { note: note.note }),
|
|
51
|
+
...(note?.noteReviewedOn !== undefined && {
|
|
52
|
+
noteReviewedOn: note.noteReviewedOn,
|
|
53
|
+
}),
|
|
54
|
+
noteUndated: note?.note !== undefined && !dated,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
/** True when a dated note is older than the staleness window. */
|
|
60
|
+
export function noteIsStale(d, opts = {}) {
|
|
61
|
+
if (d.note === undefined)
|
|
62
|
+
return false;
|
|
63
|
+
if (d.noteReviewedOn === undefined)
|
|
64
|
+
return true;
|
|
65
|
+
const t = Date.parse(d.noteReviewedOn);
|
|
66
|
+
if (Number.isNaN(t))
|
|
67
|
+
return true;
|
|
68
|
+
const now = (opts.now ?? new Date()).getTime();
|
|
69
|
+
const days = (now - t) / 86_400_000;
|
|
70
|
+
return days > (opts.staleAfterDays ?? DEFAULT_STALE_DAYS);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The one sentence this runtime can stand behind permanently.
|
|
74
|
+
*
|
|
75
|
+
* Names no retention period, no training policy and no deletion promise: those
|
|
76
|
+
* belong to the provider and change without warning.
|
|
77
|
+
*/
|
|
78
|
+
export function disclosureFor(d) {
|
|
79
|
+
return d.type === "local"
|
|
80
|
+
? `stays on this machine`
|
|
81
|
+
: `LEAVES this machine over the network to "${d.id}"`;
|
|
82
|
+
}
|
|
83
|
+
export function formatDestinationsReport(described, opts = {}) {
|
|
84
|
+
const L = [];
|
|
85
|
+
L.push("[privacy] where your prompts may go");
|
|
86
|
+
if (described.length === 0) {
|
|
87
|
+
// The inert case is a true and expected state, not an error. Saying "0
|
|
88
|
+
// destinations" invites someone to fix a number; saying the boundary is
|
|
89
|
+
// inert says what it MEANS.
|
|
90
|
+
L.push("");
|
|
91
|
+
L.push(" Nothing is registered, so the information boundary is INERT —");
|
|
92
|
+
L.push(" no dispatch is evaluated and no decision is made. Registering a");
|
|
93
|
+
L.push(" destination is the opt-in; once opted in it fails closed.");
|
|
94
|
+
return L.join("\n");
|
|
95
|
+
}
|
|
96
|
+
L.push("");
|
|
97
|
+
for (const d of described) {
|
|
98
|
+
L.push(` ${d.id} (${d.type})`);
|
|
99
|
+
L.push(` ${disclosureFor(d)}`);
|
|
100
|
+
L.push(` cleared for: ${d.cleared.join(", ") || "(nothing)"}`);
|
|
101
|
+
if (d.drivers.length > 0) {
|
|
102
|
+
L.push(` drivers: ${d.drivers.join(", ")}`);
|
|
103
|
+
}
|
|
104
|
+
if (d.sendsSensitiveOffMachine) {
|
|
105
|
+
L.push(` ⚠ cleared for data above "internal" AND off-machine — your`);
|
|
106
|
+
L.push(` own choice, and the one that sends personal data to a`);
|
|
107
|
+
L.push(` third party's servers.`);
|
|
108
|
+
}
|
|
109
|
+
if (d.note !== undefined) {
|
|
110
|
+
L.push(` note: ${d.note}`);
|
|
111
|
+
if (noteIsStale(d, opts)) {
|
|
112
|
+
L.push(d.noteReviewedOn === undefined || d.noteUndated
|
|
113
|
+
? ` ⚠ that note carries no reviewed date — it is your claim, undated.`
|
|
114
|
+
: ` ⚠ that note was last checked ${d.noteReviewedOn} and may be out of date.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
L.push("");
|
|
118
|
+
}
|
|
119
|
+
L.push(" Retention, training and deletion are the provider's claims, not this");
|
|
120
|
+
L.push(" tool's. They change without notice, so nothing here asserts them —");
|
|
121
|
+
L.push(" record what you have checked in `note`, with the date.");
|
|
122
|
+
const anySensitive = described.some((d) => d.sendsSensitiveOffMachine);
|
|
123
|
+
if (!anySensitive) {
|
|
124
|
+
L.push("");
|
|
125
|
+
L.push(" No remote destination is cleared above `internal`, so personal data");
|
|
126
|
+
L.push(" currently has nowhere off-machine to go.");
|
|
127
|
+
}
|
|
128
|
+
return L.join("\n");
|
|
129
|
+
}
|
|
130
|
+
/** Every classification, ordered — for help text that must not drift. */
|
|
131
|
+
export const ALL_CLASSIFICATIONS = [...CLASSIFICATIONS];
|
|
132
|
+
//# sourceMappingURL=describeDestinations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describeDestinations.js","sourceRoot":"","sources":["../../src/privacy/describeDestinations.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.
|
|
3
|
+
"version": "1.2.0-beta.2.canary.755",
|
|
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",
|