owlrunkit 0.20.0 → 0.21.0

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +63 -10
  3. package/docs/architecture/OWLCODA_RUNKIT_0201_RELEASE_TRUTH_AND_PROFILE_NORMALIZATION_PATCH_DECISION_20260813.md +95 -0
  4. package/docs/architecture/OWLCODA_RUNKIT_021_TEAM_DELIVERY_PHASE2_PRODUCT_DECISION_20260813.md +249 -0
  5. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +92 -12
  6. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  7. package/package.json +1 -1
  8. package/packages/attest/README.md +7 -1
  9. package/packages/attest/consumer-surface-v3.json +275 -0
  10. package/packages/attest/distribution-manifest-v1.json +14 -0
  11. package/packages/attest/schemas/canonical-candidate-identity-v1.schema.json +112 -0
  12. package/packages/attest/schemas/foreign-workspace-filesystem-identity-v1.schema.json +32 -0
  13. package/packages/attest/schemas/quick-verification-receipt-v4.schema.json +226 -0
  14. package/packages/attest/src/candidate-identity.mjs +454 -0
  15. package/packages/attest/src/index.mjs +11 -0
  16. package/packages/attest/src/quick-receipt-contract.mjs +126 -5
  17. package/packages/attest/src/quick.mjs +43 -8
  18. package/{registry-release-policy-v1.json → registry-release-policy-v2.json} +8 -9
  19. package/sbom.spdx.json +4 -4
  20. package/scripts/runkit-contract/core-contract.mjs +14 -1
  21. package/scripts/runkit-contract/fleet-skill-plan.mjs +223 -0
  22. package/scripts/runkit-contract/formal-workflow.mjs +3 -1
  23. package/scripts/runkit-contract/inspect-presentation.mjs +56 -0
  24. package/scripts/runkit-contract/install-codex-skill.mjs +1 -0
  25. package/scripts/runkit-contract/onboarding-bootstrap.mjs +15 -0
  26. package/scripts/runkit-contract/onboarding-doctor.mjs +201 -7
  27. package/scripts/runkit-contract/profile-onboarding.mjs +6 -0
  28. package/scripts/runkit-contract/project-cli-resolver.mjs +9 -1
  29. package/scripts/runkit-contract/quick-metrics.mjs +9 -5
  30. package/scripts/runkit-contract/quick-verify.mjs +108 -24
  31. package/scripts/runkit-contract/registry-adoption-gate.mjs +12 -3
  32. package/scripts/runkit-contract/registry-adoption.mjs +10 -5
  33. package/scripts/runkit-contract/runkit-cli.mjs +289 -19
  34. package/scripts/runkit-contract/schemas/delivery-lifecycle-v1.schema.json +91 -0
  35. package/scripts/runkit-contract/schemas/external-gate-v1.schema.json +68 -0
  36. package/scripts/runkit-contract/schemas/fleet-skill-upgrade-plan-v1.schema.json +140 -0
  37. package/scripts/runkit-contract/schemas/inspect-compact-v1.schema.json +93 -0
  38. package/scripts/runkit-contract/schemas/team-delivery-status-v2.schema.json +219 -0
  39. package/scripts/runkit-contract/schemas/team-project-event-v6.schema.json +82 -0
  40. package/scripts/runkit-contract/schemas/team-project-event-v7.schema.json +80 -0
  41. package/scripts/runkit-contract/schemas/team-project-status-v4.schema.json +208 -0
  42. package/scripts/runkit-contract/sync-release-identity.mjs +87 -0
  43. package/scripts/runkit-contract/team-delivery.mjs +128 -3
  44. package/scripts/runkit-contract/team-project.mjs +970 -9
