@riddledc/riddle-proof 0.8.80 → 0.8.82
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/README.md +79 -0
- package/dist/change-proof.js +2 -2
- package/dist/{chunk-ZNVYJFR7.js → chunk-2K3DIK7A.js} +5 -1
- package/dist/{chunk-6T6ZAK77.js → chunk-7OAETQU3.js} +1 -1
- package/dist/{chunk-A4EF4M3B.js → chunk-FW7CKARF.js} +1 -1
- package/dist/{chunk-IL7MLYB5.js → chunk-LR4UYJDY.js} +1 -1
- package/dist/{chunk-N7EETHYG.js → chunk-QYZ3GKCP.js} +4 -4
- package/dist/chunk-ZZ6UNKJQ.js +469 -0
- package/dist/cli/index.js +5 -5
- package/dist/cli.cjs +5 -1
- package/dist/cli.js +5 -5
- package/dist/index.cjs +481 -5
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -4
- package/dist/pr-comment.js +3 -3
- package/dist/profile/index.cjs +5 -1
- package/dist/profile/index.js +1 -1
- package/dist/profile-suggestions.js +2 -2
- package/dist/profile.cjs +5 -1
- package/dist/profile.d.cts +2 -0
- package/dist/profile.d.ts +2 -0
- package/dist/profile.js +1 -1
- package/dist/semantic-certificate.cjs +497 -0
- package/dist/semantic-certificate.d.cts +142 -0
- package/dist/semantic-certificate.d.ts +142 -0
- package/dist/semantic-certificate.js +15 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ import { createCaptureDiagnostic } from "@riddledc/riddle-proof/diagnostics";
|
|
|
51
51
|
import { toRiddleProofRunParams } from "@riddledc/riddle-proof/openclaw";
|
|
52
52
|
import { assessRiddleProofChange } from "@riddledc/riddle-proof/change-proof";
|
|
53
53
|
import { createRiddleProofObservationReceipt } from "@riddledc/riddle-proof/receipts";
|
|
54
|
+
import { composeRiddleProofSemanticCertificates } from "@riddledc/riddle-proof/semantic-certificate";
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
The root export provides generic contracts and helpers. Integration-specific
|
|
@@ -77,6 +78,75 @@ Executor (`local_playwright`, `riddle_hosted`, or direct collection), target
|
|
|
77
78
|
identity are independent dimensions. A merge recommendation is evidence-based
|
|
78
79
|
advice; it does not grant shipping authorization.
|
|
79
80
|
|
|
81
|
+
### Experimental Semantic Certificates
|
|
82
|
+
|
|
83
|
+
`riddle-proof.semantic-certificate.v0` is the first runtime surface for
|
|
84
|
+
compressing accepted, scoped observations into claims that another contract or
|
|
85
|
+
agent can inspect and compose. Every certificate has a complete scope
|
|
86
|
+
(`repository`, `revision`, `environment`, `target`, and `proof_attempt`), a
|
|
87
|
+
named and versioned claim, and at least one receipt plus artifact digest.
|
|
88
|
+
|
|
89
|
+
Atomic issuance evaluates an explicit runtime contract. A rejected contract
|
|
90
|
+
produces no certificate:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import {
|
|
94
|
+
createRiddleProofSemanticCertificate,
|
|
95
|
+
composeRiddleProofSemanticCertificates,
|
|
96
|
+
} from "@riddledc/riddle-proof/semantic-certificate";
|
|
97
|
+
|
|
98
|
+
const quiet = createRiddleProofSemanticCertificate({
|
|
99
|
+
scope,
|
|
100
|
+
observation: earlyTraceSample,
|
|
101
|
+
evidence: [{
|
|
102
|
+
receipt_id: "obs_tidepool_early",
|
|
103
|
+
artifact_digest: orderedTraceDigest,
|
|
104
|
+
role: "ordered_trace_witness",
|
|
105
|
+
}],
|
|
106
|
+
contract: {
|
|
107
|
+
contract_id: "tidepool.early-quiescence",
|
|
108
|
+
contract_version: "1",
|
|
109
|
+
label: "The early trace is quiet",
|
|
110
|
+
claim: {
|
|
111
|
+
claim_id: "tidepool.early-quiescence",
|
|
112
|
+
claim_version: "1",
|
|
113
|
+
parameters: { sample_index: 1 },
|
|
114
|
+
label: "The early sample has no collision and no sound",
|
|
115
|
+
},
|
|
116
|
+
accepts: (_scope, sample) => !sample.collision && !sample.sound,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!quiet.ok) throw new Error(quiet.error.message);
|
|
121
|
+
|
|
122
|
+
const behavior = composeRiddleProofSemanticCertificates({
|
|
123
|
+
rule: {
|
|
124
|
+
rule_id: "tidepool.quiet-then-collision",
|
|
125
|
+
rule_version: "1",
|
|
126
|
+
label: "Interpret the two trace facts together",
|
|
127
|
+
premises: [quiet.certificate.claim, later.certificate.claim],
|
|
128
|
+
conclusion: observedWaveCollisionBehaviorClaim,
|
|
129
|
+
},
|
|
130
|
+
certificates: [quiet.certificate, later.certificate],
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Composition requires the declared premise claims and exact scope equality. It
|
|
135
|
+
copies compact premise snapshots into the derivation and sets the higher
|
|
136
|
+
certificate's evidence to the ordered concatenation of premise evidence. The
|
|
137
|
+
snapshots retain whether each input came from a checked runtime contract or a
|
|
138
|
+
declared runtime rule. The parser rechecks those relationships and the
|
|
139
|
+
content-addressed certificate ID. The serialized v0 objects use exact keys and
|
|
140
|
+
reject any `status`, verdict, merge, sync, ready-to-ship, shipping-authority,
|
|
141
|
+
or free-form metadata field: semantic composition is deliberately separate
|
|
142
|
+
from release policy.
|
|
143
|
+
|
|
144
|
+
The boundary remains explicit. Derivations say `runtime_contract_accepted` or
|
|
145
|
+
`declared_runtime_rule`; JavaScript predicates and named rules are inspectable
|
|
146
|
+
runtime models, not Lean proof terms. Certificate hashes are content identities,
|
|
147
|
+
not signatures, and receipt IDs and digests do not authenticate browser, Git,
|
|
148
|
+
CDN, screenshot, or outside-world truth.
|
|
149
|
+
|
|
80
150
|
Persist a deploy result and its immutable receipt for later Change Proof input:
|
|
81
151
|
|
|
82
152
|
```sh
|
|
@@ -309,6 +379,15 @@ or as a stronger proof base before a change loop.
|
|
|
309
379
|
}
|
|
310
380
|
```
|
|
311
381
|
|
|
382
|
+
Viewport entries may set `hasTouch` and `isMobile` when the behavior under
|
|
383
|
+
test depends on a touch-capable browser context rather than dimensions alone.
|
|
384
|
+
The snake-case aliases `has_touch` and `is_mobile` are accepted at profile
|
|
385
|
+
input boundaries. For example:
|
|
386
|
+
|
|
387
|
+
```json
|
|
388
|
+
{ "name": "ipad", "width": 820, "height": 1180, "hasTouch": true, "isMobile": true }
|
|
389
|
+
```
|
|
390
|
+
|
|
312
391
|
For early proof authoring, `profile-suggest` can turn a route plus changed file,
|
|
313
392
|
selector, and changed-text hints into a conservative draft profile:
|
|
314
393
|
|
package/dist/change-proof.js
CHANGED
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
parseRiddleProofHandoffReceipt,
|
|
13
13
|
riddleProofChangeReceiptHtml,
|
|
14
14
|
riddleProofChangeReceiptMarkdown
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-LR4UYJDY.js";
|
|
16
16
|
import "./chunk-MEVVL4TI.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-2K3DIK7A.js";
|
|
18
18
|
import "./chunk-MLKGABMK.js";
|
|
19
19
|
export {
|
|
20
20
|
RIDDLE_PROOF_CHANGE_CONTRACT_VERSION,
|
|
@@ -1144,10 +1144,14 @@ function normalizeViewport(input, index) {
|
|
|
1144
1144
|
if (!width || !height || width < 100 || height < 100) {
|
|
1145
1145
|
throw new Error(`target.viewports[${index}] requires numeric width and height >= 100.`);
|
|
1146
1146
|
}
|
|
1147
|
+
const hasTouch = booleanValue(valueFromOwn(input, "hasTouch", "has_touch"));
|
|
1148
|
+
const isMobile = booleanValue(valueFromOwn(input, "isMobile", "is_mobile"));
|
|
1147
1149
|
return {
|
|
1148
1150
|
name: normalizeName(input.name || input.label, `viewport-${index + 1}`),
|
|
1149
1151
|
width: Math.round(width),
|
|
1150
|
-
height: Math.round(height)
|
|
1152
|
+
height: Math.round(height),
|
|
1153
|
+
...hasTouch === void 0 ? {} : { hasTouch },
|
|
1154
|
+
...isMobile === void 0 ? {} : { isMobile }
|
|
1151
1155
|
};
|
|
1152
1156
|
}
|
|
1153
1157
|
function normalizeViewports(value) {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
createRiddleProofHandoffReceipt,
|
|
6
6
|
parseRiddleProofChangeReceipt,
|
|
7
7
|
parseRiddleProofHandoffReceipt
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LR4UYJDY.js";
|
|
9
9
|
import {
|
|
10
10
|
riddleProofPublicStateAllowsMergeRecommendation,
|
|
11
11
|
riddleProofPublicStateMergeRecommendation,
|
|
@@ -8,17 +8,17 @@ import {
|
|
|
8
8
|
RIDDLE_PROOF_PR_COMMENT_MARKER,
|
|
9
9
|
buildRiddleProofPrCommentMarkdown,
|
|
10
10
|
summarizeRiddleProofPrComment
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-7OAETQU3.js";
|
|
12
12
|
import {
|
|
13
13
|
suggestRiddleProofProfileChecks
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-FW7CKARF.js";
|
|
15
15
|
import {
|
|
16
16
|
assessRiddleProofChange,
|
|
17
17
|
createRiddleProofChangeReceipt,
|
|
18
18
|
createRiddleProofHandoffReceipt,
|
|
19
19
|
riddleProofChangeReceiptHtml,
|
|
20
20
|
riddleProofChangeReceiptMarkdown
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-LR4UYJDY.js";
|
|
22
22
|
import {
|
|
23
23
|
createRiddleProofObservationReceipt,
|
|
24
24
|
parseRiddlePreviewReceipt,
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
profileStatusExitCode,
|
|
42
42
|
resolveRiddleProofProfileTargetUrl,
|
|
43
43
|
resolveRiddleProofProfileTimeoutSec
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-2K3DIK7A.js";
|
|
45
45
|
import {
|
|
46
46
|
createCodexExecAgentAdapter,
|
|
47
47
|
runCodexExecAgentDoctor
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
// src/semantic-certificate.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
var RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION = "riddle-proof.semantic-certificate.v0";
|
|
4
|
+
var SCOPE_FIELDS = [
|
|
5
|
+
"repository",
|
|
6
|
+
"revision",
|
|
7
|
+
"environment",
|
|
8
|
+
"target",
|
|
9
|
+
"proof_attempt"
|
|
10
|
+
];
|
|
11
|
+
var AUTHORITY_FIELDS = [
|
|
12
|
+
"authority",
|
|
13
|
+
"status",
|
|
14
|
+
"verdict",
|
|
15
|
+
"ready_to_ship",
|
|
16
|
+
"merge_ready",
|
|
17
|
+
"sync_allowed",
|
|
18
|
+
"ship_authorized",
|
|
19
|
+
"shipping_authorized",
|
|
20
|
+
"shipping_disabled",
|
|
21
|
+
"merge_recommended",
|
|
22
|
+
"merge_recommendation",
|
|
23
|
+
"shipping_authorization"
|
|
24
|
+
];
|
|
25
|
+
var CERTIFICATE_FIELDS = /* @__PURE__ */ new Set([
|
|
26
|
+
"version",
|
|
27
|
+
"certificate_id",
|
|
28
|
+
"scope",
|
|
29
|
+
"claim",
|
|
30
|
+
"evidence",
|
|
31
|
+
"derivation",
|
|
32
|
+
"issued_at"
|
|
33
|
+
]);
|
|
34
|
+
function isRecord(value) {
|
|
35
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
function assertOnlyKeys(record, allowed, context) {
|
|
38
|
+
const allowedSet = new Set(allowed);
|
|
39
|
+
for (const key of Object.keys(record)) {
|
|
40
|
+
if (!allowedSet.has(key)) {
|
|
41
|
+
throw new Error(`${context} contains unsupported field ${key}.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function requiredString(record, key, context) {
|
|
46
|
+
const value = record[key];
|
|
47
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
48
|
+
throw new Error(`${context}.${key} must be a non-empty string.`);
|
|
49
|
+
}
|
|
50
|
+
return value.trim();
|
|
51
|
+
}
|
|
52
|
+
function optionalString(record, key, context) {
|
|
53
|
+
if (record[key] === void 0) return void 0;
|
|
54
|
+
return requiredString(record, key, context);
|
|
55
|
+
}
|
|
56
|
+
function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
57
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
if (ancestors.has(value)) return false;
|
|
63
|
+
ancestors.add(value);
|
|
64
|
+
const valid2 = value.every((entry) => isJsonValue(entry, ancestors));
|
|
65
|
+
ancestors.delete(value);
|
|
66
|
+
return valid2;
|
|
67
|
+
}
|
|
68
|
+
if (!isRecord(value)) return false;
|
|
69
|
+
const prototype = Object.getPrototypeOf(value);
|
|
70
|
+
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
71
|
+
if (ancestors.has(value)) return false;
|
|
72
|
+
ancestors.add(value);
|
|
73
|
+
const valid = Object.values(value).every((entry) => isJsonValue(entry, ancestors));
|
|
74
|
+
ancestors.delete(value);
|
|
75
|
+
return valid;
|
|
76
|
+
}
|
|
77
|
+
function parseJsonObject(value, context) {
|
|
78
|
+
if (value === void 0) return void 0;
|
|
79
|
+
if (!isRecord(value) || !isJsonValue(value)) {
|
|
80
|
+
throw new Error(`${context} must be a JSON object.`);
|
|
81
|
+
}
|
|
82
|
+
const cloned = JSON.parse(JSON.stringify(value));
|
|
83
|
+
if (!isRecord(cloned) || !isJsonValue(cloned)) {
|
|
84
|
+
throw new Error(`${context} must remain a JSON object when serialized.`);
|
|
85
|
+
}
|
|
86
|
+
return cloned;
|
|
87
|
+
}
|
|
88
|
+
function parseIssuedAt(value, context) {
|
|
89
|
+
if (typeof value !== "string" || !value.trim() || !Number.isFinite(Date.parse(value))) {
|
|
90
|
+
throw new Error(`${context} must be a valid timestamp.`);
|
|
91
|
+
}
|
|
92
|
+
return value.trim();
|
|
93
|
+
}
|
|
94
|
+
function parseScope(value, context) {
|
|
95
|
+
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
96
|
+
assertOnlyKeys(value, SCOPE_FIELDS, context);
|
|
97
|
+
return {
|
|
98
|
+
repository: requiredString(value, "repository", context),
|
|
99
|
+
revision: requiredString(value, "revision", context),
|
|
100
|
+
environment: requiredString(value, "environment", context),
|
|
101
|
+
target: requiredString(value, "target", context),
|
|
102
|
+
proof_attempt: requiredString(value, "proof_attempt", context)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function parseClaimRef(value, context, allowedExtras = []) {
|
|
106
|
+
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
107
|
+
assertOnlyKeys(value, ["claim_id", "claim_version", "parameters", ...allowedExtras], context);
|
|
108
|
+
const parameters = parseJsonObject(value.parameters, `${context}.parameters`);
|
|
109
|
+
return {
|
|
110
|
+
claim_id: requiredString(value, "claim_id", context),
|
|
111
|
+
claim_version: requiredString(value, "claim_version", context),
|
|
112
|
+
...parameters ? { parameters } : {}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function parseClaim(value, context) {
|
|
116
|
+
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
117
|
+
return {
|
|
118
|
+
...parseClaimRef(value, context, ["label"]),
|
|
119
|
+
label: requiredString(value, "label", context)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function parseEvidenceRef(value, context) {
|
|
123
|
+
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
124
|
+
assertOnlyKeys(
|
|
125
|
+
value,
|
|
126
|
+
["receipt_id", "artifact_digest", "role", "artifact_url", "artifact_path"],
|
|
127
|
+
context
|
|
128
|
+
);
|
|
129
|
+
const artifactDigest = requiredString(value, "artifact_digest", context).toLowerCase();
|
|
130
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(artifactDigest)) {
|
|
131
|
+
throw new Error(`${context}.artifact_digest must be a full sha256 digest.`);
|
|
132
|
+
}
|
|
133
|
+
const artifactUrl = optionalString(value, "artifact_url", context);
|
|
134
|
+
const artifactPath = optionalString(value, "artifact_path", context);
|
|
135
|
+
return {
|
|
136
|
+
receipt_id: requiredString(value, "receipt_id", context),
|
|
137
|
+
artifact_digest: artifactDigest,
|
|
138
|
+
role: requiredString(value, "role", context),
|
|
139
|
+
...artifactUrl ? { artifact_url: artifactUrl } : {},
|
|
140
|
+
...artifactPath ? { artifact_path: artifactPath } : {}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function parseEvidenceBundle(value, context) {
|
|
144
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
145
|
+
throw new Error(`${context} must contain at least one evidence reference.`);
|
|
146
|
+
}
|
|
147
|
+
return value.map((entry, index) => parseEvidenceRef(entry, `${context}[${index}]`));
|
|
148
|
+
}
|
|
149
|
+
function parseContractRef(value, context) {
|
|
150
|
+
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
151
|
+
return {
|
|
152
|
+
contract_id: requiredString(value, "contract_id", context),
|
|
153
|
+
contract_version: requiredString(value, "contract_version", context),
|
|
154
|
+
label: requiredString(value, "label", context)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function parseContract(value, context, allowRuntimePredicate = false) {
|
|
158
|
+
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
159
|
+
assertOnlyKeys(
|
|
160
|
+
value,
|
|
161
|
+
["contract_id", "contract_version", "label", "claim", ...allowRuntimePredicate ? ["accepts"] : []],
|
|
162
|
+
context
|
|
163
|
+
);
|
|
164
|
+
if (allowRuntimePredicate && typeof value.accepts !== "function") {
|
|
165
|
+
throw new Error(`${context}.accepts must be a function.`);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
...parseContractRef(value, context),
|
|
169
|
+
claim: parseClaim(value.claim, `${context}.claim`)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function parseRule(value, context, allowFullPremises = false) {
|
|
173
|
+
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
174
|
+
assertOnlyKeys(value, ["rule_id", "rule_version", "label", "premises", "conclusion"], context);
|
|
175
|
+
if (!Array.isArray(value.premises) || value.premises.length === 0) {
|
|
176
|
+
throw new Error(`${context}.premises must contain at least one claim reference.`);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
rule_id: requiredString(value, "rule_id", context),
|
|
180
|
+
rule_version: requiredString(value, "rule_version", context),
|
|
181
|
+
label: requiredString(value, "label", context),
|
|
182
|
+
premises: value.premises.map((premise, index) => parseClaimRef(
|
|
183
|
+
premise,
|
|
184
|
+
`${context}.premises[${index}]`,
|
|
185
|
+
allowFullPremises ? ["label"] : []
|
|
186
|
+
)),
|
|
187
|
+
conclusion: parseClaim(value.conclusion, `${context}.conclusion`)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function parsePremise(value, context) {
|
|
191
|
+
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
192
|
+
assertOnlyKeys(
|
|
193
|
+
value,
|
|
194
|
+
["certificate_id", "derivation_kind", "assurance", "scope", "claim", "evidence"],
|
|
195
|
+
context
|
|
196
|
+
);
|
|
197
|
+
const derivationKind = requiredString(value, "derivation_kind", context);
|
|
198
|
+
const assurance = requiredString(value, "assurance", context);
|
|
199
|
+
const validAssurance = derivationKind === "contract" && assurance === "runtime_contract_accepted" || derivationKind === "composition" && assurance === "declared_runtime_rule";
|
|
200
|
+
if (!validAssurance) {
|
|
201
|
+
throw new Error(`${context} must preserve a valid derivation_kind and assurance pair.`);
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
certificate_id: requiredString(value, "certificate_id", context),
|
|
205
|
+
derivation_kind: derivationKind,
|
|
206
|
+
assurance,
|
|
207
|
+
scope: parseScope(value.scope, `${context}.scope`),
|
|
208
|
+
claim: parseClaim(value.claim, `${context}.claim`),
|
|
209
|
+
evidence: parseEvidenceBundle(value.evidence, `${context}.evidence`)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function stableJson(value) {
|
|
213
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
214
|
+
if (isRecord(value)) {
|
|
215
|
+
return `{${Object.keys(value).filter((key) => value[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
216
|
+
}
|
|
217
|
+
const encoded = JSON.stringify(value);
|
|
218
|
+
if (encoded === void 0) throw new Error("Semantic certificate contains a non-JSON value.");
|
|
219
|
+
return encoded;
|
|
220
|
+
}
|
|
221
|
+
function sameClaimRef(left, right) {
|
|
222
|
+
return left.claim_id === right.claim_id && left.claim_version === right.claim_version && stableJson(left.parameters || {}) === stableJson(right.parameters || {});
|
|
223
|
+
}
|
|
224
|
+
function sameEvidence(left, right) {
|
|
225
|
+
return stableJson(left) === stableJson(right);
|
|
226
|
+
}
|
|
227
|
+
function certificateId(body) {
|
|
228
|
+
const digest = createHash("sha256").update(stableJson(body)).digest("hex");
|
|
229
|
+
return `rpsc_${digest}`;
|
|
230
|
+
}
|
|
231
|
+
function withCertificateId(body) {
|
|
232
|
+
return { ...body, certificate_id: certificateId(body) };
|
|
233
|
+
}
|
|
234
|
+
function parseDerivation(value, context) {
|
|
235
|
+
if (!isRecord(value)) throw new Error(`${context} must be an object.`);
|
|
236
|
+
const kind = requiredString(value, "kind", context);
|
|
237
|
+
if (kind === "contract") {
|
|
238
|
+
assertOnlyKeys(value, ["kind", "assurance", "contract"], context);
|
|
239
|
+
if (value.assurance !== "runtime_contract_accepted") {
|
|
240
|
+
throw new Error(`${context}.assurance must be runtime_contract_accepted.`);
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
kind,
|
|
244
|
+
assurance: "runtime_contract_accepted",
|
|
245
|
+
contract: parseContract(value.contract, `${context}.contract`)
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (kind === "composition") {
|
|
249
|
+
assertOnlyKeys(value, ["kind", "assurance", "rule", "premises"], context);
|
|
250
|
+
if (value.assurance !== "declared_runtime_rule") {
|
|
251
|
+
throw new Error(`${context}.assurance must be declared_runtime_rule.`);
|
|
252
|
+
}
|
|
253
|
+
if (!Array.isArray(value.premises) || value.premises.length === 0) {
|
|
254
|
+
throw new Error(`${context}.premises must contain at least one certificate premise.`);
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
kind,
|
|
258
|
+
assurance: "declared_runtime_rule",
|
|
259
|
+
rule: parseRule(value.rule, `${context}.rule`),
|
|
260
|
+
premises: value.premises.map((premise, index) => parsePremise(premise, `${context}.premises[${index}]`))
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
throw new Error(`${context}.kind must be contract or composition.`);
|
|
264
|
+
}
|
|
265
|
+
function riddleProofSemanticScopesEqual(left, right) {
|
|
266
|
+
return SCOPE_FIELDS.every((field) => left[field] === right[field]);
|
|
267
|
+
}
|
|
268
|
+
function createRiddleProofSemanticCertificate(input) {
|
|
269
|
+
if (!isRecord(input)) throw new Error("Semantic certificate input must be an object.");
|
|
270
|
+
assertOnlyKeys(
|
|
271
|
+
input,
|
|
272
|
+
["scope", "evidence", "observation", "contract", "issued_at"],
|
|
273
|
+
"semantic certificate input"
|
|
274
|
+
);
|
|
275
|
+
const scope = parseScope(input.scope, "semantic certificate scope");
|
|
276
|
+
const evidence = parseEvidenceBundle(input.evidence, "semantic certificate evidence");
|
|
277
|
+
const contract = parseContract(input.contract, "semantic certificate contract", true);
|
|
278
|
+
const contractRef = {
|
|
279
|
+
contract_id: contract.contract_id,
|
|
280
|
+
contract_version: contract.contract_version,
|
|
281
|
+
label: contract.label
|
|
282
|
+
};
|
|
283
|
+
const claim = contract.claim;
|
|
284
|
+
let accepted;
|
|
285
|
+
try {
|
|
286
|
+
accepted = input.contract.accepts({ ...scope }, input.observation) === true;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
error: {
|
|
291
|
+
code: "contract_error",
|
|
292
|
+
contract: contractRef,
|
|
293
|
+
message: `Semantic contract evaluation failed: ${error instanceof Error ? error.message : String(error)}.`
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (!accepted) {
|
|
298
|
+
return {
|
|
299
|
+
ok: false,
|
|
300
|
+
error: {
|
|
301
|
+
code: "contract_rejected",
|
|
302
|
+
contract: contractRef,
|
|
303
|
+
message: "Semantic contract rejected the supplied observation at this scope."
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const body = {
|
|
308
|
+
version: RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION,
|
|
309
|
+
scope,
|
|
310
|
+
claim,
|
|
311
|
+
evidence,
|
|
312
|
+
derivation: { kind: "contract", assurance: "runtime_contract_accepted", contract },
|
|
313
|
+
issued_at: parseIssuedAt(input.issued_at || (/* @__PURE__ */ new Date()).toISOString(), "semantic certificate issued_at")
|
|
314
|
+
};
|
|
315
|
+
return { ok: true, certificate: withCertificateId(body) };
|
|
316
|
+
}
|
|
317
|
+
function parseRiddleProofSemanticCertificate(value) {
|
|
318
|
+
if (!isRecord(value)) throw new Error("Semantic certificate must be an object.");
|
|
319
|
+
if (value.version !== RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION) {
|
|
320
|
+
throw new Error(`Unsupported Semantic certificate version ${String(value.version || "missing")}.`);
|
|
321
|
+
}
|
|
322
|
+
for (const field of AUTHORITY_FIELDS) {
|
|
323
|
+
if (Object.prototype.hasOwnProperty.call(value, field)) {
|
|
324
|
+
throw new Error(`Semantic certificate must not contain authority field ${field}.`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
for (const field of Object.keys(value)) {
|
|
328
|
+
if (!CERTIFICATE_FIELDS.has(field)) {
|
|
329
|
+
throw new Error(`Semantic certificate contains unsupported field ${field}.`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const scope = parseScope(value.scope, "semantic certificate scope");
|
|
333
|
+
const claim = parseClaim(value.claim, "semantic certificate claim");
|
|
334
|
+
const evidence = parseEvidenceBundle(value.evidence, "semantic certificate evidence");
|
|
335
|
+
const derivation = parseDerivation(value.derivation, "semantic certificate derivation");
|
|
336
|
+
if (derivation.kind === "contract" && !sameClaimRef(claim, derivation.contract.claim)) {
|
|
337
|
+
throw new Error("Semantic contract-derived claim must match its contract claim.");
|
|
338
|
+
}
|
|
339
|
+
if (derivation.kind === "composition") {
|
|
340
|
+
if (derivation.premises.length !== derivation.rule.premises.length) {
|
|
341
|
+
throw new Error("Semantic composition premises must match the rule premise count.");
|
|
342
|
+
}
|
|
343
|
+
derivation.premises.forEach((premise, index) => {
|
|
344
|
+
if (!riddleProofSemanticScopesEqual(scope, premise.scope)) {
|
|
345
|
+
throw new Error(`Semantic composition premise ${index} must have the certificate scope.`);
|
|
346
|
+
}
|
|
347
|
+
if (!sameClaimRef(premise.claim, derivation.rule.premises[index])) {
|
|
348
|
+
throw new Error(`Semantic composition premise ${index} must match its rule claim.`);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
if (!sameClaimRef(claim, derivation.rule.conclusion)) {
|
|
352
|
+
throw new Error("Semantic composition claim must match its rule conclusion.");
|
|
353
|
+
}
|
|
354
|
+
const expectedEvidence = derivation.premises.flatMap((premise) => premise.evidence);
|
|
355
|
+
if (!sameEvidence(evidence, expectedEvidence)) {
|
|
356
|
+
throw new Error("Semantic composition evidence must be the ordered concatenation of premise evidence.");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const body = {
|
|
360
|
+
version: RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION,
|
|
361
|
+
scope,
|
|
362
|
+
claim,
|
|
363
|
+
evidence,
|
|
364
|
+
derivation,
|
|
365
|
+
issued_at: parseIssuedAt(value.issued_at, "semantic certificate issued_at")
|
|
366
|
+
};
|
|
367
|
+
const observedId = requiredString(value, "certificate_id", "semantic certificate");
|
|
368
|
+
const expectedId = certificateId(body);
|
|
369
|
+
if (observedId !== expectedId) {
|
|
370
|
+
throw new Error("Semantic certificate_id must match its content.");
|
|
371
|
+
}
|
|
372
|
+
return { ...body, certificate_id: observedId };
|
|
373
|
+
}
|
|
374
|
+
function firstScopeMismatch(expected, observed) {
|
|
375
|
+
for (const field of SCOPE_FIELDS) {
|
|
376
|
+
if (expected[field] !== observed[field]) {
|
|
377
|
+
return { field, expected: expected[field], observed: observed[field] };
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return void 0;
|
|
381
|
+
}
|
|
382
|
+
function premiseFromCertificate(certificate) {
|
|
383
|
+
return {
|
|
384
|
+
certificate_id: certificate.certificate_id,
|
|
385
|
+
derivation_kind: certificate.derivation.kind,
|
|
386
|
+
assurance: certificate.derivation.assurance,
|
|
387
|
+
scope: { ...certificate.scope },
|
|
388
|
+
claim: parseClaim(certificate.claim, "semantic composition premise claim"),
|
|
389
|
+
evidence: certificate.evidence.map((entry) => ({ ...entry }))
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function composeRiddleProofSemanticCertificates(input) {
|
|
393
|
+
if (!isRecord(input)) throw new Error("Semantic composition input must be an object.");
|
|
394
|
+
assertOnlyKeys(
|
|
395
|
+
input,
|
|
396
|
+
["rule", "certificates", "issued_at"],
|
|
397
|
+
"semantic composition input"
|
|
398
|
+
);
|
|
399
|
+
const rule = parseRule(input.rule, "semantic composition rule", true);
|
|
400
|
+
if (!Array.isArray(input.certificates) || input.certificates.length === 0) {
|
|
401
|
+
throw new Error("Semantic composition requires at least one certificate.");
|
|
402
|
+
}
|
|
403
|
+
const certificates = input.certificates.map((certificate) => parseRiddleProofSemanticCertificate(certificate));
|
|
404
|
+
if (certificates.length !== rule.premises.length) {
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
error: {
|
|
408
|
+
code: "premise_count_mismatch",
|
|
409
|
+
expected: rule.premises.length,
|
|
410
|
+
observed: certificates.length,
|
|
411
|
+
message: `Semantic rule expected ${rule.premises.length} certificate(s), received ${certificates.length}.`
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
const expectedScope = certificates[0].scope;
|
|
416
|
+
for (let index = 1; index < certificates.length; index += 1) {
|
|
417
|
+
const mismatch = firstScopeMismatch(expectedScope, certificates[index].scope);
|
|
418
|
+
if (mismatch) {
|
|
419
|
+
return {
|
|
420
|
+
ok: false,
|
|
421
|
+
error: {
|
|
422
|
+
code: "scope_mismatch",
|
|
423
|
+
input_index: index,
|
|
424
|
+
...mismatch,
|
|
425
|
+
message: `Semantic certificate ${index} has a different ${mismatch.field}.`
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
for (let index = 0; index < certificates.length; index += 1) {
|
|
431
|
+
const expected = rule.premises[index];
|
|
432
|
+
const observed = certificates[index].claim;
|
|
433
|
+
if (!sameClaimRef(expected, observed)) {
|
|
434
|
+
return {
|
|
435
|
+
ok: false,
|
|
436
|
+
error: {
|
|
437
|
+
code: "premise_mismatch",
|
|
438
|
+
input_index: index,
|
|
439
|
+
expected,
|
|
440
|
+
observed: parseClaimRef(observed, "semantic composition observed claim", ["label"]),
|
|
441
|
+
message: `Semantic certificate ${index} does not satisfy its declared rule premise.`
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const evidence = certificates.flatMap((certificate) => certificate.evidence);
|
|
447
|
+
const body = {
|
|
448
|
+
version: RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION,
|
|
449
|
+
scope: { ...expectedScope },
|
|
450
|
+
claim: { ...rule.conclusion },
|
|
451
|
+
evidence,
|
|
452
|
+
derivation: {
|
|
453
|
+
kind: "composition",
|
|
454
|
+
assurance: "declared_runtime_rule",
|
|
455
|
+
rule,
|
|
456
|
+
premises: certificates.map(premiseFromCertificate)
|
|
457
|
+
},
|
|
458
|
+
issued_at: parseIssuedAt(input.issued_at || (/* @__PURE__ */ new Date()).toISOString(), "semantic certificate issued_at")
|
|
459
|
+
};
|
|
460
|
+
return { ok: true, certificate: withCertificateId(body) };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export {
|
|
464
|
+
RIDDLE_PROOF_SEMANTIC_CERTIFICATE_VERSION,
|
|
465
|
+
riddleProofSemanticScopesEqual,
|
|
466
|
+
createRiddleProofSemanticCertificate,
|
|
467
|
+
parseRiddleProofSemanticCertificate,
|
|
468
|
+
composeRiddleProofSemanticCertificates
|
|
469
|
+
};
|
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import "../chunk-
|
|
1
|
+
import "../chunk-QYZ3GKCP.js";
|
|
2
2
|
import "../chunk-5IFZSUPF.js";
|
|
3
3
|
import "../chunk-JFQXAJH2.js";
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
4
|
+
import "../chunk-7OAETQU3.js";
|
|
5
|
+
import "../chunk-FW7CKARF.js";
|
|
6
|
+
import "../chunk-LR4UYJDY.js";
|
|
7
7
|
import "../chunk-MEVVL4TI.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-2K3DIK7A.js";
|
|
9
9
|
import "../chunk-KXLEN4SA.js";
|
|
10
10
|
import "../chunk-WLUMLHII.js";
|
|
11
11
|
import "../chunk-CGJX7LJO.js";
|