patchwork-os 1.2.0-beta.2.canary.679 → 1.2.0-beta.2.canary.680

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,97 @@
1
+ /**
2
+ * A short, stable identifier for the workspace an evidence record belongs to.
3
+ *
4
+ * ## Why evidence needs this and scoping does not
5
+ *
6
+ * Isolation, policy and blast radius are workspace-scoped already: one bridge,
7
+ * one workspace, one policy. Evidence is different, and deliberately so — it
8
+ * must OUTLIVE the workspace it describes. Deleting a workspace must not delete
9
+ * the record of what was done in it; that is the property an auditor is paying
10
+ * for. So evidence is not scoped by workspace, it is TAGGED with one.
11
+ *
12
+ * Concretely, today: no run row, gate decision, boundary receipt or trust
13
+ * checkpoint carries a workspace, so a decision cannot be attributed to the
14
+ * context it was made in. That is invisible while there is exactly one
15
+ * workspace and is the first thing to break when there are two — at which point
16
+ * two workspaces sharing a recipe name also share one trust ledger.
17
+ *
18
+ * ## Why a hash rather than the path
19
+ *
20
+ * Two reasons, both concrete.
21
+ *
22
+ * **Bytes.** `runs.jsonl` is capped at 1 MB and that cap is already what starves
23
+ * the trust ledger (#1337) — a full path is 40-60 bytes on a ~758-byte row,
24
+ * roughly 7%. Twelve hex characters is not.
25
+ *
26
+ * **Disclosure.** An evidence record is the artefact most likely to leave the
27
+ * machine — exported, attached to a compliance question, pasted into a ticket.
28
+ * A filesystem path names directories, and often a person: `/Users/<name>/...`.
29
+ * The id identifies the workspace to anyone holding the mapping and discloses
30
+ * nothing to anyone who is not.
31
+ *
32
+ * ## What it is not
33
+ *
34
+ * Not a secret, and not an authentication token — it is derived from a path
35
+ * anyone with the machine already knows, so it resists disclosure, not
36
+ * guessing.
37
+ *
38
+ * **Deliberately NOT a cryptographic hash, and please do not "fix" that.** The
39
+ * fingerprint below is FNV-1a: fast, stable, and obviously an identifier rather
40
+ * than a security primitive. An earlier revision used `createHash("sha256")`,
41
+ * which bought nothing — the value is not a secret and brute-forcing it means
42
+ * guessing a path, which a cryptographic digest does not prevent either — and
43
+ * cost something real: CodeQL correctly identifies a filesystem string reaching
44
+ * a password-hashing sink as `js/insufficient-password-hash`, because the sink
45
+ * is the sink regardless of intent. Restructuring beats suppressing; a
46
+ * suppression comment would rot and the next reader would not know why it was
47
+ * there. Not stable across a workspace being MOVED: the path is the
48
+ * identity, so relocating a workspace starts a new id. That is the honest
49
+ * behaviour — a moved directory genuinely may not be the same operating
50
+ * context — and the alternative (a stored id file) invents an identity that can
51
+ * be copied, which is worse for an audit record.
52
+ */
53
+ import path from "node:path";
54
+ /** Hex characters kept. 48 bits — ample against accidental collision between
55
+ * the handful of workspaces one installation ever sees, and short enough to
56
+ * add to a byte-capped log without argument. */
57
+ const ID_LENGTH = 12;
58
+ /**
59
+ * Derive the id for a workspace path.
60
+ *
61
+ * Normalised first so that `/a/b`, `/a/b/` and `/a/./b` are one workspace
62
+ * rather than three — otherwise a trailing slash in a config file silently
63
+ * splits an audit trail in half.
64
+ *
65
+ * Returns `undefined` for absent or empty input. A caller must be able to tell
66
+ * "no workspace was recorded" from "the workspace is «»", and the record
67
+ * omitting the field is how that is expressed.
68
+ */
69
+ export function workspaceIdFor(workspacePath) {
70
+ if (!workspacePath || !workspacePath.trim())
71
+ return "";
72
+ const normalised = path.resolve(workspacePath.trim());
73
+ // FNV-1a, 64-bit, over UTF-8 bytes. BigInt rather than `>>>` arithmetic
74
+ // because 32 bits collides at a few tens of thousands of inputs by the
75
+ // birthday bound, and an id that can silently merge two workspaces' evidence
76
+ // is worse than no id at all.
77
+ let hash = 0xcbf29ce484222325n;
78
+ const bytes = Buffer.from(normalised, "utf8");
79
+ for (const byte of bytes) {
80
+ hash ^= BigInt(byte);
81
+ hash = (hash * 0x100000001b3n) & 0xffffffffffffffffn;
82
+ }
83
+ return hash.toString(16).padStart(16, "0").slice(0, ID_LENGTH);
84
+ }
85
+ /**
86
+ * The id for this process's workspace, or `undefined` when it has none.
87
+ *
88
+ * `undefined` is deliberately not a sentinel string. An evidence row that omits
89
+ * the field says nothing; a row carrying `"unknown"` asserts that somebody
90
+ * looked and could not tell. Those differ, and the second is a claim this
91
+ * module has no grounds to make.
92
+ */
93
+ export function currentWorkspaceId(workspacePath) {
94
+ const id = workspaceIdFor(workspacePath);
95
+ return id === "" ? undefined : id;
96
+ }
97
+ //# sourceMappingURL=workspaceId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspaceId.js","sourceRoot":"","sources":["../src/workspaceId.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;iDAEiD;AACjD,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,aAAiC;IAC9D,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IACvD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC;IACtD,wEAAwE;IACxE,uEAAuE;IACvE,6EAA6E;IAC7E,8BAA8B;IAC9B,IAAI,IAAI,GAAG,mBAAmB,CAAC;IAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,GAAG,CAAC,IAAI,GAAG,cAAc,CAAC,GAAG,mBAAmB,CAAC;IACvD,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAiC;IAEjC,MAAM,EAAE,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;IACzC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;AACpC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchwork-os",
3
- "version": "1.2.0-beta.2.canary.679",
3
+ "version": "1.2.0-beta.2.canary.680",
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",