@qijenchen/governance 0.1.0-beta.94

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 (40) hide show
  1. package/README.md +89 -0
  2. package/bin/attest.mjs +4 -0
  3. package/bin/check.mjs +4 -0
  4. package/bin/doctor.mjs +4 -0
  5. package/bin/generate.mjs +4 -0
  6. package/bin/governance.mjs +4 -0
  7. package/bin/hook.mjs +4 -0
  8. package/bin/upgrade.mjs +4 -0
  9. package/canonical/gates.json +42 -0
  10. package/canonical/manifest.json +476 -0
  11. package/canonical/plugin-aliases.json +25 -0
  12. package/canonical/provider-lifecycle.json +48 -0
  13. package/canonical/providers.json +455 -0
  14. package/canonical/roles.json +12 -0
  15. package/canonical/rules.json +81 -0
  16. package/canonical/schemas/attestation.schema.json +61 -0
  17. package/canonical/schemas/diagnostic.schema.json +37 -0
  18. package/canonical/schemas/gates.schema.json +27 -0
  19. package/canonical/schemas/lock.schema.json +189 -0
  20. package/canonical/schemas/manifest.schema.json +103 -0
  21. package/canonical/schemas/plugin-aliases.schema.json +59 -0
  22. package/canonical/schemas/provider-hook-coverage.schema.json +173 -0
  23. package/canonical/schemas/provider-lifecycle.schema.json +126 -0
  24. package/canonical/schemas/providers.schema.json +917 -0
  25. package/canonical/schemas/roles.schema.json +27 -0
  26. package/canonical/schemas/rules.schema.json +46 -0
  27. package/canonical/schemas/upgrade-plan.schema.json +31 -0
  28. package/package.json +42 -0
  29. package/src/authority-decision-evidence.mjs +413 -0
  30. package/src/canonical-order.mjs +8 -0
  31. package/src/carrier-projection.mjs +407 -0
  32. package/src/cli.mjs +114 -0
  33. package/src/closed-tool-execution.mjs +1001 -0
  34. package/src/common.mjs +278 -0
  35. package/src/contract.mjs +760 -0
  36. package/src/hook-api.mjs +107 -0
  37. package/src/index.mjs +14 -0
  38. package/src/provider-hook-normalization.mjs +1646 -0
  39. package/src/provider-review-binding.mjs +2377 -0
  40. package/src/snapshot.mjs +520 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://qijenchen.dev/schemas/governance/roles-v1.json",
