@kungfu-tech/buildchain 4.0.1-alpha.2 → 4.0.1-alpha.21
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/actions/release-tail/README.md +8 -0
- package/architecture/ci-lane-change-budget.json +187 -0
- package/architecture/decisions/0002-next-development-transition.md +101 -0
- package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
- package/architecture/internal-capabilities.json +211 -8
- package/architecture/maintainability-policy.json +113 -28
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +91 -0
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-capability-state-machine-manifest.json +73 -0
- package/architecture/v4-compatibility-facts-parity.json +77 -0
- package/architecture/v4-delivery-authority-parity.json +250 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
- package/architecture/v4-floating-consumer-policy.json +6 -0
- package/architecture/v4-next-development-parity.json +192 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-release-train-parity.json +1 -0
- package/architecture/v4-tail-reseal-parity.json +150 -0
- package/bin/buildchain.mjs +32 -3
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +3 -0
- package/bin/internal/compatibility-facts-cli.mjs +101 -0
- package/contracts/dev-delivery-authority-v2.schema.json +662 -0
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
- package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
- package/contracts/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/contracts/v4-tail-reseal-v1.schema.json +276 -0
- package/dist/site/agent-index.json +5 -0
- package/dist/site/artifact-schemas.json +10 -0
- package/dist/site/buildchain-contract.json +1475 -27
- package/dist/site/buildchain-site.json +627 -31
- package/dist/site/capability-registry.json +17 -14
- package/dist/site/cli-registry.json +205 -3
- package/dist/site/kfd-claims.json +490 -16
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +52 -6
- package/dist/site/node-api-registry.json +14736 -8851
- package/dist/site/page-registry.json +593 -21
- package/dist/site/public-surface-audit.json +837 -48
- package/dist/site/publication-authority-registry.json +44 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +24 -0
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
- package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
- package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +34 -10
- package/dist/site/workflow-registry.json +160 -10
- package/docs/MAP.md +8 -2
- package/docs/cli-reference.md +404 -13
- package/docs/cli.md +8 -0
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/dev-delivery-qualification-landing-adr.md +251 -0
- package/docs/dev-delivery-warrant.md +351 -38
- package/docs/next-development-transition.md +118 -0
- package/docs/node-api-reference.md +646 -293
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +133 -0
- package/docs/v4-compatibility-facts.md +91 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/docs/v4-tail-reseal.md +73 -0
- package/docs/versioning.md +4 -3
- package/package.json +34 -6
- package/packages/core/adopter-delivery-passport.js +259 -0
- package/packages/core/adopter-delivery-vectors.js +158 -0
- package/packages/core/adopter-delivery-vectors.json +77 -0
- package/packages/core/buildchain-agent-manuals.js +3 -0
- package/packages/core/buildchain-compatibility-authority.js +336 -0
- package/packages/core/buildchain-compatibility-fact.js +394 -0
- package/packages/core/buildchain-compatibility-facts.json +217 -0
- package/packages/core/buildchain-compatibility-proof.js +566 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-contract.js +96 -143
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +55 -0
- package/packages/core/ci-lane-change-budget.js +247 -0
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-authority-candidate.js +270 -0
- package/packages/core/dev-delivery-authority-evidence.js +146 -0
- package/packages/core/dev-delivery-authority-landing.js +461 -0
- package/packages/core/dev-delivery-authority-observation.js +48 -0
- package/packages/core/dev-delivery-authority-qualification.js +591 -0
- package/packages/core/dev-delivery-authority-settlement.js +213 -0
- package/packages/core/dev-delivery-authority-state.js +583 -0
- package/packages/core/dev-delivery-candidate-identity.js +13 -0
- package/packages/core/dev-delivery-contract-surface.js +76 -0
- package/packages/core/dev-delivery-execution-failure.js +133 -0
- package/packages/core/dev-delivery-execution-transfer.js +592 -0
- package/packages/core/dev-delivery-landing-admission-core.js +119 -0
- package/packages/core/dev-delivery-landing-readback.js +598 -0
- package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
- package/packages/core/dev-delivery-landing-testing-port.js +6 -0
- package/packages/core/dev-delivery-native-execution.js +110 -0
- package/packages/core/dev-delivery-native-proof.js +562 -0
- package/packages/core/dev-delivery-process-boundary.js +563 -0
- package/packages/core/dev-delivery-provider-attempt.js +127 -0
- package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-qualification.js +145 -0
- package/packages/core/dev-delivery-warrant-settlement.js +237 -36
- package/packages/core/dev-delivery-warrant-state.js +587 -0
- package/packages/core/dev-delivery-warrant.js +328 -370
- package/packages/core/github-governance-authority.js +1 -0
- package/packages/core/index.js +5 -0
- package/packages/core/kungfu-temporal-fact.js +557 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/next-development-candidate-reservation.js +186 -0
- package/packages/core/next-development-controller.js +728 -0
- package/packages/core/next-development-projection.js +287 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper.js +16 -5
- package/packages/core/published-delivery-authority.js +266 -0
- package/packages/core/release-candidate-recovery.js +70 -15
- package/packages/core/release-candidate.js +2 -2
- package/packages/core/release-passport.js +36 -9
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-canonical-contracts.js +5 -0
- package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
- package/packages/core/v4-floating-consumer-evidence.js +18 -4
- package/packages/core/v4-floating-consumer-policy.js +143 -20
- package/packages/core/v4-floating-consumer-release-passport.js +32 -5
- package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
- package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
- package/packages/core/v4-publication-rehearsal.js +481 -0
- package/packages/core/v4-tail-reseal-contract.js +26 -0
- package/packages/core/v4-tail-reseal-github.js +178 -0
- package/packages/core/v4-tail-reseal-receipt.js +225 -0
- package/packages/core/v4-tail-reseal.js +594 -0
- package/packages/core/workflow-yaml-contract.js +24 -2
- package/scripts/buildchain-cli-help.mjs +41 -2
- package/scripts/check-ci-lane-change-budget.mjs +100 -0
- package/scripts/check-inventory.mjs +14 -3
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
- package/scripts/dev-delivery-authority-provider.mjs +28 -0
- package/scripts/dev-delivery-authority.mjs +490 -0
- package/scripts/dev-delivery-native-run.mjs +177 -0
- package/scripts/dev-delivery-process-boundary.mjs +260 -0
- package/scripts/dev-delivery-proof.mjs +67 -2
- package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
- package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
- package/scripts/dev-delivery-two-phase.mjs +573 -0
- package/scripts/dev-delivery-warrant-options.mjs +266 -0
- package/scripts/dev-delivery-warrant-store.mjs +231 -0
- package/scripts/dev-delivery-warrant.mjs +232 -194
- package/scripts/dev-pr-auto-merge.mjs +1 -1
- package/scripts/dev-pr-delivery-warrant.mjs +11 -0
- package/scripts/dev-qualification-patrol.mjs +9 -9
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +2 -2
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-release-candidate-passport.mjs +152 -0
- package/scripts/generate-site-bundle.mjs +69 -4
- package/scripts/init-repo.mjs +22 -4
- package/scripts/next-development-self-dogfood-harness.mjs +460 -0
- package/scripts/next-development-self-dogfood.mjs +597 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/npm-publish-transaction.mjs +2 -2
- package/scripts/promotion-channel-router.mjs +4 -4
- package/scripts/release-candidate-anchor-provenance.mjs +99 -0
- package/scripts/release-tail.mjs +51 -3
- package/scripts/release-train-self-dogfood.mjs +94 -92
- package/scripts/resume-from-candidate-run.mjs +557 -30
- package/scripts/site-capability-metadata.mjs +41 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v4-consumer-policy.mjs +5 -5
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
- package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
- package/scripts/v4-tail-reseal.mjs +416 -0
- package/templates/native-dev-delivery.yml +143 -0
|
@@ -116,6 +116,7 @@ const PROTECTED_AUTHORITY_PATHS = Object.freeze([
|
|
|
116
116
|
".github/CODEOWNERS",
|
|
117
117
|
".github/workflows/.publication-authority.yml",
|
|
118
118
|
".github/workflows/.release-candidate-promote.yml",
|
|
119
|
+
".github/workflows/buildchain-ref-promotion-recovery.yml",
|
|
119
120
|
".github/workflows/buildchain-ref-promotion.yml",
|
|
120
121
|
".github/workflows/github-governance-audit.yml",
|
|
121
122
|
".github/workflows/paper-release-sealed.yml",
|
package/packages/core/index.js
CHANGED
|
@@ -139,6 +139,11 @@ export {
|
|
|
139
139
|
normalizeCandidateTimelineEvent,
|
|
140
140
|
} from "./candidate-timeline.js";
|
|
141
141
|
|
|
142
|
+
export {
|
|
143
|
+
CI_LANE_CHANGE_BUDGET_CONTRACT,
|
|
144
|
+
evaluateCiLaneChangeBudget,
|
|
145
|
+
} from "./ci-lane-change-budget.js";
|
|
146
|
+
|
|
142
147
|
export {
|
|
143
148
|
CHANNEL_CANDIDATE_DECISION_SCHEMA,
|
|
144
149
|
channelCandidateSourceLockRef,
|
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const KUNGFU_FACT_ROOT_PROTOCOL = "kungfu.fact-root.canonical/v2";
|
|
4
|
+
export const KUNGFU_TEMPORAL_BUNDLE_SCHEMA = "kungfu.fact.temporal-bundle/v1";
|
|
5
|
+
export const KUNGFU_TEMPORAL_PATH_QUERY_SCHEMA =
|
|
6
|
+
"kungfu.fact.temporal-path-query/v1";
|
|
7
|
+
export const KUNGFU_TEMPORAL_PATH_RECEIPT_SCHEMA =
|
|
8
|
+
"kungfu.fact.temporal-path-receipt/v1";
|
|
9
|
+
|
|
10
|
+
const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
11
|
+
const MAX_PATH_DEPTH = 32;
|
|
12
|
+
const SCHEMA_FIELDS = {
|
|
13
|
+
"kungfu.fact.temporal-predicate/v1": [
|
|
14
|
+
"schema",
|
|
15
|
+
"predicateId",
|
|
16
|
+
"operations",
|
|
17
|
+
"direction",
|
|
18
|
+
"pathPolicy",
|
|
19
|
+
"cyclePolicy",
|
|
20
|
+
"authorityRoot",
|
|
21
|
+
],
|
|
22
|
+
"kungfu.fact.temporal-relation/v1": [
|
|
23
|
+
"schema",
|
|
24
|
+
"relationId",
|
|
25
|
+
"predicateRoot",
|
|
26
|
+
"sourceRoot",
|
|
27
|
+
"targetRoot",
|
|
28
|
+
"validFromCutRoot",
|
|
29
|
+
"scopeRoot",
|
|
30
|
+
"authorityRoot",
|
|
31
|
+
"admissionRoots",
|
|
32
|
+
],
|
|
33
|
+
"kungfu.fact.temporal-supersession/v1": [
|
|
34
|
+
"schema",
|
|
35
|
+
"priorRelationRoot",
|
|
36
|
+
"successorRelationRoot",
|
|
37
|
+
"effectiveCutRoot",
|
|
38
|
+
"reasonRoot",
|
|
39
|
+
"authorityRoot",
|
|
40
|
+
"admissionRoots",
|
|
41
|
+
],
|
|
42
|
+
"kungfu.fact.temporal-revocation/v1": [
|
|
43
|
+
"schema",
|
|
44
|
+
"relationRoot",
|
|
45
|
+
"effectiveCutRoot",
|
|
46
|
+
"reasonRoot",
|
|
47
|
+
"authorityRoot",
|
|
48
|
+
"admissionRoots",
|
|
49
|
+
],
|
|
50
|
+
"kungfu.fact.temporal-authority-proof/v1": [
|
|
51
|
+
"schema",
|
|
52
|
+
"proofId",
|
|
53
|
+
"subjectAuthorityRoot",
|
|
54
|
+
"governingAuthorityRoot",
|
|
55
|
+
"operations",
|
|
56
|
+
"validFromCutRoot",
|
|
57
|
+
"revokedAtCutRoot",
|
|
58
|
+
"admissionRoots",
|
|
59
|
+
],
|
|
60
|
+
[KUNGFU_TEMPORAL_PATH_QUERY_SCHEMA]: [
|
|
61
|
+
"schema",
|
|
62
|
+
"queryId",
|
|
63
|
+
"operation",
|
|
64
|
+
"predicateRoot",
|
|
65
|
+
"sourceRoot",
|
|
66
|
+
"targetRoot",
|
|
67
|
+
"cutRoot",
|
|
68
|
+
"relationPathRoots",
|
|
69
|
+
"requiredAuthorityRoot",
|
|
70
|
+
"maxDepth",
|
|
71
|
+
],
|
|
72
|
+
[KUNGFU_TEMPORAL_PATH_RECEIPT_SCHEMA]: [
|
|
73
|
+
"schema",
|
|
74
|
+
"queryRoot",
|
|
75
|
+
"status",
|
|
76
|
+
"failureCode",
|
|
77
|
+
"cutRoot",
|
|
78
|
+
"relationPathRoots",
|
|
79
|
+
"authorityProofRoots",
|
|
80
|
+
"omissionRoots",
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
const SET_FIELDS = new Set([
|
|
84
|
+
"operations",
|
|
85
|
+
"admissionRoots",
|
|
86
|
+
"authorityProofRoots",
|
|
87
|
+
"omissionRoots",
|
|
88
|
+
]);
|
|
89
|
+
const ARRAY_FIELDS = new Set(["relationPathRoots"]);
|
|
90
|
+
|
|
91
|
+
function fail(code, message) {
|
|
92
|
+
const error = new Error(message);
|
|
93
|
+
error.code = code;
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function u64(value) {
|
|
98
|
+
const bytes = Buffer.alloc(8);
|
|
99
|
+
bytes.writeBigUInt64BE(BigInt(value));
|
|
100
|
+
return bytes;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function textBytes(value) {
|
|
104
|
+
return Buffer.from(value, "utf8");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function encodeTyped(value) {
|
|
108
|
+
switch (value?.type) {
|
|
109
|
+
case "null":
|
|
110
|
+
return Buffer.from([0]);
|
|
111
|
+
case "u64":
|
|
112
|
+
return Buffer.concat([Buffer.from([0x10]), u64(value.value)]);
|
|
113
|
+
case "text": {
|
|
114
|
+
const bytes = textBytes(value.value);
|
|
115
|
+
return Buffer.concat([Buffer.from([0x20]), u64(bytes.length), bytes]);
|
|
116
|
+
}
|
|
117
|
+
case "array":
|
|
118
|
+
case "set": {
|
|
119
|
+
const items = (value.items || []).map(encodeTyped);
|
|
120
|
+
if (value.type === "set") items.sort(Buffer.compare);
|
|
121
|
+
if (
|
|
122
|
+
items.some(
|
|
123
|
+
(entry, index) => index > 0 && entry.equals(items[index - 1]),
|
|
124
|
+
)
|
|
125
|
+
) {
|
|
126
|
+
fail("canonical-duplicate-item", "set contains equal canonical items");
|
|
127
|
+
}
|
|
128
|
+
return Buffer.concat([
|
|
129
|
+
Buffer.from([value.type === "array" ? 0x30 : 0x31]),
|
|
130
|
+
u64(items.length),
|
|
131
|
+
...items,
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
case "record": {
|
|
135
|
+
const allowed = SCHEMA_FIELDS[value.schema];
|
|
136
|
+
if (!allowed)
|
|
137
|
+
fail(
|
|
138
|
+
"canonical-unknown-schema",
|
|
139
|
+
`unregistered temporal schema: ${value.schema || "<empty>"}`,
|
|
140
|
+
);
|
|
141
|
+
const fields = [...(value.fields || [])].sort(
|
|
142
|
+
(left, right) => Number(left.id) - Number(right.id),
|
|
143
|
+
);
|
|
144
|
+
if (
|
|
145
|
+
fields.length !== allowed.length ||
|
|
146
|
+
fields.some((field, index) => Number(field.id) !== index + 1)
|
|
147
|
+
) {
|
|
148
|
+
fail(
|
|
149
|
+
"canonical-missing-field",
|
|
150
|
+
"temporal record does not contain its exact closed field set",
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const schema = encodeTyped({ type: "text", value: value.schema });
|
|
154
|
+
return Buffer.concat([
|
|
155
|
+
Buffer.from([0x40]),
|
|
156
|
+
schema,
|
|
157
|
+
u64(fields.length),
|
|
158
|
+
...fields.flatMap((field) => [u64(field.id), encodeTyped(field.value)]),
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
default:
|
|
162
|
+
fail(
|
|
163
|
+
"canonical-unsupported-type",
|
|
164
|
+
`unsupported canonical type: ${value?.type || "<empty>"}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function root(value, field) {
|
|
170
|
+
if (typeof value !== "string" || !ROOT_PATTERN.test(value))
|
|
171
|
+
fail("orphan-root", `${field} must be a lowercase SHA-256 root`);
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function typedField(field, value) {
|
|
176
|
+
if (SET_FIELDS.has(field)) {
|
|
177
|
+
if (
|
|
178
|
+
!Array.isArray(value) ||
|
|
179
|
+
value.some((entry) => typeof entry !== "string")
|
|
180
|
+
)
|
|
181
|
+
fail("invalid-record", `${field} must be an array of strings`);
|
|
182
|
+
return {
|
|
183
|
+
type: "set",
|
|
184
|
+
items: value.map((entry) => ({
|
|
185
|
+
type: "text",
|
|
186
|
+
value: field.endsWith("Roots") ? root(entry, field) : entry,
|
|
187
|
+
})),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (ARRAY_FIELDS.has(field)) {
|
|
191
|
+
if (!Array.isArray(value))
|
|
192
|
+
fail("invalid-record", `${field} must be an array`);
|
|
193
|
+
return {
|
|
194
|
+
type: "array",
|
|
195
|
+
items: value.map((entry) => ({
|
|
196
|
+
type: "text",
|
|
197
|
+
value: root(entry, field),
|
|
198
|
+
})),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (field === "revokedAtCutRoot" && value === null) return { type: "null" };
|
|
202
|
+
if (field === "maxDepth") {
|
|
203
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
204
|
+
fail("invalid-record", "maxDepth must be an unsigned integer");
|
|
205
|
+
return { type: "u64", value: String(value) };
|
|
206
|
+
}
|
|
207
|
+
if (field.endsWith("Root"))
|
|
208
|
+
return { type: "text", value: root(value, field) };
|
|
209
|
+
if (typeof value !== "string" || (!value && field !== "failureCode"))
|
|
210
|
+
fail("invalid-record", `${field} must be a non-empty string`);
|
|
211
|
+
return { type: "text", value };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function kungfuTemporalRecordRoot(record) {
|
|
215
|
+
const fields = SCHEMA_FIELDS[record?.schema];
|
|
216
|
+
if (
|
|
217
|
+
!fields ||
|
|
218
|
+
Object.keys(record).length !== fields.length ||
|
|
219
|
+
fields.some((field) => !(field in record))
|
|
220
|
+
) {
|
|
221
|
+
fail(
|
|
222
|
+
"invalid-record",
|
|
223
|
+
"temporal record fields do not match its closed schema",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const descriptor = {
|
|
227
|
+
type: "record",
|
|
228
|
+
schema: record.schema,
|
|
229
|
+
fields: fields.map((field, index) => ({
|
|
230
|
+
id: String(index + 1),
|
|
231
|
+
value: typedField(field, record[field]),
|
|
232
|
+
})),
|
|
233
|
+
};
|
|
234
|
+
return `sha256:${crypto
|
|
235
|
+
.createHash("sha256")
|
|
236
|
+
.update(Buffer.concat([Buffer.from("KFR2"), encodeTyped(descriptor)]))
|
|
237
|
+
.digest("hex")}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function createKungfuTemporalEntry(record) {
|
|
241
|
+
return {
|
|
242
|
+
root: kungfuTemporalRecordRoot(record),
|
|
243
|
+
record: structuredClone(record),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function indexEntries(entries, family) {
|
|
248
|
+
if (!Array.isArray(entries))
|
|
249
|
+
fail("invalid-bundle", `${family} must be an array`);
|
|
250
|
+
const indexed = new Map();
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
if (!entry || Object.keys(entry).sort().join(",") !== "record,root")
|
|
253
|
+
fail("invalid-bundle", `${family} entries require root and record`);
|
|
254
|
+
if (entry.root !== kungfuTemporalRecordRoot(entry.record))
|
|
255
|
+
fail("root-mismatch", `${family} record root does not match its bytes`);
|
|
256
|
+
if (indexed.has(entry.root))
|
|
257
|
+
fail("ambiguous-root", `${family} contains a duplicate root`);
|
|
258
|
+
indexed.set(entry.root, entry.record);
|
|
259
|
+
}
|
|
260
|
+
return indexed;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function indexCuts(entries) {
|
|
264
|
+
const cuts = new Map();
|
|
265
|
+
for (const cut of entries || []) {
|
|
266
|
+
if (
|
|
267
|
+
!cut ||
|
|
268
|
+
Object.keys(cut).sort().join(",") !==
|
|
269
|
+
"activeRelationRoots,declarationRoots,parentCutRoots,root"
|
|
270
|
+
)
|
|
271
|
+
fail("invalid-bundle", "Cut projection has unexpected fields");
|
|
272
|
+
root(cut.root, "cut.root");
|
|
273
|
+
if (cuts.has(cut.root))
|
|
274
|
+
fail("ambiguous-root", "Cut projection contains a duplicate root");
|
|
275
|
+
for (const field of [
|
|
276
|
+
"parentCutRoots",
|
|
277
|
+
"activeRelationRoots",
|
|
278
|
+
"declarationRoots",
|
|
279
|
+
]) {
|
|
280
|
+
if (
|
|
281
|
+
!Array.isArray(cut[field]) ||
|
|
282
|
+
new Set(cut[field]).size !== cut[field].length
|
|
283
|
+
)
|
|
284
|
+
fail("invalid-bundle", `${field} must be a duplicate-free array`);
|
|
285
|
+
cut[field].forEach((entry) => root(entry, field));
|
|
286
|
+
}
|
|
287
|
+
cuts.set(cut.root, cut);
|
|
288
|
+
}
|
|
289
|
+
return cuts;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function ancestorResolver(cuts) {
|
|
293
|
+
const ancestor = (earlier, later, visiting = new Set()) => {
|
|
294
|
+
if (earlier === later) return true;
|
|
295
|
+
if (visiting.has(later)) fail("forbidden-cycle", "Cut lineage is cyclic");
|
|
296
|
+
const cut = cuts.get(later);
|
|
297
|
+
if (!cut) fail("orphan-root", "Cut lineage references an unknown root");
|
|
298
|
+
visiting.add(later);
|
|
299
|
+
const found = cut.parentCutRoots.some((parent) =>
|
|
300
|
+
ancestor(earlier, parent, visiting),
|
|
301
|
+
);
|
|
302
|
+
visiting.delete(later);
|
|
303
|
+
return found;
|
|
304
|
+
};
|
|
305
|
+
return ancestor;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function assertCutAcyclic(cuts) {
|
|
309
|
+
const visited = new Set();
|
|
310
|
+
const visit = (cutRoot, visiting = new Set()) => {
|
|
311
|
+
if (visiting.has(cutRoot)) fail("forbidden-cycle", "Cut lineage is cyclic");
|
|
312
|
+
if (visited.has(cutRoot)) return;
|
|
313
|
+
const cut = cuts.get(cutRoot);
|
|
314
|
+
if (!cut) fail("orphan-root", "Cut parent is absent from bundle");
|
|
315
|
+
visiting.add(cutRoot);
|
|
316
|
+
cut.parentCutRoots.forEach((parent) => visit(parent, visiting));
|
|
317
|
+
visiting.delete(cutRoot);
|
|
318
|
+
visited.add(cutRoot);
|
|
319
|
+
};
|
|
320
|
+
[...cuts.keys()].forEach((cutRoot) => visit(cutRoot));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function validateCutReferences(bundle) {
|
|
324
|
+
assertCutAcyclic(bundle.cuts);
|
|
325
|
+
for (const cut of bundle.cuts.values()) {
|
|
326
|
+
if (cut.activeRelationRoots.some((entry) => !bundle.relations.has(entry))) {
|
|
327
|
+
fail("orphan-root", "Cut contains an unknown relation");
|
|
328
|
+
}
|
|
329
|
+
if (cut.declarationRoots.some((entry) => !bundle.predicates.has(entry))) {
|
|
330
|
+
fail("orphan-root", "Cut contains an unknown predicate");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function validateDeclarations(bundle) {
|
|
336
|
+
for (const relation of bundle.relations.values()) {
|
|
337
|
+
if (!bundle.predicates.has(relation.predicateRoot))
|
|
338
|
+
fail("unknown-predicate", "relation references an unknown predicate");
|
|
339
|
+
if (!bundle.cuts.has(relation.validFromCutRoot))
|
|
340
|
+
fail("orphan-root", "relation references an unknown Cut");
|
|
341
|
+
}
|
|
342
|
+
for (const predicate of bundle.predicates.values()) {
|
|
343
|
+
if (
|
|
344
|
+
predicate.direction !== "source-to-target" ||
|
|
345
|
+
!["single-edge", "explicit-bounded"].includes(predicate.pathPolicy) ||
|
|
346
|
+
predicate.cyclePolicy !== "forbid" ||
|
|
347
|
+
predicate.operations.length === 0
|
|
348
|
+
) {
|
|
349
|
+
fail(
|
|
350
|
+
"invalid-record",
|
|
351
|
+
"predicate direction, path, cycle, or operation policy is invalid",
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function validateSupersessions(bundle) {
|
|
358
|
+
const supersessionEdges = new Map();
|
|
359
|
+
for (const record of bundle.supersessions.values()) {
|
|
360
|
+
if (
|
|
361
|
+
!bundle.relations.has(record.priorRelationRoot) ||
|
|
362
|
+
!bundle.relations.has(record.successorRelationRoot) ||
|
|
363
|
+
!bundle.cuts.has(record.effectiveCutRoot)
|
|
364
|
+
)
|
|
365
|
+
fail("orphan-root", "supersession references an unknown relation or Cut");
|
|
366
|
+
supersessionEdges.set(record.priorRelationRoot, [
|
|
367
|
+
...(supersessionEdges.get(record.priorRelationRoot) || []),
|
|
368
|
+
record.successorRelationRoot,
|
|
369
|
+
]);
|
|
370
|
+
}
|
|
371
|
+
const visit = (current, visiting = new Set(), visited = new Set()) => {
|
|
372
|
+
if (visiting.has(current))
|
|
373
|
+
fail("forbidden-cycle", "supersession records form a cycle");
|
|
374
|
+
if (visited.has(current)) return;
|
|
375
|
+
visiting.add(current);
|
|
376
|
+
(supersessionEdges.get(current) || []).forEach((next) =>
|
|
377
|
+
visit(next, visiting, visited),
|
|
378
|
+
);
|
|
379
|
+
visiting.delete(current);
|
|
380
|
+
visited.add(current);
|
|
381
|
+
};
|
|
382
|
+
[...supersessionEdges.keys()].forEach((entry) => visit(entry));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function validateRevocations(bundle) {
|
|
386
|
+
for (const record of bundle.revocations.values()) {
|
|
387
|
+
if (
|
|
388
|
+
!bundle.relations.has(record.relationRoot) ||
|
|
389
|
+
!bundle.cuts.has(record.effectiveCutRoot)
|
|
390
|
+
)
|
|
391
|
+
fail("orphan-root", "revocation references an unknown relation or Cut");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function loadBundle(document) {
|
|
396
|
+
const fields = [
|
|
397
|
+
"schema",
|
|
398
|
+
"cuts",
|
|
399
|
+
"predicates",
|
|
400
|
+
"relations",
|
|
401
|
+
"supersessions",
|
|
402
|
+
"revocations",
|
|
403
|
+
"authorityProofs",
|
|
404
|
+
"provenanceObjects",
|
|
405
|
+
];
|
|
406
|
+
if (
|
|
407
|
+
!document ||
|
|
408
|
+
Object.keys(document).sort().join(",") !== [...fields].sort().join(",") ||
|
|
409
|
+
document.schema !== KUNGFU_TEMPORAL_BUNDLE_SCHEMA
|
|
410
|
+
) {
|
|
411
|
+
fail(
|
|
412
|
+
"invalid-bundle",
|
|
413
|
+
"bundle fields do not match the closed temporal schema",
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
const cuts = indexCuts(document.cuts);
|
|
417
|
+
const bundle = {
|
|
418
|
+
cuts,
|
|
419
|
+
predicates: indexEntries(document.predicates, "predicates"),
|
|
420
|
+
relations: indexEntries(document.relations, "relations"),
|
|
421
|
+
supersessions: indexEntries(document.supersessions, "supersessions"),
|
|
422
|
+
revocations: indexEntries(document.revocations, "revocations"),
|
|
423
|
+
authorityProofs: indexEntries(document.authorityProofs, "authorityProofs"),
|
|
424
|
+
};
|
|
425
|
+
validateCutReferences(bundle);
|
|
426
|
+
validateDeclarations(bundle);
|
|
427
|
+
validateSupersessions(bundle);
|
|
428
|
+
validateRevocations(bundle);
|
|
429
|
+
const ancestor = ancestorResolver(cuts);
|
|
430
|
+
return { ...bundle, ancestor };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function receipt(query, status, failureCode, authorityProofRoots = []) {
|
|
434
|
+
return createKungfuTemporalEntry({
|
|
435
|
+
schema: KUNGFU_TEMPORAL_PATH_RECEIPT_SCHEMA,
|
|
436
|
+
queryRoot: kungfuTemporalRecordRoot(query),
|
|
437
|
+
status,
|
|
438
|
+
failureCode,
|
|
439
|
+
cutRoot: query.cutRoot,
|
|
440
|
+
relationPathRoots: [...query.relationPathRoots],
|
|
441
|
+
authorityProofRoots: [...new Set(authorityProofRoots)].sort(),
|
|
442
|
+
omissionRoots: [],
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function queryContext(bundle, query) {
|
|
447
|
+
const cut = bundle.cuts.get(query.cutRoot);
|
|
448
|
+
if (!cut) fail("orphan-root", "query Cut is absent from bundle");
|
|
449
|
+
const predicate = bundle.predicates.get(query.predicateRoot);
|
|
450
|
+
if (!predicate || !cut.declarationRoots.includes(query.predicateRoot)) {
|
|
451
|
+
fail("unknown-predicate", "predicate is not declared at this Cut");
|
|
452
|
+
}
|
|
453
|
+
if (predicate.authorityRoot !== query.requiredAuthorityRoot) {
|
|
454
|
+
fail("authority-missing", "query authority does not own predicate");
|
|
455
|
+
}
|
|
456
|
+
if (!predicate.operations.includes(query.operation)) {
|
|
457
|
+
fail("unscoped-compatibility", "operation is outside predicate scope");
|
|
458
|
+
}
|
|
459
|
+
const path = query.relationPathRoots;
|
|
460
|
+
if (
|
|
461
|
+
!Number.isSafeInteger(query.maxDepth) ||
|
|
462
|
+
query.maxDepth < 1 ||
|
|
463
|
+
query.maxDepth > MAX_PATH_DEPTH ||
|
|
464
|
+
path.length > query.maxDepth
|
|
465
|
+
) {
|
|
466
|
+
fail("path-bound-exceeded", "path exceeds verifier bound");
|
|
467
|
+
}
|
|
468
|
+
if (path.length === 0)
|
|
469
|
+
fail("path-missing", "the verifier does not search for a path");
|
|
470
|
+
if (new Set(path).size !== path.length)
|
|
471
|
+
fail("forbidden-cycle", "a relation repeats in the explicit path");
|
|
472
|
+
if (path.length > 1 && predicate.pathPolicy !== "explicit-bounded") {
|
|
473
|
+
fail("implicit-transitive-acceptance", "predicate forbids composed paths");
|
|
474
|
+
}
|
|
475
|
+
return { cut, path };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function assertRelationCurrent(bundle, relationRoot, cutRoot, cut) {
|
|
479
|
+
const relation = bundle.relations.get(relationRoot);
|
|
480
|
+
if (!relation) fail("orphan-root", "path contains an unknown relation");
|
|
481
|
+
if (!bundle.ancestor(relation.validFromCutRoot, cutRoot))
|
|
482
|
+
fail("relation-not-yet-valid", "relation is not valid at Cut");
|
|
483
|
+
const superseded = [...bundle.supersessions.values()].some(
|
|
484
|
+
(record) =>
|
|
485
|
+
record.priorRelationRoot === relationRoot &&
|
|
486
|
+
bundle.ancestor(record.effectiveCutRoot, cutRoot),
|
|
487
|
+
);
|
|
488
|
+
if (superseded) fail("relation-superseded", "relation was superseded at Cut");
|
|
489
|
+
const revoked = [...bundle.revocations.values()].some(
|
|
490
|
+
(record) =>
|
|
491
|
+
record.relationRoot === relationRoot &&
|
|
492
|
+
bundle.ancestor(record.effectiveCutRoot, cutRoot),
|
|
493
|
+
);
|
|
494
|
+
if (revoked) fail("relation-revoked", "relation was revoked at Cut");
|
|
495
|
+
if (!cut.activeRelationRoots.includes(relationRoot))
|
|
496
|
+
fail("relation-inactive-at-cut", "relation is inactive at Cut");
|
|
497
|
+
return relation;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function authorityRoots(bundle, relation, query) {
|
|
501
|
+
if (relation.authorityRoot === query.requiredAuthorityRoot) return [];
|
|
502
|
+
const matches = [...bundle.authorityProofs.entries()].filter(
|
|
503
|
+
([, proof]) =>
|
|
504
|
+
proof.subjectAuthorityRoot === relation.authorityRoot &&
|
|
505
|
+
proof.governingAuthorityRoot === query.requiredAuthorityRoot &&
|
|
506
|
+
proof.operations.includes(query.operation) &&
|
|
507
|
+
bundle.ancestor(proof.validFromCutRoot, query.cutRoot) &&
|
|
508
|
+
(proof.revokedAtCutRoot === null ||
|
|
509
|
+
!bundle.ancestor(proof.revokedAtCutRoot, query.cutRoot)),
|
|
510
|
+
);
|
|
511
|
+
if (matches.length === 0)
|
|
512
|
+
fail("authority-missing", "no exact authority proof applies at Cut");
|
|
513
|
+
if (matches.length > 1)
|
|
514
|
+
fail("ambiguous-authority", "more than one authority proof applies");
|
|
515
|
+
return [matches[0][0]];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function walkPath(bundle, query, context, authorityProofRoots) {
|
|
519
|
+
let cursor = query.sourceRoot;
|
|
520
|
+
const endpoints = new Set([cursor]);
|
|
521
|
+
for (const relationRoot of context.path) {
|
|
522
|
+
const relation = assertRelationCurrent(
|
|
523
|
+
bundle,
|
|
524
|
+
relationRoot,
|
|
525
|
+
query.cutRoot,
|
|
526
|
+
context.cut,
|
|
527
|
+
);
|
|
528
|
+
if (relation.predicateRoot !== query.predicateRoot)
|
|
529
|
+
fail("predicate-mismatch", "path crosses predicates");
|
|
530
|
+
if (relation.sourceRoot !== cursor)
|
|
531
|
+
fail("direction-mismatch", "relation direction does not match path");
|
|
532
|
+
cursor = relation.targetRoot;
|
|
533
|
+
if (endpoints.has(cursor))
|
|
534
|
+
fail("forbidden-cycle", "explicit path repeats an endpoint");
|
|
535
|
+
endpoints.add(cursor);
|
|
536
|
+
authorityProofRoots.push(...authorityRoots(bundle, relation, query));
|
|
537
|
+
}
|
|
538
|
+
if (cursor !== query.targetRoot)
|
|
539
|
+
fail("direction-mismatch", "explicit path does not reach target");
|
|
540
|
+
return authorityProofRoots;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function verifyKungfuTemporalPath(document, query) {
|
|
544
|
+
if (query?.schema !== KUNGFU_TEMPORAL_PATH_QUERY_SCHEMA)
|
|
545
|
+
fail("unknown-schema", "query schema is not supported");
|
|
546
|
+
kungfuTemporalRecordRoot(query);
|
|
547
|
+
const authorityProofRoots = [];
|
|
548
|
+
try {
|
|
549
|
+
const bundle = loadBundle(document);
|
|
550
|
+
const context = queryContext(bundle, query);
|
|
551
|
+
walkPath(bundle, query, context, authorityProofRoots);
|
|
552
|
+
return receipt(query, "accepted", "", authorityProofRoots);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (!error?.code) throw error;
|
|
555
|
+
return receipt(query, "rejected", error.code, authorityProofRoots);
|
|
556
|
+
}
|
|
557
|
+
}
|