owlrunkit 0.15.0 → 0.16.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 (76) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +274 -66
  3. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +223 -117
  4. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  5. package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +2 -2
  6. package/package.json +2 -2
  7. package/packages/attest/distribution-manifest-v1.json +10 -0
  8. package/packages/attest/src/quick.mjs +10 -0
  9. package/registry-release-policy-v1.json +8 -2
  10. package/sbom.spdx.json +5 -5
  11. package/scripts/runkit-contract/assurance-router.mjs +119 -0
  12. package/scripts/runkit-contract/core-contract.mjs +250 -115
  13. package/scripts/runkit-contract/core-successor.mjs +2043 -0
  14. package/scripts/runkit-contract/deployment-workflow.mjs +2040 -0
  15. package/scripts/runkit-contract/finalize.mjs +242 -21
  16. package/scripts/runkit-contract/fleet-discovery.mjs +456 -0
  17. package/scripts/runkit-contract/formal-workflow.mjs +850 -0
  18. package/scripts/runkit-contract/human-status.mjs +553 -0
  19. package/scripts/runkit-contract/inspect-presentation.mjs +25 -8
  20. package/scripts/runkit-contract/install-codex-skill.mjs +73 -30
  21. package/scripts/runkit-contract/lease-lifecycle.mjs +8 -2
  22. package/scripts/runkit-contract/lifecycle-orchestration.mjs +135 -12
  23. package/scripts/runkit-contract/onboarding-doctor.mjs +43 -90
  24. package/scripts/runkit-contract/owner-authority-trust.mjs +230 -0
  25. package/scripts/runkit-contract/owner-deployment-decision.mjs +385 -0
  26. package/scripts/runkit-contract/profile-onboarding.mjs +841 -13
  27. package/scripts/runkit-contract/project-cli-resolver.mjs +701 -0
  28. package/scripts/runkit-contract/project-control-parser.mjs +830 -0
  29. package/scripts/runkit-contract/project-control-state.mjs +28 -0
  30. package/scripts/runkit-contract/provenance-common.mjs +19 -2
  31. package/scripts/runkit-contract/ready-for-commit.mjs +330 -18
  32. package/scripts/runkit-contract/remote-deployment.mjs +1596 -0
  33. package/scripts/runkit-contract/remote-process-adapter.mjs +177 -0
  34. package/scripts/runkit-contract/runkit-bootstrap.mjs +131 -0
  35. package/scripts/runkit-contract/runkit-cli.mjs +1097 -21
  36. package/scripts/runkit-contract/schemas/assurance-route-v1.schema.json +26 -0
  37. package/scripts/runkit-contract/schemas/core-successor-batch-receipt-v1.schema.json +126 -0
  38. package/scripts/runkit-contract/schemas/core-successor-plan-v1.schema.json +155 -0
  39. package/scripts/runkit-contract/schemas/core-successor-project-success-receipt-v1.schema.json +72 -0
  40. package/scripts/runkit-contract/schemas/deployment-execute-request-v1.schema.json +63 -0
  41. package/scripts/runkit-contract/schemas/deployment-lineage-v1.schema.json +292 -0
  42. package/scripts/runkit-contract/schemas/deployment-prepare-receipt-v1.schema.json +140 -0
  43. package/scripts/runkit-contract/schemas/fleet-discovery-v1.schema.json +31 -0
  44. package/scripts/runkit-contract/schemas/fleet-manifest-v1.schema.json +20 -0
  45. package/scripts/runkit-contract/schemas/fleet-root-registry-v1.schema.json +34 -0
  46. package/scripts/runkit-contract/schemas/human-status-v1.schema.json +102 -0
  47. package/scripts/runkit-contract/schemas/owner-authority-trust-v1.schema.json +66 -0
  48. package/scripts/runkit-contract/schemas/owner-deployment-authority-v1.schema.json +46 -0
  49. package/scripts/runkit-contract/schemas/owner-deployment-authority-v2.schema.json +94 -0
  50. package/scripts/runkit-contract/schemas/owner-deployment-decision-v1.schema.json +102 -0
  51. package/scripts/runkit-contract/schemas/owner-migration-authority-v1.schema.json +33 -0
  52. package/scripts/runkit-contract/schemas/owner-migration-authority-v2.schema.json +67 -0
  53. package/scripts/runkit-contract/schemas/profile-apply-receipt-v1.schema.json +32 -0
  54. package/scripts/runkit-contract/schemas/profile-detection-v2.schema.json +145 -0
  55. package/scripts/runkit-contract/schemas/ready-for-commit-v2.schema.json +37 -0
  56. package/scripts/runkit-contract/schemas/remote-deployment-manifest-v1.schema.json +379 -0
  57. package/scripts/runkit-contract/schemas/remote-deployment-result-v1.schema.json +153 -0
  58. package/scripts/runkit-contract/schemas/remote-deployment-stage-journal-v1.schema.json +183 -0
  59. package/scripts/runkit-contract/schemas/remote-target-v1.schema.json +32 -0
  60. package/scripts/runkit-contract/schemas/source-candidate-v2.schema.json +152 -0
  61. package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +13 -0
  62. package/scripts/runkit-contract/snapshot.mjs +1 -1
  63. package/scripts/runkit-contract/source-candidate.mjs +1382 -0
  64. package/scripts/runkit-contract/ssh-remote-adapter.mjs +1184 -0
  65. package/scripts/runkit-contract/sync-core016-release-identity.mjs +161 -0
  66. package/scripts/runkit-contract/templates/assurance-request.json +13 -0
  67. package/scripts/runkit-contract/templates/core-successor-owner-authority.json +15 -0
  68. package/scripts/runkit-contract/templates/deployment-execute-request.json +39 -0
  69. package/scripts/runkit-contract/templates/deployment-owner-authority.json +23 -0
  70. package/scripts/runkit-contract/templates/deployment-owner-decision.json +26 -0
  71. package/scripts/runkit-contract/templates/fleet-manifest.json +8 -0
  72. package/scripts/runkit-contract/templates/fleet-root-registry.json +8 -0
  73. package/scripts/runkit-contract/templates/owner-authority-trust.json +16 -0
  74. package/scripts/runkit-contract/templates/ready-for-commit-request.json +2 -2
  75. package/scripts/runkit-contract/templates/remote-deployment-manifest.json +108 -0
  76. package/scripts/runkit-contract/verification-receipt-gate.mjs +28 -0
