owlrunkit 0.13.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.
- package/CHANGELOG.md +9 -0
- package/LICENSE +674 -0
- package/NOTICE.md +137 -0
- package/README.md +44 -0
- package/SOURCE.md +10 -0
- package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_1.md +160 -0
- package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +459 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +408 -0
- package/integrations/codex/skills/owlcoda-runkit/agents/openai.yaml +4 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/agents-snippet.md +7 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +10 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/coverage-adopt-request.json +26 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/execution-delivery-packet.json +13 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/finalize-request.json +18 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/goal-contract.json +11 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/profiles.json +9 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +11 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/resource-preflight-request.json +56 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/resume-request.json +10 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/snapshot-request.json +23 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/verification-context.json +15 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/verification-receipt.json +11 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/verify-plan-request.json +31 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/visual-smoke-request.json +32 -0
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/worker-lease.json +7 -0
- package/package.json +24 -0
- package/packages/attest/LICENSE +674 -0
- package/packages/attest/README.md +63 -0
- package/packages/attest/cli/owlcoda-attest.mjs +155 -0
- package/packages/attest/consumer-surface-v1.json +255 -0
- package/packages/attest/distribution-manifest-v1.json +104 -0
- package/packages/attest/fixtures/bad/accepted-authority-leak.json +7 -0
- package/packages/attest/fixtures/bad/accepted-failed-receipt.json +7 -0
- package/packages/attest/fixtures/bad/active-context-binding-mismatch.json +7 -0
- package/packages/attest/fixtures/bad/active-source-binding-mismatch.json +7 -0
- package/packages/attest/fixtures/bad/context-fingerprint-mismatch.json +7 -0
- package/packages/attest/fixtures/bad/duplicate-object-key.json +8 -0
- package/packages/attest/fixtures/bad/multiple-active-leaves.json +10 -0
- package/packages/attest/fixtures/bad/orphan-replacement.json +7 -0
- package/packages/attest/fixtures/bad/root-shape-extra-property.json +8 -0
- package/packages/attest/fixtures/bad/source-fingerprint-mismatch.json +7 -0
- package/packages/attest/fixtures/good/accepted-replacement-chain.json +59 -0
- package/packages/attest/fixtures/good/blocked-single-leaf.json +9 -0
- package/packages/attest/fixtures/index.json +90 -0
- package/packages/attest/sbom.spdx.json +33 -0
- package/packages/attest/schemas/attestation-ref-v1.schema.json +44 -0
- package/packages/attest/schemas/attestation-result-v1.schema.json +163 -0
- package/packages/attest/schemas/offline-attestation-bundle-v1.schema.json +30 -0
- package/packages/attest/schemas/public-verification-bundle-v1.schema.json +74 -0
- package/packages/attest/schemas/quick-verification-receipt-v1.schema.json +219 -0
- package/packages/attest/schemas/workspace-snapshot-v1.schema.json +135 -0
- package/packages/attest/src/canonical.mjs +12 -0
- package/packages/attest/src/formal.mjs +762 -0
- package/packages/attest/src/index.mjs +113 -0
- package/packages/attest/src/offline-bundle.mjs +108 -0
- package/packages/attest/src/quick-receipt-contract.mjs +156 -0
- package/packages/attest/src/quick.mjs +274 -0
- package/packages/attest/src/reference-contract.mjs +64 -0
- package/packages/attest/src/reference.mjs +131 -0
- package/packages/attest/src/workspace-snapshot.mjs +171 -0
- package/registry-release-policy-v1.json +34 -0
- package/sbom.spdx.json +39 -0
- package/scripts/runkit-contract/acceptance-evidence.mjs +103 -0
- package/scripts/runkit-contract/core-contract.mjs +392 -0
- package/scripts/runkit-contract/coverage-adoption.mjs +414 -0
- package/scripts/runkit-contract/delivery-create.mjs +252 -0
- package/scripts/runkit-contract/delivery-selection.mjs +31 -0
- package/scripts/runkit-contract/finalize.mjs +790 -0
- package/scripts/runkit-contract/inspect-presentation.mjs +183 -0
- package/scripts/runkit-contract/install-codex-skill.mjs +239 -0
- package/scripts/runkit-contract/lease-lifecycle.mjs +324 -0
- package/scripts/runkit-contract/lifecycle-orchestration.mjs +415 -0
- package/scripts/runkit-contract/offline-store.mjs +248 -0
- package/scripts/runkit-contract/profile-impact.mjs +223 -0
- package/scripts/runkit-contract/provenance-common.mjs +153 -0
- package/scripts/runkit-contract/quick-attest.mjs +4 -0
- package/scripts/runkit-contract/quick-canonical.mjs +41 -0
- package/scripts/runkit-contract/quick-metrics.mjs +86 -0
- package/scripts/runkit-contract/quick-receipt.mjs +139 -0
- package/scripts/runkit-contract/quick-verify.mjs +201 -0
- package/scripts/runkit-contract/quick-workspace-snapshot.mjs +3 -0
- package/scripts/runkit-contract/ready-for-commit.mjs +188 -0
- package/scripts/runkit-contract/receipt-lineage.mjs +332 -0
- package/scripts/runkit-contract/registry-adoption-gate.mjs +117 -0
- package/scripts/runkit-contract/repair-execution.mjs +997 -0
- package/scripts/runkit-contract/resource-preflight.mjs +829 -0
- package/scripts/runkit-contract/resume-execution.mjs +569 -0
- package/scripts/runkit-contract/runkit-cli.mjs +1214 -0
- package/scripts/runkit-contract/schemas/closeout-receipt-v2.schema.json +42 -0
- package/scripts/runkit-contract/schemas/core-artifact-v2.schema.json +23 -0
- package/scripts/runkit-contract/schemas/core-artifact.schema.json +23 -0
- package/scripts/runkit-contract/schemas/coverage-adopt-request-v1.schema.json +66 -0
- package/scripts/runkit-contract/schemas/engine-pin-v2.schema.json +14 -0
- package/scripts/runkit-contract/schemas/engine-pin.schema.json +14 -0
- package/scripts/runkit-contract/schemas/evidence-coverage-index-v1.schema.json +131 -0
- package/scripts/runkit-contract/schemas/finalize-request-v1.schema.json +131 -0
- package/scripts/runkit-contract/schemas/profiles.schema.json +47 -0
- package/scripts/runkit-contract/schemas/project-config-v2.schema.json +13 -0
- package/scripts/runkit-contract/schemas/project-config.schema.json +13 -0
- package/scripts/runkit-contract/schemas/ready-for-commit-v1.schema.json +26 -0
- package/scripts/runkit-contract/schemas/replayable-evidence-v1.schema.json +82 -0
- package/scripts/runkit-contract/schemas/resource-preflight-request-v1.schema.json +178 -0
- package/scripts/runkit-contract/schemas/resource-preflight-v1.schema.json +153 -0
- package/scripts/runkit-contract/schemas/resume-attempt-v1.schema.json +80 -0
- package/scripts/runkit-contract/schemas/resume-request-v1.schema.json +77 -0
- package/scripts/runkit-contract/schemas/snapshot-v1.schema.json +71 -0
- package/scripts/runkit-contract/schemas/verification-context-v1.schema.json +92 -0
- package/scripts/runkit-contract/schemas/verification-plan-v1.schema.json +150 -0
- package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +118 -0
- package/scripts/runkit-contract/schemas/verify-plan-request-v1.schema.json +56 -0
- package/scripts/runkit-contract/schemas/visual-smoke-request-v1.schema.json +64 -0
- package/scripts/runkit-contract/schemas/visual-smoke-result-v1.schema.json +68 -0
- package/scripts/runkit-contract/snapshot.mjs +340 -0
- package/scripts/runkit-contract/source-fingerprint.mjs +404 -0
- package/scripts/runkit-contract/templates/coverage-adopt-request.json +26 -0
- package/scripts/runkit-contract/templates/finalize-request.json +18 -0
- package/scripts/runkit-contract/templates/goal-contract.json +11 -0
- package/scripts/runkit-contract/templates/ready-for-commit-request.json +11 -0
- package/scripts/runkit-contract/templates/resource-preflight-request.json +56 -0
- package/scripts/runkit-contract/templates/resume-request.json +10 -0
- package/scripts/runkit-contract/templates/snapshot-request.json +23 -0
- package/scripts/runkit-contract/templates/verify-plan-request.json +31 -0
- package/scripts/runkit-contract/templates/visual-smoke-request.json +32 -0
- package/scripts/runkit-contract/templates/worker-lease.json +7 -0
- package/scripts/runkit-contract/verification-context.mjs +141 -0
- package/scripts/runkit-contract/verification-plan.mjs +608 -0
- package/scripts/runkit-contract/verification-receipt-gate.mjs +566 -0
- package/scripts/runkit-contract/visual-smoke.mjs +410 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decodeUtf8Strict,
|
|
3
|
+
hasExactKeys,
|
|
4
|
+
isPlainJsonObject,
|
|
5
|
+
isSha256Ref,
|
|
6
|
+
parseJsonStrict,
|
|
7
|
+
readFileBytesBounded,
|
|
8
|
+
sha256Bytes,
|
|
9
|
+
} from "./formal.mjs";
|
|
10
|
+
import { validQuickReceiptShape } from "./quick-receipt-contract.mjs";
|
|
11
|
+
|
|
12
|
+
export function referenceError(message) {
|
|
13
|
+
const error = new Error(message);
|
|
14
|
+
error.code = "receipt_schema_invalid";
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function readQuickReceiptFile(receiptPath) {
|
|
19
|
+
const { absolutePath, bytes } = readFileBytesBounded(receiptPath);
|
|
20
|
+
const receipt = parseJsonStrict(decodeUtf8Strict(bytes));
|
|
21
|
+
if (!isPlainJsonObject(receipt) || !validQuickReceiptShape(receipt)) {
|
|
22
|
+
throw referenceError("receipt does not satisfy Quick Receipt V1");
|
|
23
|
+
}
|
|
24
|
+
return { absolutePath, bytes, receipt };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createAttestationRef(receiptPath) {
|
|
28
|
+
const { bytes, receipt } = readQuickReceiptFile(receiptPath);
|
|
29
|
+
return {
|
|
30
|
+
schemaVersion: "OwlCodaAttestationRefV1",
|
|
31
|
+
receiptId: receipt.receiptId,
|
|
32
|
+
receiptSha256: sha256Bytes(bytes),
|
|
33
|
+
coreIdentity: {
|
|
34
|
+
contractVersion: receipt.coreIdentity.contractVersion,
|
|
35
|
+
coreManifestSha256: receipt.coreIdentity.coreManifestSha256,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseAttestationRef(input) {
|
|
41
|
+
let value;
|
|
42
|
+
try {
|
|
43
|
+
value = typeof input === "string" ? parseJsonStrict(input) : input;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error?.code === "DUPLICATE_OBJECT_KEY") {
|
|
46
|
+
error.code = "receipt_duplicate_key";
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
if (
|
|
51
|
+
!hasExactKeys(value, ["schemaVersion", "receiptId", "receiptSha256", "coreIdentity"])
|
|
52
|
+
|| value.schemaVersion !== "OwlCodaAttestationRefV1"
|
|
53
|
+
|| typeof value.receiptId !== "string"
|
|
54
|
+
|| value.receiptId.length === 0
|
|
55
|
+
|| !isSha256Ref(value.receiptSha256)
|
|
56
|
+
|| !hasExactKeys(value.coreIdentity, ["contractVersion", "coreManifestSha256"])
|
|
57
|
+
|| typeof value.coreIdentity.contractVersion !== "string"
|
|
58
|
+
|| value.coreIdentity.contractVersion.length === 0
|
|
59
|
+
|| !isSha256Ref(value.coreIdentity.coreManifestSha256)
|
|
60
|
+
) {
|
|
61
|
+
throw referenceError("reference does not satisfy OwlCodaAttestationRefV1");
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
lstatSync,
|
|
4
|
+
realpathSync,
|
|
5
|
+
} from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
import { sha256Bytes } from "./formal.mjs";
|
|
9
|
+
import { attestQuickReceiptDetailsFromBytes } from "./quick.mjs";
|
|
10
|
+
import {
|
|
11
|
+
createAttestationRef,
|
|
12
|
+
parseAttestationRef,
|
|
13
|
+
readQuickReceiptFile,
|
|
14
|
+
referenceError,
|
|
15
|
+
} from "./reference-contract.mjs";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
createAttestationRef,
|
|
19
|
+
parseAttestationRef,
|
|
20
|
+
} from "./reference-contract.mjs";
|
|
21
|
+
|
|
22
|
+
function withinRoot(root, candidate) {
|
|
23
|
+
const relative = path.relative(root, candidate);
|
|
24
|
+
return relative !== ""
|
|
25
|
+
&& relative !== ".."
|
|
26
|
+
&& !relative.startsWith(`..${path.sep}`)
|
|
27
|
+
&& !path.isAbsolute(relative);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function safeQuickCandidate(store, receiptId) {
|
|
31
|
+
if (!/^[A-Za-z0-9._-]+$/.test(receiptId)) return null;
|
|
32
|
+
const rootStat = lstatSync(store);
|
|
33
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
|
|
34
|
+
const root = realpathSync(store);
|
|
35
|
+
const segments = ["quick", "receipts", receiptId, "receipt.json"];
|
|
36
|
+
let current = root;
|
|
37
|
+
for (const segment of segments) {
|
|
38
|
+
current = path.join(current, segment);
|
|
39
|
+
if (!existsSync(current)) return null;
|
|
40
|
+
const stat = lstatSync(current);
|
|
41
|
+
if (stat.isSymbolicLink()) return null;
|
|
42
|
+
if (segment === "receipt.json" ? !stat.isFile() : !stat.isDirectory()) return null;
|
|
43
|
+
}
|
|
44
|
+
const resolved = realpathSync(current);
|
|
45
|
+
return withinRoot(root, resolved) ? resolved : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function issue(code) {
|
|
49
|
+
return { code };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveAttestationRef({ reference, stores, workspaceRoot }) {
|
|
53
|
+
const parsed = parseAttestationRef(reference);
|
|
54
|
+
if (!Array.isArray(stores) || stores.length === 0) {
|
|
55
|
+
throw referenceError("at least one explicit local store is required");
|
|
56
|
+
}
|
|
57
|
+
const candidates = [];
|
|
58
|
+
for (const selectedStore of stores) {
|
|
59
|
+
if (typeof selectedStore !== "string" || selectedStore.length === 0) {
|
|
60
|
+
throw referenceError("store paths must be non-empty strings");
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const candidate = safeQuickCandidate(selectedStore, parsed.receiptId);
|
|
64
|
+
if (candidate !== null) candidates.push(candidate);
|
|
65
|
+
} catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const base = {
|
|
70
|
+
schemaVersion: "OwlCodaAttestationResolutionV1",
|
|
71
|
+
reference: parsed,
|
|
72
|
+
authorizationGranted: false,
|
|
73
|
+
networkRequests: 0,
|
|
74
|
+
};
|
|
75
|
+
if (candidates.length === 0) {
|
|
76
|
+
return {
|
|
77
|
+
...base,
|
|
78
|
+
status: "not_found",
|
|
79
|
+
exitCode: 3,
|
|
80
|
+
issueCodes: ["attestation_target_not_found"],
|
|
81
|
+
candidates: [],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (candidates.length > 1) {
|
|
85
|
+
return {
|
|
86
|
+
...base,
|
|
87
|
+
status: "ambiguous",
|
|
88
|
+
exitCode: 1,
|
|
89
|
+
issueCodes: ["attestation_target_ambiguous"],
|
|
90
|
+
candidates,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const candidate = candidates[0];
|
|
95
|
+
const { bytes, receipt } = readQuickReceiptFile(candidate);
|
|
96
|
+
const actualHash = sha256Bytes(bytes);
|
|
97
|
+
const identityValid = receipt.receiptId === parsed.receiptId
|
|
98
|
+
&& receipt.coreIdentity.contractVersion === parsed.coreIdentity.contractVersion
|
|
99
|
+
&& receipt.coreIdentity.coreManifestSha256 === parsed.coreIdentity.coreManifestSha256;
|
|
100
|
+
if (actualHash !== parsed.receiptSha256 || !identityValid) {
|
|
101
|
+
return {
|
|
102
|
+
...base,
|
|
103
|
+
status: "resolved_invalid",
|
|
104
|
+
exitCode: 1,
|
|
105
|
+
issueCodes: [
|
|
106
|
+
...(actualHash === parsed.receiptSha256 ? [] : ["receipt_material_hash_mismatch"]),
|
|
107
|
+
...(identityValid ? [] : ["core_identity_mismatch"]),
|
|
108
|
+
],
|
|
109
|
+
candidates,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const details = attestQuickReceiptDetailsFromBytes({
|
|
113
|
+
receiptPath: candidate,
|
|
114
|
+
receiptBytes: bytes,
|
|
115
|
+
workspaceRoot,
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
...base,
|
|
119
|
+
status: details.attestation.decision === "GO"
|
|
120
|
+
? "resolved_valid"
|
|
121
|
+
: details.attestation.decision === "INDETERMINATE"
|
|
122
|
+
? "resolved_indeterminate"
|
|
123
|
+
: "resolved_invalid",
|
|
124
|
+
exitCode: details.attestation.decision === "GO" ? 0 : details.attestation.decision === "INDETERMINATE" ? 3 : 1,
|
|
125
|
+
issueCodes: details.attestation.issueCodes,
|
|
126
|
+
candidates,
|
|
127
|
+
receiptPath: candidate,
|
|
128
|
+
receiptSha256: actualHash,
|
|
129
|
+
attestation: details.attestation,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { lstatSync, readFileSync, readlinkSync, realpathSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { sha256Bytes, sha256Canonical } from "./formal.mjs";
|
|
6
|
+
|
|
7
|
+
const CONTROL_ROOT = ".owlcoda/runkit";
|
|
8
|
+
const LOCKFILE_NAMES = new Set([
|
|
9
|
+
"bun.lock",
|
|
10
|
+
"bun.lockb",
|
|
11
|
+
"Cargo.lock",
|
|
12
|
+
"composer.lock",
|
|
13
|
+
"Gemfile.lock",
|
|
14
|
+
"go.sum",
|
|
15
|
+
"npm-shrinkwrap.json",
|
|
16
|
+
"package-lock.json",
|
|
17
|
+
"pnpm-lock.yaml",
|
|
18
|
+
"poetry.lock",
|
|
19
|
+
"uv.lock",
|
|
20
|
+
"yarn.lock",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function git(root, args, options = {}) {
|
|
24
|
+
return execFileSync("git", args, {
|
|
25
|
+
cwd: root,
|
|
26
|
+
encoding: options.encoding ?? "utf8",
|
|
27
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
28
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
29
|
+
env: {
|
|
30
|
+
...process.env,
|
|
31
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function optionalGit(root, args) {
|
|
37
|
+
try {
|
|
38
|
+
return git(root, args).trim();
|
|
39
|
+
} catch {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizePath(value) {
|
|
45
|
+
return value.split(path.sep).join("/");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isControlPath(value) {
|
|
49
|
+
return value === CONTROL_ROOT || value.startsWith(`${CONTROL_ROOT}/`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function fileHash(root, relativePath) {
|
|
53
|
+
const absolutePath = path.resolve(root, relativePath);
|
|
54
|
+
const stat = lstatSync(absolutePath);
|
|
55
|
+
if (stat.isSymbolicLink()) {
|
|
56
|
+
return sha256Bytes(Buffer.from(`symlink:${readlinkSync(absolutePath)}`, "utf8"));
|
|
57
|
+
}
|
|
58
|
+
if (stat.isDirectory()) {
|
|
59
|
+
const commit = optionalGit(absolutePath, ["rev-parse", "HEAD"]);
|
|
60
|
+
return sha256Canonical({
|
|
61
|
+
algorithm: "gitlink-worktree-v1",
|
|
62
|
+
commit,
|
|
63
|
+
dirtyOverlay: parseStatus(absolutePath),
|
|
64
|
+
submodules: submodules(absolutePath),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return sha256Bytes(readFileSync(absolutePath));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseStatus(root) {
|
|
71
|
+
const raw = git(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
72
|
+
const fields = raw.split("\0");
|
|
73
|
+
const entries = new Map();
|
|
74
|
+
for (let index = 0; index < fields.length; index += 1) {
|
|
75
|
+
const field = fields[index];
|
|
76
|
+
if (!field) continue;
|
|
77
|
+
const status = field.slice(0, 2);
|
|
78
|
+
const currentPath = normalizePath(field.slice(3));
|
|
79
|
+
if (status.includes("R") || status.includes("C")) index += 1;
|
|
80
|
+
if (!currentPath || isControlPath(currentPath)) continue;
|
|
81
|
+
let state = "modified";
|
|
82
|
+
if (status === "??") state = "untracked";
|
|
83
|
+
if (status.includes("D")) state = "deleted";
|
|
84
|
+
let sha256 = null;
|
|
85
|
+
if (state !== "deleted") {
|
|
86
|
+
try {
|
|
87
|
+
sha256 = fileHash(root, currentPath);
|
|
88
|
+
} catch {
|
|
89
|
+
state = "deleted";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
entries.set(currentPath, { path: currentPath, state, sha256 });
|
|
93
|
+
}
|
|
94
|
+
return [...entries.values()].sort((left, right) => left.path.localeCompare(right.path, "en"));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function submodules(root) {
|
|
98
|
+
const output = optionalGit(root, ["submodule", "status", "--recursive"]);
|
|
99
|
+
if (!output) return [];
|
|
100
|
+
return output
|
|
101
|
+
.split("\n")
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.map((line) => {
|
|
104
|
+
const match = /^[ +-U]?([0-9a-f]{40,64})\s+([^\s]+)/.exec(line);
|
|
105
|
+
if (!match) throw new Error(`Cannot parse git submodule status: ${line}`);
|
|
106
|
+
return { path: normalizePath(match[2]), commit: match[1] };
|
|
107
|
+
})
|
|
108
|
+
.sort((left, right) => left.path.localeCompare(right.path, "en"));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function dependencyLockfiles(root) {
|
|
112
|
+
const raw = git(root, ["ls-files", "-co", "--exclude-standard", "-z"]);
|
|
113
|
+
return raw
|
|
114
|
+
.split("\0")
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.map(normalizePath)
|
|
117
|
+
.filter((entry) => !isControlPath(entry) && LOCKFILE_NAMES.has(path.posix.basename(entry)))
|
|
118
|
+
.sort((left, right) => left.localeCompare(right, "en"))
|
|
119
|
+
.map((entry) => ({ path: entry, sha256: fileHash(root, entry) }));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sanitizedRemoteIdentity(remote) {
|
|
123
|
+
try {
|
|
124
|
+
const parsed = new URL(remote);
|
|
125
|
+
parsed.username = "";
|
|
126
|
+
parsed.password = "";
|
|
127
|
+
parsed.search = "";
|
|
128
|
+
parsed.hash = "";
|
|
129
|
+
return parsed.toString();
|
|
130
|
+
} catch {
|
|
131
|
+
return remote.replace(/^[^/@\s]+@(?=[^/\s]+:)/, "");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function repositoryIdentity(root) {
|
|
136
|
+
const remote = optionalGit(root, ["remote", "get-url", "origin"]);
|
|
137
|
+
if (remote) return sanitizedRemoteIdentity(remote);
|
|
138
|
+
return `local:${realpathSync(root)}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function trackedTreeIdentity(root, headCommit) {
|
|
142
|
+
const tree = headCommit ? git(root, ["rev-parse", "HEAD^{tree}"]).trim() : "";
|
|
143
|
+
const objectFormat = optionalGit(root, ["rev-parse", "--show-object-format"]) || "sha1";
|
|
144
|
+
return sha256Canonical({
|
|
145
|
+
algorithm: "git-tree-object-v1",
|
|
146
|
+
objectFormat,
|
|
147
|
+
tree,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function captureWorkspaceSnapshot(workspaceRoot) {
|
|
152
|
+
const root = realpathSync(workspaceRoot);
|
|
153
|
+
git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
154
|
+
const headCommit = optionalGit(root, ["rev-parse", "HEAD"]) || null;
|
|
155
|
+
const payload = {
|
|
156
|
+
schemaVersion: "OwlCodaWorkspaceSnapshotV1",
|
|
157
|
+
repositoryIdentity: repositoryIdentity(root),
|
|
158
|
+
headCommit,
|
|
159
|
+
trackedTreeIdentity: trackedTreeIdentity(root, headCommit),
|
|
160
|
+
submodules: submodules(root),
|
|
161
|
+
dirtyOverlay: parseStatus(root),
|
|
162
|
+
dependencyLockfiles: dependencyLockfiles(root),
|
|
163
|
+
excludedRoots: [CONTROL_ROOT],
|
|
164
|
+
ignoredPathsBound: false,
|
|
165
|
+
policyVersion: "workspace-snapshot-v1",
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
...payload,
|
|
169
|
+
sourceFingerprint: sha256Canonical(payload),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "OwlCodaRunKitRegistryReleasePolicyV1",
|
|
3
|
+
"status": "release_candidate_unpublished",
|
|
4
|
+
"packageName": "owlrunkit",
|
|
5
|
+
"candidateVersion": "0.13.0",
|
|
6
|
+
"registry": "https://registry.npmjs.org",
|
|
7
|
+
"registryRequiredBeforeAdoption": true,
|
|
8
|
+
"exactVersionRequired": true,
|
|
9
|
+
"requiredRegistryBindings": [
|
|
10
|
+
"packageName",
|
|
11
|
+
"version",
|
|
12
|
+
"shasum",
|
|
13
|
+
"integrity",
|
|
14
|
+
"tarballUrl"
|
|
15
|
+
],
|
|
16
|
+
"forbiddenAdoptionSources": [
|
|
17
|
+
"directory",
|
|
18
|
+
"file",
|
|
19
|
+
"git",
|
|
20
|
+
"local_tarball",
|
|
21
|
+
"symlink",
|
|
22
|
+
"workspace"
|
|
23
|
+
],
|
|
24
|
+
"currentPublishedRelease": null,
|
|
25
|
+
"registryRelease": null,
|
|
26
|
+
"registryEvidenceMustBeExternal": true,
|
|
27
|
+
"registryEvidenceSchemaVersion": "OwlCodaRunKitRegistryReleaseEvidenceV1",
|
|
28
|
+
"embeddedAttestComponentVersion": "0.2.0",
|
|
29
|
+
"standaloneAttestPublicationRequired": false,
|
|
30
|
+
"signatureRequired": false,
|
|
31
|
+
"githubActionRequired": false,
|
|
32
|
+
"foreignProjectWriteAuthorized": false,
|
|
33
|
+
"authorizationGranted": false
|
|
34
|
+
}
|
package/sbom.spdx.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"spdxVersion": "SPDX-2.3",
|
|
3
|
+
"dataLicense": "CC0-1.0",
|
|
4
|
+
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
|
+
"name": "owlrunkit-0.13.0",
|
|
6
|
+
"documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.13.0",
|
|
7
|
+
"creationInfo": {
|
|
8
|
+
"created": "2026-07-29T00:00:00Z",
|
|
9
|
+
"creators": [
|
|
10
|
+
"Organization: OwlCoda"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"packages": [
|
|
14
|
+
{
|
|
15
|
+
"name": "owlrunkit",
|
|
16
|
+
"SPDXID": "SPDXRef-Package-OwlRunKit",
|
|
17
|
+
"versionInfo": "0.13.0",
|
|
18
|
+
"downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.13.0.tgz",
|
|
19
|
+
"filesAnalyzed": false,
|
|
20
|
+
"licenseConcluded": "GPL-3.0-or-later",
|
|
21
|
+
"licenseDeclared": "GPL-3.0-or-later",
|
|
22
|
+
"copyrightText": "NOASSERTION",
|
|
23
|
+
"externalRefs": [
|
|
24
|
+
{
|
|
25
|
+
"referenceCategory": "PACKAGE-MANAGER",
|
|
26
|
+
"referenceType": "purl",
|
|
27
|
+
"referenceLocator": "pkg:npm/owlrunkit@0.13.0"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"relationships": [
|
|
33
|
+
{
|
|
34
|
+
"spdxElementId": "SPDXRef-DOCUMENT",
|
|
35
|
+
"relationshipType": "DESCRIBES",
|
|
36
|
+
"relatedSpdxElement": "SPDXRef-Package-OwlRunKit"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
2
|
+
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function validateAllowedKeys(value, label, allowedKeys) {
|
|
8
|
+
if (!isRecord(value)) return [];
|
|
9
|
+
const allowed = new Set(allowedKeys);
|
|
10
|
+
return Object.keys(value)
|
|
11
|
+
.filter((key) => !allowed.has(key))
|
|
12
|
+
.map((key) => `${label} contains unsupported field: ${key}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function validateVersionEntries(values, label) {
|
|
16
|
+
if (!Array.isArray(values)) return [`${label} must be an array.`];
|
|
17
|
+
const issues = [];
|
|
18
|
+
for (const [index, value] of values.entries()) {
|
|
19
|
+
if (!isRecord(value)
|
|
20
|
+
|| typeof value.name !== "string"
|
|
21
|
+
|| value.name.length === 0
|
|
22
|
+
|| typeof value.version !== "string"
|
|
23
|
+
|| value.version.length === 0) {
|
|
24
|
+
issues.push(`${label}[${index}] requires name and version.`);
|
|
25
|
+
} else {
|
|
26
|
+
issues.push(...validateAllowedKeys(value, `${label}[${index}]`, ["name", "version"]));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return issues;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function validateHashEntries(values, label, identityField) {
|
|
33
|
+
if (!Array.isArray(values)) return [`${label} must be an array.`];
|
|
34
|
+
const issues = [];
|
|
35
|
+
for (const [index, value] of values.entries()) {
|
|
36
|
+
if (!isRecord(value)
|
|
37
|
+
|| typeof value[identityField] !== "string"
|
|
38
|
+
|| value[identityField].length === 0
|
|
39
|
+
|| typeof value.sha256 !== "string"
|
|
40
|
+
|| !SHA256_PATTERN.test(value.sha256)) {
|
|
41
|
+
issues.push(`${label}[${index}] requires ${identityField} and lowercase sha256.`);
|
|
42
|
+
} else {
|
|
43
|
+
issues.push(...validateAllowedKeys(value, `${label}[${index}]`, [identityField, "sha256"]));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return issues;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function validateReplayableEvidence(evidence) {
|
|
50
|
+
if (!isRecord(evidence)) {
|
|
51
|
+
return { valid: false, issues: ["Replayable evidence must be an object."] };
|
|
52
|
+
}
|
|
53
|
+
const issues = validateAllowedKeys(evidence, "Replayable evidence", [
|
|
54
|
+
"schemaVersion",
|
|
55
|
+
"kind",
|
|
56
|
+
"cwd",
|
|
57
|
+
"launcher",
|
|
58
|
+
"argv",
|
|
59
|
+
"automationManifestSha256",
|
|
60
|
+
"toolVersions",
|
|
61
|
+
"materialInputs",
|
|
62
|
+
"outputArtifacts",
|
|
63
|
+
]);
|
|
64
|
+
if (evidence.schemaVersion !== "OwlCodaRunKitReplayableEvidenceV1") {
|
|
65
|
+
issues.push("Unsupported replayable evidence schemaVersion.");
|
|
66
|
+
}
|
|
67
|
+
if (!new Set(["shell", "automation"]).has(evidence.kind)) {
|
|
68
|
+
issues.push("Replayable evidence kind must be shell or automation.");
|
|
69
|
+
}
|
|
70
|
+
if (typeof evidence.cwd !== "string" || evidence.cwd.length === 0) {
|
|
71
|
+
issues.push("Replayable evidence requires cwd.");
|
|
72
|
+
}
|
|
73
|
+
if (!isRecord(evidence.launcher)
|
|
74
|
+
|| typeof evidence.launcher.executable !== "string"
|
|
75
|
+
|| evidence.launcher.executable.length === 0
|
|
76
|
+
|| typeof evidence.launcher.version !== "string"
|
|
77
|
+
|| evidence.launcher.version.length === 0) {
|
|
78
|
+
issues.push("Replayable evidence requires launcher executable and version.");
|
|
79
|
+
} else {
|
|
80
|
+
issues.push(...validateAllowedKeys(evidence.launcher, "launcher", ["executable", "version"]));
|
|
81
|
+
}
|
|
82
|
+
issues.push(...validateVersionEntries(evidence.toolVersions, "toolVersions"));
|
|
83
|
+
issues.push(...validateHashEntries(evidence.materialInputs, "materialInputs", "id"));
|
|
84
|
+
issues.push(...validateHashEntries(evidence.outputArtifacts, "outputArtifacts", "path"));
|
|
85
|
+
if (evidence.kind === "shell") {
|
|
86
|
+
if (!Array.isArray(evidence.argv)
|
|
87
|
+
|| evidence.argv.length === 0
|
|
88
|
+
|| typeof evidence.argv[0] !== "string"
|
|
89
|
+
|| evidence.argv[0].length === 0
|
|
90
|
+
|| evidence.argv.some((value) => typeof value !== "string")) {
|
|
91
|
+
issues.push("Shell evidence requires exact argv with a non-empty executable.");
|
|
92
|
+
}
|
|
93
|
+
} else if (evidence.kind === "automation") {
|
|
94
|
+
if (typeof evidence.automationManifestSha256 !== "string"
|
|
95
|
+
|| !SHA256_PATTERN.test(evidence.automationManifestSha256)) {
|
|
96
|
+
issues.push("Automation evidence requires automationManifestSha256.");
|
|
97
|
+
}
|
|
98
|
+
if (!Array.isArray(evidence.outputArtifacts) || evidence.outputArtifacts.length === 0) {
|
|
99
|
+
issues.push("Automation evidence requires at least one output artifact.");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { valid: issues.length === 0, issues };
|
|
103
|
+
}
|