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,183 @@
|
|
|
1
|
+
function latestIndexedCloseout(executions) {
|
|
2
|
+
return [...executions].reverse().find(execution => execution.lifecycle === "closed") ?? null;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function compareCodeUnits(left, right) {
|
|
6
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function escapeHumanControls(input) {
|
|
10
|
+
return String(input).replace(/[\u0000-\u001f\u007f-\u009f]/g, character => (
|
|
11
|
+
`\\u${character.codePointAt(0).toString(16).padStart(4, "0")}`
|
|
12
|
+
));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function selectedFocus(inspected) {
|
|
16
|
+
const selectedRunId = inspected.recovery.selectedRunId;
|
|
17
|
+
if (selectedRunId) {
|
|
18
|
+
return inspected.executions.find(execution => execution.runId === selectedRunId) ?? null;
|
|
19
|
+
}
|
|
20
|
+
return inspected.recovery.state === "no_active_execution"
|
|
21
|
+
? latestIndexedCloseout(inspected.executions)
|
|
22
|
+
: null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildInspectSummary(inspected) {
|
|
26
|
+
const focus = selectedFocus(inspected);
|
|
27
|
+
const latestClosed = latestIndexedCloseout(inspected.executions);
|
|
28
|
+
const holders = inspected.executions
|
|
29
|
+
.flatMap(execution => (execution.recovery?.lease?.activeWorkItemIds ?? []).map(workItemId => ({
|
|
30
|
+
runId: execution.runId,
|
|
31
|
+
workItemId,
|
|
32
|
+
})))
|
|
33
|
+
.sort((left, right) => compareCodeUnits(
|
|
34
|
+
`${left.runId}/${left.workItemId}`,
|
|
35
|
+
`${right.runId}/${right.workItemId}`,
|
|
36
|
+
));
|
|
37
|
+
const delivery = focus?.recovery?.delivery ?? null;
|
|
38
|
+
const verification = focus?.recovery?.verification ?? null;
|
|
39
|
+
const resource = focus?.recovery?.resourcePreflight ?? { status: "none", selected: null };
|
|
40
|
+
const selectedResource = resource.selected ?? null;
|
|
41
|
+
const reasons = [
|
|
42
|
+
...(inspected.recovery.state === "multiple_active_executions"
|
|
43
|
+
? ["Multiple active executions require explicit selection."]
|
|
44
|
+
: (focus?.recovery?.issues ?? [])),
|
|
45
|
+
...(inspected.controlIssues ?? []),
|
|
46
|
+
...(selectedResource?.blockers ?? []),
|
|
47
|
+
].filter((reason, index, all) => all.indexOf(reason) === index);
|
|
48
|
+
return {
|
|
49
|
+
schemaVersion: "OwlCodaRunKitInspectSummaryV1",
|
|
50
|
+
currentExecution: {
|
|
51
|
+
state: inspected.recovery.state,
|
|
52
|
+
selectedRunId: inspected.recovery.selectedRunId,
|
|
53
|
+
activeRunIds: [...inspected.recovery.activeRunIds],
|
|
54
|
+
openCount: inspected.recovery.activeRunIds.length,
|
|
55
|
+
},
|
|
56
|
+
latestIndexedCloseout: latestClosed
|
|
57
|
+
? {
|
|
58
|
+
runId: latestClosed.runId,
|
|
59
|
+
decision: latestClosed.closeout?.decision ?? "invalid",
|
|
60
|
+
trustLevel: latestClosed.recovery?.evidenceTrustLevel ?? "invalid",
|
|
61
|
+
}
|
|
62
|
+
: null,
|
|
63
|
+
source: {
|
|
64
|
+
status: delivery?.status ?? "none",
|
|
65
|
+
sourceFingerprint: delivery?.sourceFingerprint ?? verification?.sourceFingerprint ?? null,
|
|
66
|
+
},
|
|
67
|
+
leases: {
|
|
68
|
+
activeCount: holders.length,
|
|
69
|
+
holders,
|
|
70
|
+
},
|
|
71
|
+
evidence: {
|
|
72
|
+
status: verification?.status ?? "none",
|
|
73
|
+
decision: verification?.decision ?? null,
|
|
74
|
+
activeReceiptSha256: verification?.activeReceiptSha256 ?? null,
|
|
75
|
+
trustLevel: focus?.recovery?.evidenceTrustLevel ?? "none",
|
|
76
|
+
},
|
|
77
|
+
resourcePreflight: {
|
|
78
|
+
status: resource.status,
|
|
79
|
+
preflightId: selectedResource?.preflightId ?? null,
|
|
80
|
+
sequence: selectedResource?.sequence ?? null,
|
|
81
|
+
evaluatedAt: selectedResource?.evaluatedAt ?? null,
|
|
82
|
+
validUntil: selectedResource?.validUntil ?? null,
|
|
83
|
+
decision: selectedResource?.status ?? null,
|
|
84
|
+
nextAllowedAction: selectedResource?.nextAllowedAction ?? null,
|
|
85
|
+
blockers: [...(selectedResource?.blockers ?? [])],
|
|
86
|
+
warnings: [...(selectedResource?.warnings ?? [])],
|
|
87
|
+
receiptReuse: selectedResource?.receiptReuse ?? { reusableCount: 0, appliedCount: 0 },
|
|
88
|
+
estimate: selectedResource?.estimate ?? {
|
|
89
|
+
calls: 0,
|
|
90
|
+
inputTokens: 0,
|
|
91
|
+
outputTokens: 0,
|
|
92
|
+
totalTokens: 0,
|
|
93
|
+
elapsedMs: 0,
|
|
94
|
+
cost: { status: "unknown", knownSubtotalUsd: 0, unknownResources: [] },
|
|
95
|
+
},
|
|
96
|
+
resources: [...(selectedResource?.resources ?? [])],
|
|
97
|
+
},
|
|
98
|
+
dominantGap: {
|
|
99
|
+
code: inspected.recovery.nextAllowedAction,
|
|
100
|
+
reasons: [...reasons],
|
|
101
|
+
},
|
|
102
|
+
nextAllowedAction: inspected.recovery.nextAllowedAction,
|
|
103
|
+
authorizationGranted: false,
|
|
104
|
+
gitAuthorization: false,
|
|
105
|
+
releaseAuthorization: false,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function value(value) {
|
|
110
|
+
if (value === null || value === undefined || value === "") return "none";
|
|
111
|
+
return String(value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function summaryLines(summary) {
|
|
115
|
+
const latest = summary.latestIndexedCloseout
|
|
116
|
+
? `${summary.latestIndexedCloseout.runId} ${summary.latestIndexedCloseout.decision}`
|
|
117
|
+
: "none";
|
|
118
|
+
const visibleHolders = summary.leases.holders.slice(0, 5)
|
|
119
|
+
.map(holder => `${holder.runId}/${holder.workItemId}`);
|
|
120
|
+
const holders = visibleHolders.length === 0
|
|
121
|
+
? "none"
|
|
122
|
+
: `${visibleHolders.join(", ")}${summary.leases.holders.length > visibleHolders.length
|
|
123
|
+
? ` (+${summary.leases.holders.length - visibleHolders.length} more)`
|
|
124
|
+
: ""}`;
|
|
125
|
+
return [
|
|
126
|
+
`Current execution: ${value(summary.currentExecution.selectedRunId)}`,
|
|
127
|
+
`Latest indexed closeout: ${latest}`,
|
|
128
|
+
`Source status: ${summary.source.status}`,
|
|
129
|
+
`Active leases: ${summary.leases.activeCount}`,
|
|
130
|
+
`Lease holders: ${holders}`,
|
|
131
|
+
`Open executions: ${summary.currentExecution.openCount}`,
|
|
132
|
+
`Evidence: ${summary.evidence.status}`,
|
|
133
|
+
`Resource preflight: ${summary.resourcePreflight.status}`,
|
|
134
|
+
`Model estimate: ${summary.resourcePreflight.estimate.calls} calls, ${summary.resourcePreflight.estimate.totalTokens} tokens`,
|
|
135
|
+
`Model cost: ${summary.resourcePreflight.estimate.cost.status === "known"
|
|
136
|
+
? `$${summary.resourcePreflight.estimate.cost.valueUsd}`
|
|
137
|
+
: "unknown"}`,
|
|
138
|
+
`Receipt reuse: ${summary.resourcePreflight.receiptReuse.appliedCount}/${summary.resourcePreflight.receiptReuse.reusableCount}`,
|
|
139
|
+
`Dominant gap: ${summary.dominantGap.code}`,
|
|
140
|
+
`Next allowed action: ${summary.nextAllowedAction}`,
|
|
141
|
+
`Release authorization: ${summary.releaseAuthorization}`,
|
|
142
|
+
].map(escapeHumanControls);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function formatHistory(inspected) {
|
|
146
|
+
const closed = inspected.executions.filter(execution => execution.lifecycle === "closed");
|
|
147
|
+
return [
|
|
148
|
+
"Indexed closeout history",
|
|
149
|
+
...(closed.length === 0
|
|
150
|
+
? ["none"]
|
|
151
|
+
: closed.map(execution => [
|
|
152
|
+
execution.runId,
|
|
153
|
+
execution.closeout?.decision ?? "invalid",
|
|
154
|
+
execution.recovery?.evidenceTrustLevel ?? "invalid",
|
|
155
|
+
].join(" "))),
|
|
156
|
+
"Release authorization: false",
|
|
157
|
+
].map(escapeHumanControls).join("\n");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function formatExecution(execution) {
|
|
161
|
+
const recovery = execution.recovery ?? {};
|
|
162
|
+
const holders = recovery.lease?.activeWorkItemIds ?? [];
|
|
163
|
+
const issues = recovery.issues ?? [];
|
|
164
|
+
return [
|
|
165
|
+
`Execution: ${execution.runId}`,
|
|
166
|
+
`Lifecycle: ${execution.lifecycle}`,
|
|
167
|
+
`Engine pin: ${execution.enginePin?.status ?? "invalid"}`,
|
|
168
|
+
`Lease state: ${recovery.lease?.status ?? "none"}`,
|
|
169
|
+
`Lease holders: ${holders.length > 0 ? holders.join(", ") : "none"}`,
|
|
170
|
+
`Source status: ${recovery.delivery?.status ?? "none"}`,
|
|
171
|
+
`Evidence: ${recovery.verification?.status ?? "none"}`,
|
|
172
|
+
`Trust level: ${recovery.evidenceTrustLevel ?? "invalid"}`,
|
|
173
|
+
`Next allowed action: ${recovery.nextAllowedAction ?? "repair_execution_artifacts"}`,
|
|
174
|
+
`Issues: ${issues.length > 0 ? issues.join(" | ") : "none"}`,
|
|
175
|
+
"Release authorization: false",
|
|
176
|
+
].map(escapeHumanControls).join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function formatInspectHuman(inspected) {
|
|
180
|
+
if (inspected.view?.mode === "history") return `${formatHistory(inspected)}\n`;
|
|
181
|
+
if (inspected.view?.mode === "execution") return `${formatExecution(inspected.view.execution)}\n`;
|
|
182
|
+
return `${summaryLines(inspected.summary).join("\n")}\n`;
|
|
183
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import {
|
|
5
|
+
cp,
|
|
6
|
+
lstat,
|
|
7
|
+
mkdir,
|
|
8
|
+
mkdtemp,
|
|
9
|
+
readFile,
|
|
10
|
+
readdir,
|
|
11
|
+
rename,
|
|
12
|
+
rm,
|
|
13
|
+
writeFile,
|
|
14
|
+
} from "node:fs/promises";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
|
|
17
|
+
import { isDirectExecution } from "./core-contract.mjs";
|
|
18
|
+
|
|
19
|
+
const MANIFEST_FILE = ".owlcoda-install-manifest.json";
|
|
20
|
+
|
|
21
|
+
function sha256(value) {
|
|
22
|
+
return createHash("sha256").update(value).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function exists(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
await lstat(filePath);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error?.code === "ENOENT") return false;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function fileMap(root, { excludeManifest = true } = {}) {
|
|
36
|
+
const files = {};
|
|
37
|
+
async function walk(directory, prefix = "") {
|
|
38
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
39
|
+
entries.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
42
|
+
if (excludeManifest && relativePath === MANIFEST_FILE) continue;
|
|
43
|
+
const absolutePath = path.join(directory, entry.name);
|
|
44
|
+
if (entry.isDirectory()) await walk(absolutePath, relativePath);
|
|
45
|
+
else if (entry.isFile()) files[relativePath] = sha256(await readFile(absolutePath));
|
|
46
|
+
else throw new Error(`unsupported_skill_entry:${relativePath}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
await walk(root);
|
|
50
|
+
return files;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function contentSha256(files) {
|
|
54
|
+
const stream = Object.entries(files).map(([name, hash]) => `${name}\tsha256:${hash}\n`).join("");
|
|
55
|
+
return sha256(stream);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function buildManifest(root, repositoryRoot) {
|
|
59
|
+
const wholeFileSha256 = await fileMap(root);
|
|
60
|
+
const manifestSha256 = contentSha256(wholeFileSha256);
|
|
61
|
+
return {
|
|
62
|
+
schemaVersion: "OwlCodaRunKitSkillInstallManifestV1",
|
|
63
|
+
skillName: "owlcoda-runkit",
|
|
64
|
+
manifestSha256: `sha256:${manifestSha256}`,
|
|
65
|
+
sourceRef: `artifact:sha256:${manifestSha256}`,
|
|
66
|
+
sourceRepository: path.resolve(repositoryRoot),
|
|
67
|
+
fileCount: Object.keys(wholeFileSha256).length,
|
|
68
|
+
wholeFileSha256,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function replaceManifestAtomically(targetRoot, manifest) {
|
|
73
|
+
const temporaryRoot = await mkdtemp(path.join(path.dirname(targetRoot), ".owlcoda-runkit.manifest-"));
|
|
74
|
+
try {
|
|
75
|
+
const temporaryManifest = path.join(temporaryRoot, MANIFEST_FILE);
|
|
76
|
+
await writeFile(temporaryManifest, `${JSON.stringify(manifest, null, 2)}\n`, { flag: "wx" });
|
|
77
|
+
await rename(temporaryManifest, path.join(targetRoot, MANIFEST_FILE));
|
|
78
|
+
} finally {
|
|
79
|
+
await rm(temporaryRoot, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function assemble({ repositoryRoot, stagingRoot }) {
|
|
84
|
+
const skillSource = path.join(repositoryRoot, "integrations/codex/skills/owlcoda-runkit");
|
|
85
|
+
const coreSource = path.join(repositoryRoot, "scripts/runkit-contract");
|
|
86
|
+
const attestSource = path.join(repositoryRoot, "packages/attest");
|
|
87
|
+
const contractV01Source = path.join(repositoryRoot, "docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_1.md");
|
|
88
|
+
const contractV02Source = path.join(repositoryRoot, "docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md");
|
|
89
|
+
for (const required of [path.join(skillSource, "SKILL.md"), path.join(skillSource, "agents/openai.yaml"), coreSource, attestSource, contractV01Source, contractV02Source]) {
|
|
90
|
+
if (!await exists(required)) throw new Error(`missing_authoritative_skill_source:${required}`);
|
|
91
|
+
}
|
|
92
|
+
await cp(skillSource, stagingRoot, { recursive: true });
|
|
93
|
+
await mkdir(path.join(stagingRoot, "scripts"), { recursive: true });
|
|
94
|
+
await cp(coreSource, path.join(stagingRoot, "scripts/runkit-contract"), { recursive: true });
|
|
95
|
+
await mkdir(path.join(stagingRoot, "packages"), { recursive: true });
|
|
96
|
+
await cp(attestSource, path.join(stagingRoot, "packages/attest"), { recursive: true });
|
|
97
|
+
await mkdir(path.join(stagingRoot, "references"), { recursive: true });
|
|
98
|
+
await cp(contractV01Source, path.join(stagingRoot, "references/contract-v0.1.md"));
|
|
99
|
+
await cp(contractV02Source, path.join(stagingRoot, "references/contract-v0.2.md"));
|
|
100
|
+
const manifest = await buildManifest(stagingRoot, repositoryRoot);
|
|
101
|
+
await writeFile(path.join(stagingRoot, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`, { flag: "wx" });
|
|
102
|
+
return manifest;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function inspectInstalledSkill({ targetRoot } = {}) {
|
|
106
|
+
if (typeof targetRoot !== "string" || targetRoot.length === 0) throw new Error("targetRoot is required");
|
|
107
|
+
if (!await exists(targetRoot)) return { status: "missing", valid: false };
|
|
108
|
+
const manifestPath = path.join(targetRoot, MANIFEST_FILE);
|
|
109
|
+
if (!await exists(manifestPath)) {
|
|
110
|
+
const wholeFileSha256 = await fileMap(targetRoot);
|
|
111
|
+
return {
|
|
112
|
+
status: "unmanaged",
|
|
113
|
+
valid: false,
|
|
114
|
+
manifestSha256: `sha256:${contentSha256(wholeFileSha256)}`,
|
|
115
|
+
wholeFileSha256,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
let manifest;
|
|
119
|
+
try {
|
|
120
|
+
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
121
|
+
} catch {
|
|
122
|
+
return { status: "drifted", valid: false, issues: ["invalid install manifest"] };
|
|
123
|
+
}
|
|
124
|
+
const actualFiles = await fileMap(targetRoot);
|
|
125
|
+
const actualSha256 = `sha256:${contentSha256(actualFiles)}`;
|
|
126
|
+
const issues = [];
|
|
127
|
+
if (manifest.schemaVersion !== "OwlCodaRunKitSkillInstallManifestV1") issues.push("unsupported install manifest schema");
|
|
128
|
+
if (manifest.manifestSha256 !== actualSha256) issues.push("installed_skill_drift: content fingerprint mismatch");
|
|
129
|
+
if (JSON.stringify(manifest.wholeFileSha256) !== JSON.stringify(actualFiles)) issues.push("installed_skill_drift: file map mismatch");
|
|
130
|
+
return {
|
|
131
|
+
status: issues.length === 0 ? "valid" : "drifted",
|
|
132
|
+
valid: issues.length === 0,
|
|
133
|
+
manifestSha256: actualSha256,
|
|
134
|
+
wholeFileSha256: actualFiles,
|
|
135
|
+
manifest,
|
|
136
|
+
issues,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function installCodexSkill({
|
|
141
|
+
repositoryRoot,
|
|
142
|
+
targetRoot,
|
|
143
|
+
archiveRoot,
|
|
144
|
+
expectedUnmanagedManifestSha256 = null,
|
|
145
|
+
} = {}) {
|
|
146
|
+
if (typeof repositoryRoot !== "string" || typeof targetRoot !== "string" || typeof archiveRoot !== "string") {
|
|
147
|
+
throw new Error("repositoryRoot, targetRoot, and archiveRoot are required");
|
|
148
|
+
}
|
|
149
|
+
const parent = path.dirname(targetRoot);
|
|
150
|
+
await mkdir(parent, { recursive: true });
|
|
151
|
+
const stagingRoot = await mkdtemp(path.join(parent, ".owlcoda-runkit.install-"));
|
|
152
|
+
let stagingPresent = true;
|
|
153
|
+
try {
|
|
154
|
+
const desired = await assemble({ repositoryRoot: path.resolve(repositoryRoot), stagingRoot });
|
|
155
|
+
let prior = await inspectInstalledSkill({ targetRoot });
|
|
156
|
+
if (prior.status === "missing") {
|
|
157
|
+
await rename(stagingRoot, targetRoot);
|
|
158
|
+
stagingPresent = false;
|
|
159
|
+
return { status: "installed", manifestSha256: desired.manifestSha256, previousInstallation: null };
|
|
160
|
+
}
|
|
161
|
+
if (prior.status === "unmanaged") {
|
|
162
|
+
if (prior.manifestSha256 === desired.manifestSha256) {
|
|
163
|
+
await writeFile(path.join(targetRoot, MANIFEST_FILE), `${JSON.stringify(desired, null, 2)}\n`, { flag: "wx" });
|
|
164
|
+
return { status: "adopted", manifestSha256: desired.manifestSha256, previousInstallation: null };
|
|
165
|
+
}
|
|
166
|
+
if (prior.manifestSha256 !== expectedUnmanagedManifestSha256) {
|
|
167
|
+
throw new Error("installed_skill_drift: unmanaged installation differs from authoritative source and expected prior manifest");
|
|
168
|
+
}
|
|
169
|
+
prior = { ...prior, valid: true };
|
|
170
|
+
}
|
|
171
|
+
if (!prior.valid) throw new Error(`installed_skill_drift: ${prior.issues.join("; ")}`);
|
|
172
|
+
if (prior.manifestSha256 === desired.manifestSha256) {
|
|
173
|
+
if (prior.manifest.sourceRepository !== desired.sourceRepository) {
|
|
174
|
+
await replaceManifestAtomically(targetRoot, desired);
|
|
175
|
+
return { status: "provenance_updated", manifestSha256: desired.manifestSha256, previousInstallation: null };
|
|
176
|
+
}
|
|
177
|
+
return { status: "unchanged", manifestSha256: desired.manifestSha256, previousInstallation: null };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
await mkdir(archiveRoot, { recursive: true });
|
|
181
|
+
const archivePath = path.join(archiveRoot, `owlcoda-runkit-${prior.manifestSha256.slice(7)}`);
|
|
182
|
+
if (await exists(archivePath)) throw new Error(`archive_already_exists:${archivePath}`);
|
|
183
|
+
await rename(targetRoot, archivePath);
|
|
184
|
+
try {
|
|
185
|
+
await rename(stagingRoot, targetRoot);
|
|
186
|
+
stagingPresent = false;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
await rename(archivePath, targetRoot);
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
status: "updated",
|
|
193
|
+
manifestSha256: desired.manifestSha256,
|
|
194
|
+
previousInstallation: { manifestSha256: prior.manifestSha256, archivePath },
|
|
195
|
+
};
|
|
196
|
+
} finally {
|
|
197
|
+
if (stagingPresent) await rm(stagingRoot, { recursive: true, force: true });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function parseOptions(values) {
|
|
202
|
+
const options = {};
|
|
203
|
+
for (let index = 0; index < values.length; index += 2) {
|
|
204
|
+
const flag = values[index];
|
|
205
|
+
const value = values[index + 1];
|
|
206
|
+
if (!flag?.startsWith("--") || value === undefined) throw new Error("Options must be --name value pairs");
|
|
207
|
+
options[flag.slice(2)] = value;
|
|
208
|
+
}
|
|
209
|
+
return options;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
213
|
+
try {
|
|
214
|
+
const [command, ...rest] = argv;
|
|
215
|
+
const options = parseOptions(rest);
|
|
216
|
+
if (command === "install") {
|
|
217
|
+
const output = await installCodexSkill({
|
|
218
|
+
repositoryRoot: options.repository,
|
|
219
|
+
targetRoot: options.target,
|
|
220
|
+
archiveRoot: options.archive,
|
|
221
|
+
expectedUnmanagedManifestSha256: options["expected-unmanaged"] ?? null,
|
|
222
|
+
});
|
|
223
|
+
return { exitCode: 0, ...output };
|
|
224
|
+
}
|
|
225
|
+
if (command === "inspect") {
|
|
226
|
+
const output = await inspectInstalledSkill({ targetRoot: options.target });
|
|
227
|
+
return { exitCode: output.valid ? 0 : 2, ...output };
|
|
228
|
+
}
|
|
229
|
+
throw new Error("Usage: install-codex-skill.mjs <install|inspect> [options]");
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return { status: "invalid_input", exitCode: 3, issues: [error instanceof Error ? error.message : String(error)] };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (isDirectExecution(import.meta.url)) {
|
|
236
|
+
const result = await runCli();
|
|
237
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
238
|
+
process.exitCode = result.exitCode;
|
|
239
|
+
}
|