@@ -0,0 +1,230 @@
1
+ import {
2
+ createHash,
3
+ createPublicKey,
4
+ verify,
5
+ } from "node:crypto";
6
+ import {
7
+ lstatSync,
8
+ readFileSync,
9
+ realpathSync,
10
+ } from "node:fs";
11
+ import { userInfo } from "node:os";
12
+ import path from "node:path";
13
+
14
+ const KEY_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
15
+ const SHA256_REF = /^sha256:[a-f0-9]{64}$/u;
16
+ const SIGNATURE = /^[A-Za-z0-9+/]+={0,2}$/u;
17
+ const MAX_TRUST_STORE_BYTES = 1_048_576;
18
+
19
+ function canonicalJson(value) {
20
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
21
+ if (value && typeof value === "object") {
22
+ return `{${Object.keys(value)
23
+ .sort()
24
+ .map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
25
+ .join(",")}}`;
26
+ }
27
+ const encoded = JSON.stringify(value);
28
+ if (encoded === undefined) throw new Error("owner_authority_noncanonical_value");
29
+ return encoded;
30
+ }
31
+
32
+ function sha256Ref(bytes) {
33
+ return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
34
+ }
35
+
36
+ function signingPayload(authority) {
37
+ if (!authority || typeof authority !== "object" || Array.isArray(authority)) {
38
+ throw new Error("owner_authority_invalid");
39
+ }
40
+ const {
41
+ authoritySha256,
42
+ signature,
43
+ ...payload
44
+ } = authority;
45
+ return payload;
46
+ }
47
+
48
+ export function ownerAuthoritySigningBytesV1(authority) {
49
+ return Buffer.from(canonicalJson(signingPayload(authority)), "utf8");
50
+ }
51
+
52
+ export function ownerAuthorityArtifactSha256V1(authority) {
53
+ return sha256Ref(ownerAuthoritySigningBytesV1(authority));
54
+ }
55
+
56
+ export function publicKeySha256V1(publicKey) {
57
+ const key = publicKey?.type === "public"
58
+ ? publicKey
59
+ : createPublicKey(publicKey);
60
+ return sha256Ref(key.export({ type: "spki", format: "der" }));
61
+ }
62
+
63
+ export function defaultOwnerAuthorityTrustStorePathV1() {
64
+ return path.join(
65
+ userInfo().homedir,
66
+ ".owlcoda",
67
+ "trust",
68
+ "owner-authority-keys-v1.json",
69
+ );
70
+ }
71
+
72
+ function readTrustStore(trustStorePath) {
73
+ const resolvedPath = path.resolve(trustStorePath);
74
+ let stat;
75
+ try {
76
+ stat = lstatSync(resolvedPath);
77
+ } catch (error) {
78
+ if (error?.code === "ENOENT") {
79
+ throw new Error("owner_authority_trust_store_missing");
80
+ }
81
+ throw error;
82
+ }
83
+ if (
84
+ stat.isSymbolicLink()
85
+ || !stat.isFile()
86
+ ) {
87
+ throw new Error("owner_authority_trust_store_invalid");
88
+ }
89
+ realpathSync(resolvedPath);
90
+ const parentPath = path.dirname(resolvedPath);
91
+ const parentStat = lstatSync(parentPath);
92
+ if (
93
+ parentStat.isSymbolicLink()
94
+ || !parentStat.isDirectory()
95
+ ) {
96
+ throw new Error("owner_authority_trust_store_path_invalid");
97
+ }
98
+ const currentUid = typeof process.getuid === "function"
99
+ ? process.getuid()
100
+ : null;
101
+ if (
102
+ currentUid !== null
103
+ && (stat.uid !== currentUid || parentStat.uid !== currentUid)
104
+ ) {
105
+ throw new Error("owner_authority_trust_store_owner_invalid");
106
+ }
107
+ if (
108
+ process.platform !== "win32"
109
+ && (stat.mode & 0o022) !== 0
110
+ ) {
111
+ throw new Error("owner_authority_trust_store_permissions_invalid");
112
+ }
113
+ if (
114
+ process.platform !== "win32"
115
+ && (parentStat.mode & 0o022) !== 0
116
+ ) {
117
+ throw new Error("owner_authority_trust_store_parent_permissions_invalid");
118
+ }
119
+ if (stat.size <= 0 || stat.size > MAX_TRUST_STORE_BYTES) {
120
+ throw new Error("owner_authority_trust_store_invalid");
121
+ }
122
+ const bytes = readFileSync(resolvedPath);
123
+ let value;
124
+ try {
125
+ value = JSON.parse(bytes.toString("utf8"));
126
+ } catch {
127
+ throw new Error("owner_authority_trust_store_invalid");
128
+ }
129
+ if (
130
+ value?.schemaVersion !== "OwlCodaRunKitOwnerAuthorityTrustV1"
131
+ || !Array.isArray(value.keys)
132
+ || value.keys.length === 0
133
+ ) {
134
+ throw new Error("owner_authority_trust_store_invalid");
135
+ }
136
+ const keyIds = new Set();
137
+ const keys = value.keys.map((entry) => {
138
+ if (
139
+ !entry
140
+ || typeof entry !== "object"
141
+ || Array.isArray(entry)
142
+ || Object.keys(entry).sort().join(",")
143
+ !== "algorithm,keyId,publicKeyPem,publicKeySha256,purposes,status"
144
+ || !KEY_ID.test(entry.keyId)
145
+ || entry.algorithm !== "ed25519"
146
+ || typeof entry.publicKeyPem !== "string"
147
+ || !SHA256_REF.test(entry.publicKeySha256)
148
+ || !Array.isArray(entry.purposes)
149
+ || entry.purposes.length === 0
150
+ || entry.purposes.some(purpose => !KEY_ID.test(purpose))
151
+ || new Set(entry.purposes).size !== entry.purposes.length
152
+ || entry.status !== "active"
153
+ || keyIds.has(entry.keyId)
154
+ ) {
155
+ throw new Error("owner_authority_trust_store_invalid");
156
+ }
157
+ keyIds.add(entry.keyId);
158
+ let publicKey;
159
+ try {
160
+ publicKey = createPublicKey(entry.publicKeyPem);
161
+ } catch {
162
+ throw new Error("owner_authority_trust_store_invalid");
163
+ }
164
+ if (
165
+ publicKey.asymmetricKeyType !== "ed25519"
166
+ || publicKeySha256V1(publicKey) !== entry.publicKeySha256
167
+ ) {
168
+ throw new Error("owner_authority_trust_store_invalid");
169
+ }
170
+ return {
171
+ keyId: entry.keyId,
172
+ publicKey,
173
+ purposes: [...entry.purposes],
174
+ };
175
+ });
176
+ return {
177
+ bytes,
178
+ keys,
179
+ };
180
+ }
181
+
182
+ export function verifyTrustedOwnerAuthorityV1({
183
+ authority,
184
+ expectedScope,
185
+ expectedPurpose,
186
+ trustStorePath = defaultOwnerAuthorityTrustStorePathV1(),
187
+ }) {
188
+ if (
189
+ !authority
190
+ || typeof authority !== "object"
191
+ || Array.isArray(authority)
192
+ || !KEY_ID.test(authority.signerKeyId)
193
+ || authority.signatureAlgorithm !== "ed25519"
194
+ || typeof authority.signature !== "string"
195
+ || !SIGNATURE.test(authority.signature)
196
+ || !SHA256_REF.test(authority.authoritySha256)
197
+ || typeof expectedScope !== "string"
198
+ || expectedScope.length === 0
199
+ || !KEY_ID.test(expectedPurpose)
200
+ ) {
201
+ throw new Error("owner_authority_invalid");
202
+ }
203
+ const trust = readTrustStore(path.resolve(trustStorePath));
204
+ const selected = trust.keys.find(key => key.keyId === authority.signerKeyId);
205
+ if (!selected) throw new Error("owner_authority_signer_unknown");
206
+ if (!selected.purposes.includes(expectedPurpose)) {
207
+ throw new Error("owner_authority_purpose_not_trusted");
208
+ }
209
+ const signingBytes = ownerAuthoritySigningBytesV1(authority);
210
+ if (ownerAuthorityArtifactSha256V1(authority) !== authority.authoritySha256) {
211
+ throw new Error("owner_authority_hash_mismatch");
212
+ }
213
+ if (authority.scope !== expectedScope) {
214
+ throw new Error("owner_authority_scope_mismatch");
215
+ }
216
+ const signature = Buffer.from(authority.signature, "base64");
217
+ if (
218
+ signature.length === 0
219
+ || signature.toString("base64") !== authority.signature
220
+ || !verify(null, signingBytes, selected.publicKey, signature)
221
+ ) {
222
+ throw new Error("owner_authority_signature_invalid");
223
+ }
224
+ return {
225
+ status: "trusted",
226
+ signerKeyId: authority.signerKeyId,
227
+ authoritySha256: authority.authoritySha256,
228
+ trustStoreSha256: sha256Ref(trust.bytes),
229
+ };
230
+ }
@@ -0,0 +1,385 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
4
+ const SHA256 = /^[a-f0-9]{64}$/u;
5
+ const DECISION_KEYS = [
6
+ "schemaVersion",
7
+ "decisionId",
8
+ "decisionVersion",
9
+ "provenance",
10
+ "project",
11
+ "supersedesDecisionSha256",
12
+ "deploymentMode",
13
+ "existingProjectAssets",
14
+ "legacyRollbackAllowed",
15
+ "dataAuthority",
16
+ "serviceActivationAuthorized",
17
+ "baselineCutAuthorized",
18
+ "destructiveScope",
19
+ "decisionSha256",
20
+ ];
21
+ const BINDING_KEYS = [
22
+ "decisionId",
23
+ "decisionVersion",
24
+ "decisionSha256",
25
+ "deploymentMode",
26
+ "existingProjectAssets",
27
+ "legacyRollbackAllowed",
28
+ "dataAuthority",
29
+ "serviceActivationAuthorized",
30
+ "baselineCutAuthorized",
31
+ "destructiveScope",
32
+ ];
33
+ const REUSE_FIELDS = [
34
+ "decisionSha256",
35
+ "sourceSha256",
36
+ "targetSha256",
37
+ "artifactSha256",
38
+ "remoteDeploymentIntentSha256",
39
+ "destructive",
40
+ "serviceActivationAuthorized",
41
+ "baselineCutAuthorized",
42
+ "dataAuthoritySha256",
43
+ "rollbackSemanticsSha256",
44
+ ];
45
+
46
+ function canonicalJson(value) {
47
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
48
+ if (value && typeof value === "object") {
49
+ return `{${Object.keys(value).sort()
50
+ .map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
51
+ .join(",")}}`;
52
+ }
53
+ const encoded = JSON.stringify(value);
54
+ if (encoded === undefined) throw new Error("owner_decision_noncanonical_value");
55
+ return encoded;
56
+ }
57
+
58
+ function hashObject(value) {
59
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
60
+ }
61
+
62
+ function exactKeys(value, expected, code) {
63
+ if (
64
+ !value
65
+ || typeof value !== "object"
66
+ || Array.isArray(value)
67
+ || Object.keys(value).sort().join(",") !== [...expected].sort().join(",")
68
+ ) throw new Error(code);
69
+ }
70
+
71
+ function identifier(value, code) {
72
+ if (!ID.test(value ?? "")) throw new Error(code);
73
+ return value;
74
+ }
75
+
76
+ function digest(value, code) {
77
+ if (!SHA256.test(value ?? "")) throw new Error(code);
78
+ return value;
79
+ }
80
+
81
+ function highRisk(condition) {
82
+ if (!condition) throw new Error("owner_decision_high_risk_field_unset");
83
+ }
84
+
85
+ function decisionBody(input) {
86
+ const value = {
87
+ schemaVersion: "OwlCodaRunKitOwnerDeploymentDecisionV1",
88
+ decisionId: input.decisionId,
89
+ decisionVersion: input.decisionVersion,
90
+ provenance: structuredClone(input.provenance),
91
+ project: structuredClone(input.project),
92
+ supersedesDecisionSha256: input.supersedesDecisionSha256,
93
+ deploymentMode: input.deploymentMode,
94
+ existingProjectAssets: input.existingProjectAssets,
95
+ legacyRollbackAllowed: input.legacyRollbackAllowed,
96
+ dataAuthority: structuredClone(input.dataAuthority),
97
+ serviceActivationAuthorized: input.serviceActivationAuthorized,
98
+ baselineCutAuthorized: input.baselineCutAuthorized,
99
+ destructiveScope: Array.isArray(input.destructiveScope)
100
+ ? [...input.destructiveScope]
101
+ : input.destructiveScope,
102
+ };
103
+ identifier(value.decisionId, "owner_decision_id_invalid");
104
+ if (!Number.isInteger(value.decisionVersion) || value.decisionVersion < 1) {
105
+ throw new Error("owner_decision_version_invalid");
106
+ }
107
+ exactKeys(value.provenance, ["kind", "sourceRef", "recordedAt"], "owner_decision_provenance_invalid");
108
+ if (
109
+ value.provenance.kind !== "owner_explicit"
110
+ || typeof value.provenance.sourceRef !== "string"
111
+ || value.provenance.sourceRef.length === 0
112
+ || !Number.isFinite(Date.parse(value.provenance.recordedAt))
113
+ ) throw new Error("owner_decision_provenance_invalid");
114
+ exactKeys(value.project, ["projectId", "scope"], "owner_decision_project_invalid");
115
+ identifier(value.project.projectId, "owner_decision_project_invalid");
116
+ identifier(value.project.scope, "owner_decision_project_invalid");
117
+ if (value.supersedesDecisionSha256 !== null) {
118
+ digest(value.supersedesDecisionSha256, "owner_decision_supersedes_invalid");
119
+ }
120
+ highRisk(new Set(["clean_install", "in_place_update"]).has(value.deploymentMode));
121
+ highRisk(new Set(["preserve", "replace", "remove"]).has(value.existingProjectAssets));
122
+ highRisk(typeof value.legacyRollbackAllowed === "boolean");
123
+ highRisk(value.dataAuthority && typeof value.dataAuthority === "object" && !Array.isArray(value.dataAuthority));
124
+ exactKeys(value.dataAuthority, ["mode", "sourceRef"], "owner_decision_high_risk_field_unset");
125
+ highRisk(new Set([
126
+ "preserve_existing",
127
+ "replace_from_artifact",
128
+ "initialize_new",
129
+ ]).has(value.dataAuthority.mode));
130
+ highRisk(typeof value.dataAuthority.sourceRef === "string" && value.dataAuthority.sourceRef.length > 0);
131
+ highRisk(typeof value.serviceActivationAuthorized === "boolean");
132
+ highRisk(typeof value.baselineCutAuthorized === "boolean");
133
+ highRisk(Array.isArray(value.destructiveScope));
134
+ if (
135
+ value.destructiveScope.some(item => typeof item !== "string" || item.length === 0)
136
+ || new Set(value.destructiveScope).size !== value.destructiveScope.length
137
+ ) throw new Error("owner_decision_destructive_scope_invalid");
138
+ return value;
139
+ }
140
+
141
+ export function createOwnerDeploymentDecisionV1(input) {
142
+ const body = decisionBody(input);
143
+ return { ...body, decisionSha256: hashObject(body) };
144
+ }
145
+
146
+ export function validateOwnerDeploymentDecisionV1(value) {
147
+ exactKeys(value, DECISION_KEYS, "owner_decision_invalid");
148
+ const expected = createOwnerDeploymentDecisionV1(value);
149
+ if (value.decisionSha256 !== expected.decisionSha256) {
150
+ throw new Error("owner_decision_hash_mismatch");
151
+ }
152
+ return expected;
153
+ }
154
+
155
+ export function ownerDeploymentDecisionBindingV1(value) {
156
+ const decision = validateOwnerDeploymentDecisionV1(value);
157
+ return {
158
+ decisionId: decision.decisionId,
159
+ decisionVersion: decision.decisionVersion,
160
+ decisionSha256: decision.decisionSha256,
161
+ deploymentMode: decision.deploymentMode,
162
+ existingProjectAssets: decision.existingProjectAssets,
163
+ legacyRollbackAllowed: decision.legacyRollbackAllowed,
164
+ dataAuthority: structuredClone(decision.dataAuthority),
165
+ serviceActivationAuthorized: decision.serviceActivationAuthorized,
166
+ baselineCutAuthorized: decision.baselineCutAuthorized,
167
+ destructiveScope: [...decision.destructiveScope],
168
+ };
169
+ }
170
+
171
+ export function validateOwnerDeploymentDecisionBindingV1(value) {
172
+ exactKeys(value, BINDING_KEYS, "owner_decision_binding_invalid");
173
+ identifier(value.decisionId, "owner_decision_binding_invalid");
174
+ if (!Number.isInteger(value.decisionVersion) || value.decisionVersion < 1) {
175
+ throw new Error("owner_decision_binding_invalid");
176
+ }
177
+ digest(value.decisionSha256, "owner_decision_binding_invalid");
178
+ highRisk(new Set(["clean_install", "in_place_update"]).has(value.deploymentMode));
179
+ highRisk(new Set(["preserve", "replace", "remove"]).has(value.existingProjectAssets));
180
+ highRisk(typeof value.legacyRollbackAllowed === "boolean");
181
+ highRisk(value.dataAuthority && typeof value.dataAuthority === "object" && !Array.isArray(value.dataAuthority));
182
+ exactKeys(value.dataAuthority, ["mode", "sourceRef"], "owner_decision_binding_invalid");
183
+ highRisk(new Set([
184
+ "preserve_existing",
185
+ "replace_from_artifact",
186
+ "initialize_new",
187
+ ]).has(value.dataAuthority.mode));
188
+ highRisk(typeof value.dataAuthority.sourceRef === "string" && value.dataAuthority.sourceRef.length > 0);
189
+ highRisk(typeof value.serviceActivationAuthorized === "boolean");
190
+ highRisk(typeof value.baselineCutAuthorized === "boolean");
191
+ highRisk(Array.isArray(value.destructiveScope));
192
+ if (
193
+ value.destructiveScope.some(item => typeof item !== "string" || item.length === 0)
194
+ || new Set(value.destructiveScope).size !== value.destructiveScope.length
195
+ ) throw new Error("owner_decision_binding_invalid");
196
+ return structuredClone(value);
197
+ }
198
+
199
+ function compareBinding(conflicts, surface, expected, actual) {
200
+ if (!actual || typeof actual !== "object" || Array.isArray(actual)) {
201
+ conflicts.push({
202
+ surface,
203
+ field: "ownerDecision",
204
+ decisionValue: expected,
205
+ observedValue: actual ?? null,
206
+ });
207
+ return;
208
+ }
209
+ for (const field of BINDING_KEYS) {
210
+ if (canonicalJson(actual[field]) !== canonicalJson(expected[field])) {
211
+ conflicts.push({
212
+ surface,
213
+ field,
214
+ decisionValue: structuredClone(expected[field]),
215
+ observedValue: actual[field] === undefined
216
+ ? null
217
+ : structuredClone(actual[field]),
218
+ });
219
+ }
220
+ }
221
+ }
222
+
223
+ function conflictError(conflicts) {
224
+ const error = new Error(`owner_decision_conflict:${canonicalJson(conflicts)}`);
225
+ error.code = "owner_decision_conflict";
226
+ error.conflicts = conflicts;
227
+ return error;
228
+ }
229
+
230
+ export function compileOwnerDeploymentDecisionV1(input) {
231
+ const decision = validateOwnerDeploymentDecisionV1(input?.decision);
232
+ const binding = ownerDeploymentDecisionBindingV1(decision);
233
+ const conflicts = [];
234
+ compareBinding(conflicts, "prepareReceipt", binding, input.prepareReceipt?.ownerDecision);
235
+ compareBinding(conflicts, "goal", binding, input.goal?.deployment?.ownerDecision);
236
+ compareBinding(conflicts, "remoteManifest", binding, input.remoteManifest?.ownerDecision);
237
+ const expectedMode = decision.deploymentMode === "clean_install" ? "first" : "update";
238
+ if (input.remoteManifest?.mode !== expectedMode) {
239
+ conflicts.push({
240
+ surface: "remoteManifest",
241
+ field: "deploymentMode",
242
+ decisionValue: decision.deploymentMode,
243
+ observedValue: input.remoteManifest?.mode ?? null,
244
+ });
245
+ }
246
+ for (const [field, decisionValue, observedValue] of [
247
+ [
248
+ "serviceActivationAuthorized",
249
+ decision.serviceActivationAuthorized,
250
+ input.remoteManifest?.serviceActivation,
251
+ ],
252
+ [
253
+ "baselineCutAuthorized",
254
+ decision.baselineCutAuthorized,
255
+ input.remoteManifest?.baselineCut,
256
+ ],
257
+ ]) {
258
+ if (observedValue !== decisionValue) {
259
+ conflicts.push({
260
+ surface: "remoteManifest",
261
+ field,
262
+ decisionValue,
263
+ observedValue: observedValue ?? null,
264
+ });
265
+ }
266
+ }
267
+ const destructive = decision.destructiveScope.length > 0;
268
+ if (input.goal?.authorization?.deploy !== true) {
269
+ conflicts.push({ surface: "goal", field: "deploy", decisionValue: true, observedValue: input.goal?.authorization?.deploy ?? null });
270
+ }
271
+ if (input.goal?.authorization?.destructive !== destructive) {
272
+ conflicts.push({ surface: "goal", field: "destructive", decisionValue: destructive, observedValue: input.goal?.authorization?.destructive ?? null });
273
+ }
274
+ if (input.authority?.permissions?.deploy !== true) {
275
+ conflicts.push({ surface: "authority", field: "deploy", decisionValue: true, observedValue: input.authority?.permissions?.deploy ?? null });
276
+ }
277
+ if (input.authority?.permissions?.destructive !== destructive) {
278
+ conflicts.push({ surface: "authority", field: "destructive", decisionValue: destructive, observedValue: input.authority?.permissions?.destructive ?? null });
279
+ }
280
+ for (const field of ["decisionId", "decisionSha256"]) {
281
+ const authorityField = field === "decisionId" ? "ownerDecisionId" : "ownerDecisionSha256";
282
+ if (input.authority?.[authorityField] !== decision[field]) {
283
+ conflicts.push({ surface: "authority", field, decisionValue: decision[field], observedValue: input.authority?.[authorityField] ?? null });
284
+ }
285
+ }
286
+ for (const [field, observed] of [
287
+ ["targetSha256", input.authority?.targetSha256],
288
+ ["artifactSha256", input.authority?.artifactSha256],
289
+ ["remoteDeploymentIntentSha256", input.authority?.remoteDeploymentIntentSha256],
290
+ ]) {
291
+ if (observed !== input.expected?.[field]) {
292
+ conflicts.push({ surface: "authority", field, decisionValue: input.expected?.[field] ?? null, observedValue: observed ?? null });
293
+ }
294
+ }
295
+ if (input.prepareReceipt?.artifact?.sha256 !== input.expected?.artifactSha256) {
296
+ conflicts.push({ surface: "prepareReceipt", field: "artifactSha256", decisionValue: input.expected?.artifactSha256 ?? null, observedValue: input.prepareReceipt?.artifact?.sha256 ?? null });
297
+ }
298
+ const declaredDestructiveScope = (input.remoteManifest?.deletionAllowlist ?? [])
299
+ .map(entry => entry?.path);
300
+ if (canonicalJson(declaredDestructiveScope) !== canonicalJson(decision.destructiveScope)) {
301
+ conflicts.push({ surface: "remoteManifest", field: "destructiveScope", decisionValue: decision.destructiveScope, observedValue: declaredDestructiveScope });
302
+ }
303
+ if (conflicts.length > 0) throw conflictError(conflicts);
304
+ const body = {
305
+ schemaVersion: "OwlCodaRunKitOwnerDeploymentDecisionCompilationV1",
306
+ status: "compiled",
307
+ decision: binding,
308
+ targetSha256: input.expected.targetSha256,
309
+ artifactSha256: input.expected.artifactSha256,
310
+ remoteDeploymentIntentSha256: input.expected.remoteDeploymentIntentSha256,
311
+ conflicts: [],
312
+ authorizationGranted: false,
313
+ };
314
+ return { ...body, compilationSha256: hashObject(body) };
315
+ }
316
+
317
+ export function classifyOwnerDeploymentAuthorityReuseV1({ existing, requested }) {
318
+ const changedFields = REUSE_FIELDS.filter(field => (
319
+ canonicalJson(existing?.[field]) !== canonicalJson(requested?.[field])
320
+ ));
321
+ return {
322
+ schemaVersion: "OwlCodaRunKitOwnerDeploymentAuthorityReuseV1",
323
+ status: changedFields.length === 0
324
+ ? "reusable_exact"
325
+ : "new_authority_required",
326
+ changedFields,
327
+ authorizationGranted: false,
328
+ };
329
+ }
330
+
331
+ export function classifyDeploymentEvidenceReuseV1({
332
+ fromDecisionSha256,
333
+ toDecisionSha256,
334
+ evidenceKind,
335
+ }) {
336
+ digest(fromDecisionSha256, "owner_decision_evidence_source_invalid");
337
+ digest(toDecisionSha256, "owner_decision_evidence_target_invalid");
338
+ const reusable = new Set(["linux_qualification", "source_qualification"]);
339
+ const invalidated = new Set(["rollback_plan", "remote_intent", "owner_authority"]);
340
+ if (!reusable.has(evidenceKind) && !invalidated.has(evidenceKind)) {
341
+ throw new Error("owner_decision_evidence_kind_invalid");
342
+ }
343
+ return {
344
+ schemaVersion: "OwlCodaRunKitDeploymentEvidenceDecisionClassificationV1",
345
+ evidenceKind,
346
+ fromDecisionSha256,
347
+ toDecisionSha256,
348
+ status: fromDecisionSha256 === toDecisionSha256 || reusable.has(evidenceKind)
349
+ ? "reusable_qualification"
350
+ : "invalidated_by_owner_decision",
351
+ authorizationGranted: false,
352
+ };
353
+ }
354
+
355
+ export function createDeploymentSupersessionStatusV1({
356
+ runId,
357
+ priorDecision,
358
+ replacementDecision,
359
+ }) {
360
+ identifier(runId, "owner_decision_supersession_run_invalid");
361
+ const prior = priorDecision?.schemaVersion
362
+ === "OwlCodaRunKitOwnerDeploymentDecisionV1"
363
+ ? validateOwnerDeploymentDecisionV1(priorDecision)
364
+ : {
365
+ decisionSha256: digest(
366
+ priorDecision?.decisionSha256,
367
+ "owner_decision_supersession_lineage_invalid",
368
+ ),
369
+ };
370
+ const replacement = validateOwnerDeploymentDecisionV1(replacementDecision);
371
+ if (replacement.supersedesDecisionSha256 !== prior.decisionSha256) {
372
+ throw new Error("owner_decision_supersession_lineage_invalid");
373
+ }
374
+ return {
375
+ schemaVersion: "OwlCodaRunKitDeploymentSupersessionStatusV1",
376
+ runId,
377
+ status: "closed_superseded",
378
+ priorDecisionSha256: prior.decisionSha256,
379
+ replacementDecisionSha256: replacement.decisionSha256,
380
+ businessGoalIncomplete: true,
381
+ replacementPlanRequired: true,
382
+ nextAllowedAction: "plan_replacement_execution",
383
+ authorizationGranted: false,
384
+ };
385
+ }