@@ -0,0 +1,226 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://owlcoda.dev/schemas/runkit/trust/quick-verification-receipt-v4.schema.json",
4
+ "title": "OwlCoda Quick Verification Receipt V4",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schemaVersion",
9
+ "receiptId",
10
+ "assurance",
11
+ "authorizationGranted",
12
+ "coreIdentity",
13
+ "controllerWorkspaceRoot",
14
+ "workspaceBefore",
15
+ "sourceWorkspaceRoot",
16
+ "exactCommand",
17
+ "executionIsolation",
18
+ "mutationClasses",
19
+ "foreignWorkspace",
20
+ "verificationContext",
21
+ "startedAt",
22
+ "finishedAt",
23
+ "exitResult",
24
+ "outputArtifacts",
25
+ "workspaceAfter",
26
+ "mutationDecision",
27
+ "issueCodes"
28
+ ],
29
+ "properties": {
30
+ "schemaVersion": { "const": "OwlCodaQuickVerificationReceiptV4" },
31
+ "receiptId": { "type": "string", "minLength": 1 },
32
+ "assurance": { "const": "captured_verification" },
33
+ "authorizationGranted": { "const": false },
34
+ "coreIdentity": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["contractVersion", "coreVersion", "coreManifestSha256"],
38
+ "properties": {
39
+ "contractVersion": { "type": "string", "minLength": 1 },
40
+ "coreVersion": { "type": "string", "minLength": 1 },
41
+ "coreManifestSha256": { "$ref": "#/$defs/hash" }
42
+ }
43
+ },
44
+ "controllerWorkspaceRoot": { "type": "string", "minLength": 1 },
45
+ "workspaceBefore": {
46
+ "$ref": "quick-verification-receipt-v3.schema.json#/$defs/workspaceSnapshot"
47
+ },
48
+ "sourceWorkspaceRoot": { "type": "string", "minLength": 1 },
49
+ "exactCommand": {
50
+ "type": "object",
51
+ "additionalProperties": false,
52
+ "required": ["executable", "argv", "cwd"],
53
+ "properties": {
54
+ "executable": { "type": "string", "minLength": 1 },
55
+ "argv": { "type": "array", "items": { "type": "string" } },
56
+ "cwd": { "type": "string", "minLength": 1 }
57
+ }
58
+ },
59
+ "inputArtifacts": {
60
+ "type": "object",
61
+ "additionalProperties": false,
62
+ "required": ["stdin"],
63
+ "properties": {
64
+ "stdin": { "$ref": "quick-verification-receipt-v3.schema.json#/$defs/materialArtifact" }
65
+ }
66
+ },
67
+ "executionIsolation": {
68
+ "type": "object",
69
+ "additionalProperties": false,
70
+ "required": [
71
+ "schemaVersion",
72
+ "mode",
73
+ "consumerRoot",
74
+ "sourceCopy",
75
+ "cacheEnvironmentVariables",
76
+ "dependencyEnvironment",
77
+ "dependencyEnvironmentAfter",
78
+ "consumerDelta",
79
+ "cleanupStatus"
80
+ ],
81
+ "properties": {
82
+ "schemaVersion": { "const": "OwlCodaRunKitQuickIsolationV1" },
83
+ "mode": { "const": "temporary_consumer" },
84
+ "consumerRoot": { "type": "string", "minLength": 1 },
85
+ "sourceCopy": {
86
+ "type": "object",
87
+ "additionalProperties": false,
88
+ "required": ["fileCount", "totalBytes", "fingerprint"],
89
+ "properties": {
90
+ "fileCount": { "type": "integer", "minimum": 0 },
91
+ "totalBytes": { "type": "integer", "minimum": 0 },
92
+ "fingerprint": { "$ref": "#/$defs/hash" }
93
+ }
94
+ },
95
+ "cacheEnvironmentVariables": {
96
+ "type": "array",
97
+ "minItems": 1,
98
+ "uniqueItems": true,
99
+ "items": { "type": "string", "minLength": 1 }
100
+ },
101
+ "dependencyEnvironment": {
102
+ "oneOf": [
103
+ { "type": "null" },
104
+ { "$ref": "quick-verification-receipt-v3.schema.json#/$defs/dependencyEnvironment" }
105
+ ]
106
+ },
107
+ "dependencyEnvironmentAfter": {
108
+ "oneOf": [
109
+ { "type": "null" },
110
+ { "$ref": "quick-verification-receipt-v3.schema.json#/$defs/dependencyEnvironment" }
111
+ ]
112
+ },
113
+ "consumerDelta": {
114
+ "type": "object",
115
+ "additionalProperties": false,
116
+ "required": ["sourcePaths", "ignoredPaths"],
117
+ "properties": {
118
+ "sourcePaths": { "$ref": "quick-verification-receipt-v3.schema.json#/$defs/paths" },
119
+ "ignoredPaths": { "$ref": "quick-verification-receipt-v3.schema.json#/$defs/paths" }
120
+ }
121
+ },
122
+ "cleanupStatus": { "enum": ["removed", "cleanup_failed"] }
123
+ }
124
+ },
125
+ "mutationClasses": {
126
+ "type": "object",
127
+ "additionalProperties": false,
128
+ "required": [
129
+ "workspaceSource",
130
+ "boundIgnoredPaths",
131
+ "dependencyEnvironment",
132
+ "consumerSource",
133
+ "consumerIgnoredArtifacts",
134
+ "foreignTarget"
135
+ ],
136
+ "properties": {
137
+ "workspaceSource": { "enum": ["unchanged", "changed"] },
138
+ "boundIgnoredPaths": { "enum": ["not_bound", "unchanged", "changed"] },
139
+ "dependencyEnvironment": { "enum": ["not_bound", "unchanged", "changed"] },
140
+ "consumerSource": { "enum": ["unchanged", "changed"] },
141
+ "consumerIgnoredArtifacts": { "enum": ["none", "changed_and_discarded"] },
142
+ "foreignTarget": { "enum": ["unchanged", "changed"] }
143
+ }
144
+ },
145
+ "foreignWorkspace": {
146
+ "type": "object",
147
+ "additionalProperties": false,
148
+ "required": [
149
+ "candidateBefore",
150
+ "candidateAfter",
151
+ "filesystemBefore",
152
+ "filesystemAfter",
153
+ "zeroWriteObserved"
154
+ ],
155
+ "properties": {
156
+ "candidateBefore": { "$ref": "canonical-candidate-identity-v1.schema.json" },
157
+ "candidateAfter": { "$ref": "canonical-candidate-identity-v1.schema.json" },
158
+ "filesystemBefore": { "$ref": "foreign-workspace-filesystem-identity-v1.schema.json" },
159
+ "filesystemAfter": { "$ref": "foreign-workspace-filesystem-identity-v1.schema.json" },
160
+ "zeroWriteObserved": { "type": "boolean" }
161
+ }
162
+ },
163
+ "verificationContext": {
164
+ "type": "object",
165
+ "additionalProperties": false,
166
+ "required": ["platform", "architecture", "runtime"],
167
+ "properties": {
168
+ "platform": { "type": "string", "minLength": 1 },
169
+ "architecture": { "type": "string", "minLength": 1 },
170
+ "runtime": { "type": "string", "minLength": 1 }
171
+ }
172
+ },
173
+ "startedAt": { "type": "string", "format": "date-time" },
174
+ "finishedAt": { "type": "string", "format": "date-time" },
175
+ "exitResult": {
176
+ "type": "object",
177
+ "additionalProperties": false,
178
+ "required": ["exitCode", "signal"],
179
+ "properties": {
180
+ "exitCode": { "type": ["integer", "null"] },
181
+ "signal": { "type": ["string", "null"] }
182
+ }
183
+ },
184
+ "outputArtifacts": {
185
+ "type": "object",
186
+ "additionalProperties": false,
187
+ "required": ["stdout", "stderr"],
188
+ "properties": {
189
+ "stdout": { "$ref": "quick-verification-receipt-v3.schema.json#/$defs/materialArtifact" },
190
+ "stderr": { "$ref": "quick-verification-receipt-v3.schema.json#/$defs/materialArtifact" }
191
+ }
192
+ },
193
+ "workspaceAfter": {
194
+ "$ref": "quick-verification-receipt-v3.schema.json#/$defs/workspaceSnapshot"
195
+ },
196
+ "mutationDecision": {
197
+ "enum": [
198
+ "source_unchanged",
199
+ "invalidated_by_command_source_mutation",
200
+ "invalidated_by_bound_ignored_mutation",
201
+ "invalidated_by_dependency_environment_mutation",
202
+ "invalidated_by_consumer_source_mutation",
203
+ "invalidated_by_isolation_cleanup_failure",
204
+ "invalidated_by_foreign_workspace_mutation",
205
+ "invalidated_by_multiple_mutations"
206
+ ]
207
+ },
208
+ "issueCodes": {
209
+ "type": "array",
210
+ "uniqueItems": true,
211
+ "items": { "type": "string", "minLength": 1 }
212
+ },
213
+ "signatureRef": {
214
+ "type": "object",
215
+ "additionalProperties": false,
216
+ "required": ["path", "sha256"],
217
+ "properties": {
218
+ "path": { "type": "string", "minLength": 1 },
219
+ "sha256": { "$ref": "#/$defs/hash" }
220
+ }
221
+ }
222
+ },
223
+ "$defs": {
224
+ "hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }
225
+ }
226
+ }
@@ -0,0 +1,454 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import {
4
+ closeSync,
5
+ constants,
6
+ fstatSync,
7
+ lstatSync,
8
+ mkdtempSync,
9
+ openSync,
10
+ readSync,
11
+ readdirSync,
12
+ readlinkSync,
13
+ realpathSync,
14
+ rmSync,
15
+ } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import path from "node:path";
18
+
19
+ import {
20
+ canonicalJson,
21
+ hasExactKeys,
22
+ isSha256Ref,
23
+ sha256Bytes,
24
+ sha256Canonical,
25
+ } from "./formal.mjs";
26
+
27
+ export const CANDIDATE_LEDGER_ALGORITHM_V1 = "owlcoda-canonical-candidate-ledger-v1";
28
+ export const FOREIGN_FILESYSTEM_LEDGER_ALGORITHM_V1 = "owlcoda-foreign-workspace-ledger-v1";
29
+
30
+ const MAX_LEDGER_FILES = 100_000;
31
+ const MAX_LEDGER_BYTES = 2 * 1024 * 1024 * 1024;
32
+ const STATUS_ARGV = Object.freeze([
33
+ "git", "-c", "color.ui=false", "-c", "core.abbrev=40",
34
+ "status", "--porcelain=v1", "-z", "--untracked-files=all",
35
+ ]);
36
+ const TRANSPORT_DIFF_ARGV = Object.freeze([
37
+ "git", "-c", "color.ui=false", "-c", "core.abbrev=40",
38
+ "diff", "--full-index", "--binary", "--no-ext-diff", "--no-textconv", "HEAD",
39
+ ]);
40
+ const GIT_IDENTITY_ENVIRONMENT = Object.freeze({
41
+ GIT_OPTIONAL_LOCKS: "0",
42
+ LC_ALL: "C",
43
+ });
44
+
45
+ function compareCodeUnits(left, right) {
46
+ return left < right ? -1 : left > right ? 1 : 0;
47
+ }
48
+
49
+ function git(root, argv) {
50
+ return execFileSync(argv[0], argv.slice(1), {
51
+ cwd: root,
52
+ encoding: "utf8",
53
+ maxBuffer: 64 * 1024 * 1024,
54
+ stdio: ["ignore", "pipe", "pipe"],
55
+ env: {
56
+ ...process.env,
57
+ ...GIT_IDENTITY_ENVIRONMENT,
58
+ },
59
+ });
60
+ }
61
+
62
+ function sameFileIdentity(left, right) {
63
+ return left.dev === right.dev
64
+ && left.ino === right.ino
65
+ && left.mode === right.mode
66
+ && left.size === right.size
67
+ && left.mtimeMs === right.mtimeMs
68
+ && left.ctimeMs === right.ctimeMs;
69
+ }
70
+
71
+ function hashRegularFile(absolute, expectedStat, maximumBytes = MAX_LEDGER_BYTES) {
72
+ if (!expectedStat.isFile() || expectedStat.size > maximumBytes) {
73
+ throw new Error(`Candidate file exceeds the bounded ledger limit: ${absolute}`);
74
+ }
75
+ const descriptor = openSync(absolute, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
76
+ try {
77
+ const before = fstatSync(descriptor);
78
+ if (!before.isFile() || !sameFileIdentity(before, expectedStat)) {
79
+ throw new Error(`Candidate file identity changed before hashing: ${absolute}`);
80
+ }
81
+ const hash = createHash("sha256");
82
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
83
+ let sizeBytes = 0;
84
+ while (true) {
85
+ const bytesRead = readSync(descriptor, buffer, 0, buffer.byteLength, null);
86
+ if (bytesRead === 0) break;
87
+ sizeBytes += bytesRead;
88
+ if (sizeBytes > maximumBytes) {
89
+ throw new Error(`Candidate file exceeds the bounded ledger limit: ${absolute}`);
90
+ }
91
+ hash.update(buffer.subarray(0, bytesRead));
92
+ }
93
+ const after = fstatSync(descriptor);
94
+ const current = lstatSync(absolute);
95
+ if (!sameFileIdentity(before, after) || !sameFileIdentity(after, current)) {
96
+ throw new Error(`Candidate file changed while hashing: ${absolute}`);
97
+ }
98
+ return { sizeBytes, sha256: `sha256:${hash.digest("hex")}` };
99
+ } finally {
100
+ closeSync(descriptor);
101
+ }
102
+ }
103
+
104
+ function captureTransportDiff(root) {
105
+ const scratch = realpathSync(mkdtempSync(path.join(tmpdir(), "owlrunkit-candidate-diff-")));
106
+ const output = path.join(scratch, "candidate.diff");
107
+ try {
108
+ const descriptor = openSync(output, "wx", 0o600);
109
+ try {
110
+ execFileSync(TRANSPORT_DIFF_ARGV[0], TRANSPORT_DIFF_ARGV.slice(1), {
111
+ cwd: root,
112
+ maxBuffer: 64 * 1024 * 1024,
113
+ stdio: ["ignore", descriptor, "pipe"],
114
+ env: { ...process.env, ...GIT_IDENTITY_ENVIRONMENT },
115
+ });
116
+ } finally {
117
+ closeSync(descriptor);
118
+ }
119
+ return hashRegularFile(output, lstatSync(output));
120
+ } finally {
121
+ rmSync(scratch, { recursive: true, force: true });
122
+ }
123
+ }
124
+
125
+ function canonicalGitRoot(requestedRoot) {
126
+ const root = realpathSync(requestedRoot);
127
+ const stat = lstatSync(root);
128
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
129
+ throw new Error("Candidate workspace must be a real directory.");
130
+ }
131
+ const gitRoot = realpathSync(git(root, ["git", "rev-parse", "--show-toplevel"]).trim());
132
+ if (gitRoot !== root) {
133
+ throw new Error("Candidate workspace must be the Git worktree top-level.");
134
+ }
135
+ return root;
136
+ }
137
+
138
+ function normalizeGitPath(value) {
139
+ return value.split(path.sep).join("/");
140
+ }
141
+
142
+ function safeRelativePath(value) {
143
+ if (typeof value !== "string" || value.length === 0 || path.isAbsolute(value)) return false;
144
+ if (value.includes("\\") || value.includes("\0")) return false;
145
+ return value.split("/").every(segment => segment.length > 0 && segment !== "." && segment !== "..");
146
+ }
147
+
148
+ function withinRoot(root, candidate) {
149
+ const relative = path.relative(root, candidate);
150
+ return relative === ""
151
+ || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
152
+ }
153
+
154
+ function changedRecords(root) {
155
+ const raw = git(root, STATUS_ARGV);
156
+ const fields = raw.split("\0");
157
+ const records = [];
158
+ for (let index = 0; index < fields.length; index += 1) {
159
+ const field = fields[index];
160
+ if (!field) continue;
161
+ const status = field.slice(0, 2);
162
+ const currentPath = normalizeGitPath(field.slice(3));
163
+ let previousPath = null;
164
+ if (status.includes("R") || status.includes("C")) {
165
+ previousPath = normalizeGitPath(fields[index + 1] ?? "");
166
+ index += 1;
167
+ }
168
+ if (!safeRelativePath(currentPath) || previousPath !== null && !safeRelativePath(previousPath)) {
169
+ throw new Error("Git status contains an unsafe candidate path.");
170
+ }
171
+ records.push({ status, path: currentPath, previousPath });
172
+ }
173
+ return records.sort((left, right) => (
174
+ compareCodeUnits(left.path, right.path)
175
+ || compareCodeUnits(left.previousPath ?? "", right.previousPath ?? "")
176
+ ));
177
+ }
178
+
179
+ function fileLedgerEntry(root, record) {
180
+ const deleted = record.status.includes("D");
181
+ const operation = record.status.includes("R")
182
+ ? "renamed"
183
+ : record.status.includes("C")
184
+ ? "copied"
185
+ : deleted
186
+ ? "deleted"
187
+ : record.status === "??" || record.status.includes("A")
188
+ ? "added"
189
+ : "modified";
190
+ if (deleted) {
191
+ return {
192
+ path: record.path,
193
+ previousPath: record.previousPath,
194
+ operation,
195
+ status: record.status,
196
+ kind: null,
197
+ mode: null,
198
+ sizeBytes: null,
199
+ sha256: null,
200
+ };
201
+ }
202
+ const absolute = path.resolve(root, ...record.path.split("/"));
203
+ if (!withinRoot(root, absolute)) throw new Error(`Candidate path escapes workspace: ${record.path}`);
204
+ const stat = lstatSync(absolute);
205
+ let kind;
206
+ let material;
207
+ if (stat.isSymbolicLink()) {
208
+ kind = "symlink";
209
+ const bytes = Buffer.from(readlinkSync(absolute), "utf8");
210
+ material = { sizeBytes: bytes.byteLength, sha256: sha256Bytes(bytes) };
211
+ } else if (stat.isFile()) {
212
+ kind = "file";
213
+ material = hashRegularFile(absolute, stat);
214
+ } else if (stat.isDirectory()) {
215
+ kind = "gitlink";
216
+ const bytes = Buffer.from(git(absolute, ["git", "rev-parse", "HEAD"]).trim(), "utf8");
217
+ material = { sizeBytes: bytes.byteLength, sha256: sha256Bytes(bytes) };
218
+ } else {
219
+ throw new Error(`Candidate path is not a supported file type: ${record.path}`);
220
+ }
221
+ return {
222
+ path: record.path,
223
+ previousPath: record.previousPath,
224
+ operation,
225
+ status: record.status,
226
+ kind,
227
+ mode: stat.mode & 0o777,
228
+ ...material,
229
+ };
230
+ }
231
+
232
+ function ledgerBinding(ledger) {
233
+ const bytes = Buffer.from(canonicalJson(ledger), "utf8");
234
+ return {
235
+ ledgerByteLength: bytes.byteLength,
236
+ ledgerSha256: sha256Bytes(bytes),
237
+ };
238
+ }
239
+
240
+ function gitVersion() {
241
+ return execFileSync("git", ["--version"], {
242
+ encoding: "utf8",
243
+ stdio: ["ignore", "pipe", "pipe"],
244
+ env: { ...process.env, LC_ALL: "C" },
245
+ }).trim();
246
+ }
247
+
248
+ export function captureCanonicalCandidateIdentityV1(workspaceRoot) {
249
+ const root = canonicalGitRoot(workspaceRoot);
250
+ const ledger = [];
251
+ let totalBytes = 0;
252
+ for (const record of changedRecords(root)) {
253
+ const entry = fileLedgerEntry(root, record);
254
+ totalBytes += entry.sizeBytes ?? 0;
255
+ ledger.push(entry);
256
+ if (ledger.length > MAX_LEDGER_FILES || totalBytes > MAX_LEDGER_BYTES) {
257
+ throw new Error("Candidate exceeds the bounded ledger limit.");
258
+ }
259
+ }
260
+ const binding = ledgerBinding(ledger);
261
+ const headCommit = git(root, ["git", "rev-parse", "HEAD"]).trim();
262
+ const trackedTree = git(root, ["git", "rev-parse", "HEAD^{tree}"]).trim();
263
+ const transport = captureTransportDiff(root);
264
+ const candidateFingerprint = sha256Canonical({
265
+ manifestAlgorithmId: CANDIDATE_LEDGER_ALGORITHM_V1,
266
+ headCommit,
267
+ trackedTree,
268
+ ledgerSha256: binding.ledgerSha256,
269
+ });
270
+ return {
271
+ schemaVersion: "OwlCodaRunKitCanonicalCandidateIdentityV1",
272
+ manifestAlgorithmId: CANDIDATE_LEDGER_ALGORITHM_V1,
273
+ workspaceRoot: root,
274
+ headCommit,
275
+ trackedTree,
276
+ gitVersion: gitVersion(),
277
+ discoveryCommand: [...STATUS_ARGV],
278
+ discoveryEnvironment: { ...GIT_IDENTITY_ENVIRONMENT },
279
+ ledger,
280
+ ...binding,
281
+ transportDiff: {
282
+ exactArgv: [...TRANSPORT_DIFF_ARGV],
283
+ environment: { ...GIT_IDENTITY_ENVIRONMENT },
284
+ sizeBytes: transport.sizeBytes,
285
+ sha256: transport.sha256,
286
+ identityRole: "transport_only",
287
+ },
288
+ candidateFingerprint,
289
+ authorizationGranted: false,
290
+ };
291
+ }
292
+
293
+ function excludedFilesystemPath(relativePath) {
294
+ return relativePath === ".git"
295
+ || relativePath.startsWith(".git/");
296
+ }
297
+
298
+ export function captureForeignWorkspaceFilesystemIdentityV1(workspaceRoot) {
299
+ const root = canonicalGitRoot(workspaceRoot);
300
+ const ledger = [];
301
+ let totalFileBytes = 0;
302
+ const visit = (directory, prefix) => {
303
+ for (const name of readdirSync(directory).sort(compareCodeUnits)) {
304
+ const relativePath = prefix ? `${prefix}/${name}` : name;
305
+ if (excludedFilesystemPath(relativePath)) continue;
306
+ const absolute = path.join(directory, name);
307
+ const stat = lstatSync(absolute);
308
+ if (stat.isDirectory() && !stat.isSymbolicLink()) {
309
+ ledger.push({
310
+ path: relativePath,
311
+ kind: "directory",
312
+ mode: stat.mode & 0o777,
313
+ sizeBytes: 0,
314
+ sha256: null,
315
+ });
316
+ visit(absolute, relativePath);
317
+ } else {
318
+ let kind;
319
+ let material;
320
+ if (stat.isSymbolicLink()) {
321
+ kind = "symlink";
322
+ const bytes = Buffer.from(readlinkSync(absolute), "utf8");
323
+ material = { sizeBytes: bytes.byteLength, sha256: sha256Bytes(bytes) };
324
+ } else if (stat.isFile()) {
325
+ kind = "file";
326
+ if (totalFileBytes + stat.size > MAX_LEDGER_BYTES) {
327
+ throw new Error("Foreign workspace exceeds the bounded zero-write ledger limit.");
328
+ }
329
+ material = hashRegularFile(absolute, stat, MAX_LEDGER_BYTES - totalFileBytes);
330
+ } else {
331
+ throw new Error(`Foreign workspace contains an unsupported file type: ${relativePath}`);
332
+ }
333
+ totalFileBytes += material.sizeBytes;
334
+ ledger.push({
335
+ path: relativePath,
336
+ kind,
337
+ mode: stat.mode & 0o777,
338
+ ...material,
339
+ });
340
+ }
341
+ if (ledger.length > MAX_LEDGER_FILES || totalFileBytes > MAX_LEDGER_BYTES) {
342
+ throw new Error("Foreign workspace exceeds the bounded zero-write ledger limit.");
343
+ }
344
+ }
345
+ };
346
+ visit(root, "");
347
+ ledger.sort((left, right) => compareCodeUnits(left.path, right.path));
348
+ const binding = ledgerBinding(ledger);
349
+ return {
350
+ schemaVersion: "OwlCodaRunKitForeignWorkspaceFilesystemIdentityV1",
351
+ manifestAlgorithmId: FOREIGN_FILESYSTEM_LEDGER_ALGORITHM_V1,
352
+ workspaceRoot: root,
353
+ excludedRoots: [".git"],
354
+ entryCount: ledger.length,
355
+ totalFileBytes,
356
+ ...binding,
357
+ filesystemFingerprint: sha256Canonical({
358
+ manifestAlgorithmId: FOREIGN_FILESYSTEM_LEDGER_ALGORITHM_V1,
359
+ ledgerSha256: binding.ledgerSha256,
360
+ entryCount: ledger.length,
361
+ totalFileBytes,
362
+ }),
363
+ };
364
+ }
365
+
366
+ function validLedgerEntry(value) {
367
+ return hasExactKeys(value, [
368
+ "path", "previousPath", "operation", "status", "kind", "mode", "sizeBytes", "sha256",
369
+ ])
370
+ && safeRelativePath(value.path)
371
+ && (value.previousPath === null || safeRelativePath(value.previousPath))
372
+ && ["added", "copied", "deleted", "modified", "renamed"].includes(value.operation)
373
+ && typeof value.status === "string"
374
+ && value.status.length === 2
375
+ && (value.kind === null || ["file", "gitlink", "symlink"].includes(value.kind))
376
+ && (value.mode === null || Number.isInteger(value.mode) && value.mode >= 0 && value.mode <= 0o777)
377
+ && (value.sizeBytes === null || Number.isInteger(value.sizeBytes) && value.sizeBytes >= 0)
378
+ && (value.sha256 === null || isSha256Ref(value.sha256));
379
+ }
380
+
381
+ export function validCanonicalCandidateIdentityV1(value) {
382
+ if (!hasExactKeys(value, [
383
+ "schemaVersion", "manifestAlgorithmId", "workspaceRoot", "headCommit", "trackedTree",
384
+ "gitVersion", "discoveryCommand", "discoveryEnvironment", "ledger", "ledgerByteLength", "ledgerSha256",
385
+ "transportDiff", "candidateFingerprint", "authorizationGranted",
386
+ ])) return false;
387
+ if (
388
+ value.schemaVersion !== "OwlCodaRunKitCanonicalCandidateIdentityV1"
389
+ || value.manifestAlgorithmId !== CANDIDATE_LEDGER_ALGORITHM_V1
390
+ || typeof value.workspaceRoot !== "string"
391
+ || !path.isAbsolute(value.workspaceRoot)
392
+ || !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(value.headCommit)
393
+ || !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(value.trackedTree)
394
+ || typeof value.gitVersion !== "string"
395
+ || JSON.stringify(value.discoveryCommand) !== JSON.stringify(STATUS_ARGV)
396
+ || !hasExactKeys(value.discoveryEnvironment, ["GIT_OPTIONAL_LOCKS", "LC_ALL"])
397
+ || value.discoveryEnvironment.GIT_OPTIONAL_LOCKS !== "0"
398
+ || value.discoveryEnvironment.LC_ALL !== "C"
399
+ || !Array.isArray(value.ledger)
400
+ || !value.ledger.every(validLedgerEntry)
401
+ || !isSha256Ref(value.ledgerSha256)
402
+ || !isSha256Ref(value.candidateFingerprint)
403
+ || value.authorizationGranted !== false
404
+ || !hasExactKeys(value.transportDiff, ["exactArgv", "environment", "sizeBytes", "sha256", "identityRole"])
405
+ || JSON.stringify(value.transportDiff.exactArgv) !== JSON.stringify(TRANSPORT_DIFF_ARGV)
406
+ || !hasExactKeys(value.transportDiff.environment, ["GIT_OPTIONAL_LOCKS", "LC_ALL"])
407
+ || value.transportDiff.environment.GIT_OPTIONAL_LOCKS !== "0"
408
+ || value.transportDiff.environment.LC_ALL !== "C"
409
+ || !Number.isInteger(value.transportDiff.sizeBytes)
410
+ || value.transportDiff.sizeBytes < 0
411
+ || !isSha256Ref(value.transportDiff.sha256)
412
+ || value.transportDiff.identityRole !== "transport_only"
413
+ ) return false;
414
+ const sorted = [...value.ledger].sort((left, right) => (
415
+ compareCodeUnits(left.path, right.path)
416
+ || compareCodeUnits(left.previousPath ?? "", right.previousPath ?? "")
417
+ ));
418
+ const binding = ledgerBinding(value.ledger);
419
+ return canonicalJson(sorted) === canonicalJson(value.ledger)
420
+ && binding.ledgerByteLength === value.ledgerByteLength
421
+ && binding.ledgerSha256 === value.ledgerSha256
422
+ && value.candidateFingerprint === sha256Canonical({
423
+ manifestAlgorithmId: CANDIDATE_LEDGER_ALGORITHM_V1,
424
+ headCommit: value.headCommit,
425
+ trackedTree: value.trackedTree,
426
+ ledgerSha256: value.ledgerSha256,
427
+ });
428
+ }
429
+
430
+ export function validForeignWorkspaceFilesystemIdentityV1(value) {
431
+ return hasExactKeys(value, [
432
+ "schemaVersion", "manifestAlgorithmId", "workspaceRoot", "excludedRoots", "entryCount", "totalFileBytes",
433
+ "ledgerByteLength", "ledgerSha256", "filesystemFingerprint",
434
+ ])
435
+ && value.schemaVersion === "OwlCodaRunKitForeignWorkspaceFilesystemIdentityV1"
436
+ && value.manifestAlgorithmId === FOREIGN_FILESYSTEM_LEDGER_ALGORITHM_V1
437
+ && typeof value.workspaceRoot === "string"
438
+ && path.isAbsolute(value.workspaceRoot)
439
+ && JSON.stringify(value.excludedRoots) === JSON.stringify([".git"])
440
+ && Number.isInteger(value.entryCount)
441
+ && value.entryCount >= 0
442
+ && Number.isInteger(value.totalFileBytes)
443
+ && value.totalFileBytes >= 0
444
+ && Number.isInteger(value.ledgerByteLength)
445
+ && value.ledgerByteLength >= 0
446
+ && isSha256Ref(value.ledgerSha256)
447
+ && isSha256Ref(value.filesystemFingerprint)
448
+ && value.filesystemFingerprint === sha256Canonical({
449
+ manifestAlgorithmId: FOREIGN_FILESYSTEM_LEDGER_ALGORITHM_V1,
450
+ ledgerSha256: value.ledgerSha256,
451
+ entryCount: value.entryCount,
452
+ totalFileBytes: value.totalFileBytes,
453
+ });
454
+ }