4
+ "title": "Governance role registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "roles"],
8
+ "properties": {
9
+ "$schema": { "type": "string" },
10
+ "schemaVersion": { "const": 1 },
11
+ "roles": {
12
+ "type": "array",
13
+ "minItems": 1,
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["id", "providerSelection", "requiredGateIds", "requiredSourceIds"],
18
+ "properties": {
19
+ "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
20
+ "providerSelection": { "const": "all-registered" },
21
+ "requiredGateIds": { "type": "array", "minItems": 1, "items": { "type": "string" } },
22
+ "requiredSourceIds": { "type": "array", "minItems": 1, "items": { "type": "string" } }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://qijenchen.dev/schemas/governance/rules-v1.json",
4
+ "title": "Governance rule registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "rules"],
8
+ "properties": {
9
+ "$schema": { "type": "string" },
10
+ "schemaVersion": { "const": 1 },
11
+ "rules": {
12
+ "type": "array",
13
+ "minItems": 1,
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["ruleId", "severity", "source", "appliesToRoles", "match", "blocking", "providerCoverage", "gateIds"],
18
+ "properties": {
19
+ "ruleId": { "type": "string", "pattern": "^GOV-[A-Z]+-[0-9]{3}$" },
20
+ "severity": { "enum": ["critical", "major", "minor", "info"] },
21
+ "source": { "type": "string", "minLength": 1 },
22
+ "appliesToRoles": { "type": "array", "minItems": 1, "items": { "type": "string" } },
23
+ "match": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["kind"],
27
+ "properties": {
28
+ "kind": { "enum": ["protected-generated-path", "registry-invariant"] },
29
+ "pathTemplate": { "type": "string" }
30
+ }
31
+ },
32
+ "blocking": { "type": "boolean" },
33
+ "providerCoverage": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "required": ["*"],
37
+ "properties": {
38
+ "*": { "enum": ["native", "adapter", "hard-gate", "unsupported"] }
39
+ }
40
+ },
41
+ "gateIds": { "type": "array", "minItems": 1, "items": { "type": "string" } }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://qijenchen.dev/schemas/governance-upgrade-plan.schema.json",
4
+ "title": "Governance upgrade plan",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "command", "role", "outputDirectory", "lockFile", "contractDigest", "currentDigest", "expectedDigest", "actions"],
7
+ "properties": {
8
+ "schemaVersion": { "const": 1 },
9
+ "command": { "const": "upgrade.plan" },
10
+ "role": { "type": "string" },
11
+ "outputDirectory": { "type": "string" },
12
+ "lockFile": { "type": "string" },
13
+ "contractDigest": { "$ref": "#/$defs/digest" },
14
+ "currentDigest": { "$ref": "#/$defs/digest" },
15
+ "expectedDigest": { "$ref": "#/$defs/digest" },
16
+ "actions": {
17
+ "type": "array",
18
+ "items": {
19
+ "type": "object",
20
+ "required": ["action", "path"],
21
+ "properties": {
22
+ "action": { "enum": ["add", "remove", "replace-content", "replace-mode", "replace-type"] },
23
+ "path": { "type": "string" }
24
+ },
25
+ "additionalProperties": false
26
+ }
27
+ }
28
+ },
29
+ "$defs": { "digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" } },
30
+ "additionalProperties": false
31
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@qijenchen/governance",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/ajenchen/design-system.git"
6
+ },
7
+ "version": "0.1.0-beta.94",
8
+ "description": "Provider-neutral DS-author control plane for deterministic snapshots, adapters, checks, and evidence manifests.",
9
+ "type": "module",
10
+ "license": "UNLICENSED",
11
+ "engines": {
12
+ "node": ">=22"
13
+ },
14
+ "files": [
15
+ "bin",
16
+ "canonical",
17
+ "src",
18
+ "README.md"
19
+ ],
20
+ "bin": {
21
+ "qijenchen-governance": "./bin/governance.mjs",
22
+ "qijenchen-governance-generate": "./bin/generate.mjs",
23
+ "qijenchen-governance-check": "./bin/check.mjs",
24
+ "qijenchen-governance-doctor": "./bin/doctor.mjs",
25
+ "qijenchen-governance-hook": "./bin/hook.mjs",
26
+ "qijenchen-governance-attest": "./bin/attest.mjs",
27
+ "qijenchen-governance-upgrade": "./bin/upgrade.mjs"
28
+ },
29
+ "exports": {
30
+ ".": "./src/index.mjs",
31
+ "./closed-tool-execution": "./src/closed-tool-execution.mjs",
32
+ "./hook": "./src/hook-api.mjs",
33
+ "./package.json": "./package.json"
34
+ },
35
+ "scripts": {
36
+ "test": "node --test test/*.test.mjs"
37
+ },
38
+ "dependencies": {
39
+ "ajv": "8.20.0",
40
+ "ajv-formats": "3.0.1"
41
+ }
42
+ }
@@ -0,0 +1,413 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { createHash } from 'node:crypto'
3
+
4
+ export const AUTHORITY_DECISION_RECEIPT_KIND = 'provider-neutral-authority-decision-receipt'
5
+ export const AUTHORITY_DECISION_RECEIPT_SCHEMA_VERSION = 2
6
+ export const AUTHORITY_DECISION_POLICY_ID = 'canonical-decision-authority-v1'
7
+ export const AUTHORITY_DECISION_CLASSIFIER_ID = 'approval-evidence-v1'
8
+
9
+ const AUTHORITY_START = '<!-- canonical-decision-authority:start -->'
10
+ const AUTHORITY_END = '<!-- canonical-decision-authority:end -->'
11
+ const SHA256 = /^sha256:[a-f0-9]{64}$/
12
+ const PROVIDER_ID = /^[a-z][a-z0-9-]*$/
13
+ const RUNTIME_SURFACE = /^[a-z0-9][a-z0-9._/-]{0,255}$/
14
+ const MAX_TARGET_BYTES = 16 * 1024
15
+ const MAX_OPERATION_BYTES = 16 * 1024 * 1024
16
+ const MAX_USER_MESSAGE_BYTES = 4 * 1024 * 1024
17
+ const MAX_USER_MESSAGES_BYTES = 16 * 1024 * 1024
18
+ const MAX_USER_MESSAGE_COUNT = 4096
19
+ const MAX_SCOPE_VALUE_BYTES = 4096
20
+
21
+ function canonicalize(value) {
22
+ if (Array.isArray(value)) return value.map(canonicalize)
23
+ if (value && typeof value === 'object') {
24
+ return Object.fromEntries(
25
+ Object.keys(value)
26
+ .filter((key) => value[key] !== undefined)
27
+ .sort((left, right) => Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8')))
28
+ .map((key) => [key, canonicalize(value[key])]),
29
+ )
30
+ }
31
+ return value
32
+ }
33
+
34
+ function stableJson(value) {
35
+ return `${JSON.stringify(canonicalize(value), null, 2)}\n`
36
+ }
37
+
38
+ function digest(value) {
39
+ const bytes = Buffer.isBuffer(value)
40
+ ? value
41
+ : Buffer.from(typeof value === 'string' ? value : stableJson(value))
42
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
43
+ }
44
+
45
+ const RECEIPT_KEYS = Object.freeze([
46
+ 'binding',
47
+ 'classification',
48
+ 'classificationMode',
49
+ 'kind',
50
+ 'receiptDigest',
51
+ 'schemaVersion',
52
+ 'source',
53
+ 'trust',
54
+ ])
55
+ const BINDING_KEYS = Object.freeze([
56
+ 'operationEvidenceDigest',
57
+ 'providerId',
58
+ 'runtimeSurface',
59
+ 'scopeNonceDigest',
60
+ 'sessionDigest',
61
+ 'target',
62
+ 'userMessagesDigest',
63
+ ])
64
+ const SOURCE_KEYS = Object.freeze([
65
+ 'authorityPolicy',
66
+ 'authorityPolicyDigest',
67
+ 'classifier',
68
+ 'classifierDigest',
69
+ 'providerRegistry',
70
+ 'providerRegistryDigest',
71
+ 'receiptContract',
72
+ 'receiptContractDigest',
73
+ ])
74
+ const TRUST_KEYS = Object.freeze([
75
+ 'contentAddressed',
76
+ 'promotionEligible',
77
+ 'rawAuthorityTextStored',
78
+ 'rawOperationTextStored',
79
+ 'runtimeCertification',
80
+ ])
81
+ const CLASSIFICATION_KEYS = Object.freeze([
82
+ 'allowedScopes',
83
+ 'decision',
84
+ 'decisionDomain',
85
+ 'decisionMessageSha256',
86
+ 'deniedOrAmbiguousScopes',
87
+ 'humanOnlyScopes',
88
+ 'latestUserMessageSha256',
89
+ 'operationEvidenceSha256',
90
+ 'reasonCode',
91
+ 'target',
92
+ 'targetBinding',
93
+ ])
94
+ const CLASSIFICATION_SCOPE_IDS = new Set([
95
+ 'account-holder-platform-action',
96
+ 'approved-ui-ux-implementation',
97
+ 'consumer-template-adoption',
98
+ 'credential-reference-required',
99
+ 'current-target-operation',
100
+ 'engineering-execution',
101
+ 'external-activation',
102
+ 'github-configuration',
103
+ 'legal-account-organization-business-decision',
104
+ 'mechanical-approved-ssot-projection',
105
+ 'package-release',
106
+ 'plan-external-spend',
107
+ 'product-ui-ux-change',
108
+ 'product-ui-ux-decision',
109
+ 'protected-git-delivery',
110
+ 'rollback-recovery',
111
+ 'rollout',
112
+ 'runtime-certification',
113
+ 'testing-and-harness',
114
+ ])
115
+
116
+ export class AuthorityDecisionEvidenceError extends Error {
117
+ constructor(code, detail) {
118
+ super(`authority decision evidence blocked:${code}:${detail}`)
119
+ this.name = 'AuthorityDecisionEvidenceError'
120
+ this.code = code
121
+ }
122
+ }
123
+
124
+ function block(code, detail) {
125
+ throw new AuthorityDecisionEvidenceError(code, detail)
126
+ }
127
+
128
+ function exactObject(value, keys, label) {
129
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
130
+ block('RECEIPT_SHAPE_INVALID', `${label} must be an object`)
131
+ }
132
+ const actual = Object.keys(value).sort()
133
+ const expected = [...keys].sort()
134
+ if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
135
+ block('RECEIPT_SHAPE_INVALID', `${label} has an open or incomplete shape`)
136
+ }
137
+ return value
138
+ }
139
+
140
+ function exactString(value, label, maximumBytes, { pattern = null, allowEmpty = false } = {}) {
141
+ if (typeof value !== 'string' || (!allowEmpty && value.length === 0)) {
142
+ block('INPUT_INVALID', `${label} must be ${allowEmpty ? 'a' : 'a non-empty'} string`)
143
+ }
144
+ const bytes = Buffer.byteLength(value, 'utf8')
145
+ if (bytes > maximumBytes || value.includes('\0')) {
146
+ block('INPUT_INVALID', `${label} exceeds its closed byte or character contract`)
147
+ }
148
+ if (pattern && !pattern.test(value)) block('INPUT_INVALID', `${label} has an invalid format`)
149
+ return value
150
+ }
151
+
152
+ function exactBytes(value, label) {
153
+ if (!(typeof value === 'string' || Buffer.isBuffer(value) || ArrayBuffer.isView(value))) {
154
+ block('INPUT_INVALID', `${label} bytes are unavailable`)
155
+ }
156
+ return Buffer.isBuffer(value) ? Buffer.from(value) : Buffer.from(value)
157
+ }
158
+
159
+ function exactDigest(value, label) {
160
+ if (typeof value !== 'string' || !SHA256.test(value)) {
161
+ block('RECEIPT_SHAPE_INVALID', `${label} is not a canonical sha256 digest`)
162
+ }
163
+ return value
164
+ }
165
+
166
+ function exactBoolean(value, expected, label) {
167
+ if (value !== expected) block('RECEIPT_SHAPE_INVALID', `${label} is invalid`)
168
+ }
169
+
170
+ function normalizedUserMessages(value) {
171
+ if (!Array.isArray(value) || value.length > MAX_USER_MESSAGE_COUNT) {
172
+ block('INPUT_INVALID', 'userMessages must be one bounded array')
173
+ }
174
+ let aggregate = 0
175
+ const messages = value.map((message, index) => {
176
+ exactString(message, `userMessages[${index}]`, MAX_USER_MESSAGE_BYTES, { allowEmpty: false })
177
+ aggregate += Buffer.byteLength(message, 'utf8')
178
+ if (aggregate > MAX_USER_MESSAGES_BYTES) {
179
+ block('INPUT_INVALID', 'userMessages exceed the aggregate byte contract')
180
+ }
181
+ return message
182
+ })
183
+ return messages
184
+ }
185
+
186
+ function registeredProvider(registry, providerId) {
187
+ if (!registry || typeof registry !== 'object' || Array.isArray(registry)
188
+ || !Array.isArray(registry.providers)) {
189
+ block('PROVIDER_REGISTRY_INVALID', 'provider registry has no closed provider list')
190
+ }
191
+ exactString(providerId, 'providerId', 128, { pattern: PROVIDER_ID })
192
+ const matches = registry.providers.filter((provider) => provider?.id === providerId)
193
+ if (matches.length !== 1) {
194
+ block(matches.length === 0 ? 'PROVIDER_UNKNOWN' : 'PROVIDER_REGISTRY_INVALID', providerId)
195
+ }
196
+ return matches[0]
197
+ }
198
+
199
+ function canonicalAuthorityPolicy(bytes) {
200
+ const text = exactBytes(bytes, 'authority policy').toString('utf8')
201
+ const start = text.indexOf(AUTHORITY_START)
202
+ const end = text.indexOf(AUTHORITY_END)
203
+ if (start < 0 || end < start
204
+ || text.indexOf(AUTHORITY_START, start + AUTHORITY_START.length) >= 0
205
+ || text.indexOf(AUTHORITY_END, end + AUTHORITY_END.length) >= 0) {
206
+ block('AUTHORITY_POLICY_INVALID', 'canonical authority markers must occur exactly once')
207
+ }
208
+ return `${text.slice(start, end + AUTHORITY_END.length)}\n`
209
+ }
210
+
211
+ function classifierFunctions(classifier) {
212
+ if (!classifier || typeof classifier !== 'object'
213
+ || typeof classifier.classifyLatestAuthorization !== 'function'
214
+ || typeof classifier.classifyOperationAuthorization !== 'function') {
215
+ block('CLASSIFIER_INVALID', 'canonical authority classifier functions are unavailable')
216
+ }
217
+ return classifier
218
+ }
219
+
220
+ function classificationScopes(value, label) {
221
+ if (!Array.isArray(value)
222
+ || value.some((scope) => typeof scope !== 'string' || !CLASSIFICATION_SCOPE_IDS.has(scope))) {
223
+ block('CLASSIFIER_INVALID', `${label} is not one closed scope list`)
224
+ }
225
+ const sorted = [...value].sort((left, right) => (
226
+ Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8'))
227
+ ))
228
+ if (new Set(value).size !== value.length
229
+ || value.some((scope, index) => scope !== sorted[index])) {
230
+ block('CLASSIFIER_INVALID', `${label} is not sorted and unique`)
231
+ }
232
+ return value
233
+ }
234
+
235
+ function classificationResult(value) {
236
+ if (!value || typeof value !== 'object' || Array.isArray(value)
237
+ || !['approved', 'blocked'].includes(value.decision)
238
+ || !['engineering-remediation', 'product-ui-ux', 'unknown'].includes(value.decisionDomain)
239
+ || typeof value.reasonCode !== 'string'
240
+ || typeof value.operationEvidenceSha256 !== 'string'
241
+ || !/^[a-f0-9]{64}$/.test(value.operationEvidenceSha256)) {
242
+ block('CLASSIFIER_INVALID', 'canonical classifier returned an invalid decision')
243
+ }
244
+ const actualKeys = Object.keys(value).sort()
245
+ const expectedKeys = [...CLASSIFICATION_KEYS].sort()
246
+ if (actualKeys.length !== expectedKeys.length
247
+ || actualKeys.some((key, index) => key !== expectedKeys[index])) {
248
+ block('CLASSIFIER_INVALID', 'canonical classifier returned an open or incomplete decision')
249
+ }
250
+ const scopeLists = [
251
+ classificationScopes(value.allowedScopes, 'classification.allowedScopes'),
252
+ classificationScopes(value.humanOnlyScopes, 'classification.humanOnlyScopes'),
253
+ classificationScopes(
254
+ value.deniedOrAmbiguousScopes,
255
+ 'classification.deniedOrAmbiguousScopes',
256
+ ),
257
+ ]
258
+ const allScopes = scopeLists.flat()
259
+ if (new Set(allScopes).size !== allScopes.length) {
260
+ block('CLASSIFIER_INVALID', 'classification scope lists overlap')
261
+ }
262
+ return canonicalize(value)
263
+ }
264
+
265
+ function receiptPayload({
266
+ authorityPolicyBytes,
267
+ classifierSourceBytes,
268
+ receiptContractSourceBytes,
269
+ classifier,
270
+ providerRegistry,
271
+ providerRegistrySource = 'packages/governance/canonical/providers.json',
272
+ providerId,
273
+ runtimeSurface,
274
+ sessionId,
275
+ scopeNonce,
276
+ target,
277
+ operationText,
278
+ userMessages,
279
+ }) {
280
+ registeredProvider(providerRegistry, providerId)
281
+ exactString(providerRegistrySource, 'providerRegistrySource', 512)
282
+ if (providerRegistrySource.includes('\\')
283
+ || providerRegistrySource.startsWith('/')
284
+ || providerRegistrySource.split('#', 1)[0].split('/').some((segment) => (
285
+ !segment || segment === '.' || segment === '..'
286
+ ))) {
287
+ block('INPUT_INVALID', 'providerRegistrySource is not one canonical repository reference')
288
+ }
289
+ exactString(runtimeSurface, 'runtimeSurface', 256, { pattern: RUNTIME_SURFACE })
290
+ exactString(sessionId, 'sessionId', MAX_SCOPE_VALUE_BYTES)
291
+ exactString(scopeNonce, 'scopeNonce', MAX_SCOPE_VALUE_BYTES)
292
+ exactString(target, 'target', MAX_TARGET_BYTES)
293
+ exactString(operationText, 'operationText', MAX_OPERATION_BYTES, { allowEmpty: true })
294
+ const messages = normalizedUserMessages(userMessages)
295
+ const functions = classifierFunctions(classifier)
296
+ const classificationMode = messages.length ? 'user-context' : 'operation-only'
297
+ const classification = classificationResult(messages.length
298
+ ? functions.classifyLatestAuthorization(messages.at(-1), {
299
+ target,
300
+ operationText,
301
+ userMessages: messages,
302
+ })
303
+ : functions.classifyOperationAuthorization({ target, operationText }))
304
+ const authorityPolicy = canonicalAuthorityPolicy(authorityPolicyBytes)
305
+ const classifierBytes = exactBytes(classifierSourceBytes, 'classifier source')
306
+ const receiptContractBytes = exactBytes(receiptContractSourceBytes, 'receipt contract source')
307
+ return canonicalize({
308
+ schemaVersion: AUTHORITY_DECISION_RECEIPT_SCHEMA_VERSION,
309
+ kind: AUTHORITY_DECISION_RECEIPT_KIND,
310
+ source: {
311
+ authorityPolicy: 'AGENTS.md#canonical-decision-authority',
312
+ authorityPolicyDigest: digest(authorityPolicy),
313
+ classifier: 'packages/design-system/ds-canonical/hooks/lib/approval-evidence.mjs',
314
+ classifierDigest: digest(classifierBytes),
315
+ providerRegistry: providerRegistrySource,
316
+ providerRegistryDigest: digest(providerRegistry),
317
+ receiptContract: 'packages/governance/src/authority-decision-evidence.mjs',
318
+ receiptContractDigest: digest(receiptContractBytes),
319
+ },
320
+ binding: {
321
+ providerId,
322
+ runtimeSurface,
323
+ sessionDigest: digest(sessionId),
324
+ scopeNonceDigest: digest(scopeNonce),
325
+ userMessagesDigest: digest(messages),
326
+ target: classification.target,
327
+ operationEvidenceDigest: `sha256:${classification.operationEvidenceSha256}`,
328
+ },
329
+ classificationMode,
330
+ classification,
331
+ trust: {
332
+ contentAddressed: true,
333
+ runtimeCertification: 'not-certified',
334
+ promotionEligible: false,
335
+ rawAuthorityTextStored: false,
336
+ rawOperationTextStored: false,
337
+ },
338
+ })
339
+ }
340
+
341
+ export function prepareAuthorityDecisionReceipt(input = {}) {
342
+ const payload = receiptPayload(input)
343
+ return canonicalize({
344
+ ...payload,
345
+ receiptDigest: digest(payload),
346
+ })
347
+ }
348
+
349
+ export function validateAuthorityDecisionReceiptShape(receipt) {
350
+ exactObject(receipt, RECEIPT_KEYS, 'receipt')
351
+ if (receipt.schemaVersion !== AUTHORITY_DECISION_RECEIPT_SCHEMA_VERSION
352
+ || receipt.kind !== AUTHORITY_DECISION_RECEIPT_KIND) {
353
+ block('RECEIPT_SHAPE_INVALID', 'receipt identity is invalid')
354
+ }
355
+ exactObject(receipt.source, SOURCE_KEYS, 'receipt.source')
356
+ exactObject(receipt.binding, BINDING_KEYS, 'receipt.binding')
357
+ exactObject(receipt.trust, TRUST_KEYS, 'receipt.trust')
358
+ for (const [key, value] of Object.entries(receipt.source)) {
359
+ if (key.endsWith('Digest')) exactDigest(value, `receipt.source.${key}`)
360
+ else exactString(value, `receipt.source.${key}`, 512)
361
+ }
362
+ for (const key of [
363
+ 'operationEvidenceDigest',
364
+ 'scopeNonceDigest',
365
+ 'sessionDigest',
366
+ 'userMessagesDigest',
367
+ ]) exactDigest(receipt.binding[key], `receipt.binding.${key}`)
368
+ exactString(receipt.binding.providerId, 'receipt.binding.providerId', 128, { pattern: PROVIDER_ID })
369
+ exactString(receipt.binding.runtimeSurface, 'receipt.binding.runtimeSurface', 256, { pattern: RUNTIME_SURFACE })
370
+ exactString(receipt.binding.target, 'receipt.binding.target', MAX_TARGET_BYTES)
371
+ if (!['user-context', 'operation-only'].includes(receipt.classificationMode)) {
372
+ block('RECEIPT_SHAPE_INVALID', 'classificationMode is invalid')
373
+ }
374
+ classificationResult(receipt.classification)
375
+ exactBoolean(receipt.trust.contentAddressed, true, 'receipt.trust.contentAddressed')
376
+ exactBoolean(receipt.trust.promotionEligible, false, 'receipt.trust.promotionEligible')
377
+ exactBoolean(receipt.trust.rawAuthorityTextStored, false, 'receipt.trust.rawAuthorityTextStored')
378
+ exactBoolean(receipt.trust.rawOperationTextStored, false, 'receipt.trust.rawOperationTextStored')
379
+ if (receipt.trust.runtimeCertification !== 'not-certified') {
380
+ block('RECEIPT_SHAPE_INVALID', 'receipt must not claim runtime certification')
381
+ }
382
+ exactDigest(receipt.receiptDigest, 'receipt.receiptDigest')
383
+ const { receiptDigest, ...payload } = receipt
384
+ if (digest(payload) !== receiptDigest) {
385
+ block('RECEIPT_DIGEST_MISMATCH', 'receipt content address does not match its payload')
386
+ }
387
+ return true
388
+ }
389
+
390
+ export function verifyAuthorityDecisionReceipt({ receipt, ...expectedInput } = {}) {
391
+ validateAuthorityDecisionReceiptShape(receipt)
392
+ const expected = prepareAuthorityDecisionReceipt(expectedInput)
393
+ const checks = [
394
+ ['source.authorityPolicyDigest', receipt.source.authorityPolicyDigest, expected.source.authorityPolicyDigest, 'AUTHORITY_POLICY_STALE'],
395
+ ['source.classifierDigest', receipt.source.classifierDigest, expected.source.classifierDigest, 'CLASSIFIER_STALE'],
396
+ ['source.providerRegistryDigest', receipt.source.providerRegistryDigest, expected.source.providerRegistryDigest, 'PROVIDER_REGISTRY_STALE'],
397
+ ['source.receiptContractDigest', receipt.source.receiptContractDigest, expected.source.receiptContractDigest, 'RECEIPT_CONTRACT_STALE'],
398
+ ['binding.providerId', receipt.binding.providerId, expected.binding.providerId, 'PROVIDER_SUBSTITUTION'],
399
+ ['binding.runtimeSurface', receipt.binding.runtimeSurface, expected.binding.runtimeSurface, 'RUNTIME_SURFACE_SUBSTITUTION'],
400
+ ['binding.sessionDigest', receipt.binding.sessionDigest, expected.binding.sessionDigest, 'SESSION_SUBSTITUTION'],
401
+ ['binding.scopeNonceDigest', receipt.binding.scopeNonceDigest, expected.binding.scopeNonceDigest, 'REPLAY_OR_SCOPE_MISMATCH'],
402
+ ['binding.userMessagesDigest', receipt.binding.userMessagesDigest, expected.binding.userMessagesDigest, 'AUTHORITY_CONTEXT_SUBSTITUTION'],
403
+ ['binding.target', receipt.binding.target, expected.binding.target, 'TARGET_SUBSTITUTION'],
404
+ ['binding.operationEvidenceDigest', receipt.binding.operationEvidenceDigest, expected.binding.operationEvidenceDigest, 'OPERATION_SUBSTITUTION'],
405
+ ]
406
+ for (const [label, actual, expectedValue, code] of checks) {
407
+ if (actual !== expectedValue) block(code, label)
408
+ }
409
+ if (stableJson(receipt) !== stableJson(expected)) {
410
+ block('RECEIPT_NONCANONICAL', 'receipt differs from the current canonical classifier result')
411
+ }
412
+ return true
413
+ }
@@ -0,0 +1,8 @@
1
+ import { Buffer } from 'node:buffer'
2
+
3
+ // Zero-I/O ordering primitive shared by authority, package, generated adapter, fork, and
4
+ // product-template runtimes. Canonical artifacts preserve code points exactly and compare
5
+ // their UTF-8 bytes; host locale collation and implicit Unicode normalization are forbidden.
6
+ export function compareUtf8Bytes(left, right) {
7
+ return Buffer.compare(Buffer.from(String(left), 'utf8'), Buffer.from(String(right), 'utf8'))
8
+ }