@zanii/certify 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/LICENSE +159 -0
  2. package/README.md +112 -2
  3. package/dist/appliance-execution.d.ts +18 -0
  4. package/dist/appliance-execution.js +12 -0
  5. package/dist/appliance.d.ts +135 -0
  6. package/dist/appliance.js +253 -0
  7. package/dist/assurance-status.d.ts +58 -0
  8. package/dist/assurance-status.js +86 -0
  9. package/dist/audit-renewal.d.ts +39 -0
  10. package/dist/audit-renewal.js +62 -0
  11. package/dist/audit.d.ts +69 -0
  12. package/dist/audit.js +112 -0
  13. package/dist/beacon-client.d.ts +38 -0
  14. package/dist/beacon-client.js +52 -0
  15. package/dist/beacon.d.ts +84 -0
  16. package/dist/beacon.js +106 -0
  17. package/dist/bitstring-status.d.ts +56 -0
  18. package/dist/bitstring-status.js +135 -0
  19. package/dist/board.d.ts +218 -0
  20. package/dist/board.js +428 -0
  21. package/dist/bundle.d.ts +92 -0
  22. package/dist/bundle.js +109 -0
  23. package/dist/certificate.d.ts +161 -0
  24. package/dist/certificate.js +345 -0
  25. package/dist/cli-messages.json +148 -0
  26. package/dist/cli.d.ts +16 -0
  27. package/dist/cli.js +369 -0
  28. package/dist/current-issuer-vc.d.ts +20 -0
  29. package/dist/current-issuer-vc.js +60 -0
  30. package/dist/evidence-share.d.ts +89 -0
  31. package/dist/evidence-share.js +479 -0
  32. package/dist/federation.d.ts +103 -0
  33. package/dist/federation.js +240 -0
  34. package/dist/feed-snapshot.d.ts +97 -0
  35. package/dist/feed-snapshot.js +186 -0
  36. package/dist/fingerprint.d.ts +23 -0
  37. package/dist/fingerprint.js +48 -0
  38. package/dist/formats/status-v2.schema.json +31 -0
  39. package/dist/governance.d.ts +242 -0
  40. package/dist/governance.js +411 -0
  41. package/dist/identity.d.ts +79 -0
  42. package/dist/identity.js +146 -0
  43. package/dist/impartiality.d.ts +47 -0
  44. package/dist/impartiality.js +85 -0
  45. package/dist/index.d.ts +58 -0
  46. package/dist/index.js +59 -0
  47. package/dist/issuer-authorization.d.ts +4 -0
  48. package/dist/issuer-authorization.js +52 -0
  49. package/dist/issuer-resolution.d.ts +25 -0
  50. package/dist/issuer-resolution.js +89 -0
  51. package/dist/licence-owner-entry.d.ts +20 -0
  52. package/dist/licence-owner-entry.js +33 -0
  53. package/dist/licence-owner.d.ts +67 -0
  54. package/dist/licence-owner.js +180 -0
  55. package/dist/licence.d.ts +174 -0
  56. package/dist/licence.js +400 -0
  57. package/dist/live-evidence.d.ts +135 -0
  58. package/dist/live-evidence.js +210 -0
  59. package/dist/nitro-roots.d.ts +3 -0
  60. package/dist/nitro-roots.js +20 -0
  61. package/dist/nitro-x509.d.ts +16 -0
  62. package/dist/nitro-x509.js +141 -0
  63. package/dist/nitro.d.ts +74 -0
  64. package/dist/nitro.js +329 -0
  65. package/dist/normalization.d.ts +8 -0
  66. package/dist/normalization.js +10 -0
  67. package/dist/pq.d.ts +32 -0
  68. package/dist/pq.js +69 -0
  69. package/dist/profile.d.ts +20 -0
  70. package/dist/profile.js +161 -0
  71. package/dist/register.d.ts +79 -0
  72. package/dist/register.js +134 -0
  73. package/dist/rotation.d.ts +49 -0
  74. package/dist/rotation.js +124 -0
  75. package/dist/royalty-reconciliation.d.ts +24 -0
  76. package/dist/royalty-reconciliation.js +88 -0
  77. package/dist/runtime-client.d.ts +38 -0
  78. package/dist/runtime-client.js +185 -0
  79. package/dist/runtime-gateway.d.ts +24 -0
  80. package/dist/runtime-gateway.js +167 -0
  81. package/dist/runtime-knowledge.d.ts +28 -0
  82. package/dist/runtime-knowledge.js +75 -0
  83. package/dist/runtime-nitro.d.ts +30 -0
  84. package/dist/runtime-nitro.js +57 -0
  85. package/dist/runtime.d.ts +226 -0
  86. package/dist/runtime.js +254 -0
  87. package/dist/sandbox-delegation.d.ts +38 -0
  88. package/dist/sandbox-delegation.js +61 -0
  89. package/dist/schema-dialect.d.ts +1 -0
  90. package/dist/schema-dialect.js +97 -0
  91. package/dist/schemas/clause-map.schema.json +52 -0
  92. package/dist/schemas/profile.schema.json +80 -0
  93. package/dist/schemas/scenarios.schema.json +573 -0
  94. package/dist/schemas/suite.schema.json +138 -0
  95. package/dist/scheme-authority.d.ts +5 -0
  96. package/dist/scheme-authority.js +15 -0
  97. package/dist/scheme-entry.d.ts +25 -0
  98. package/dist/scheme-entry.js +85 -0
  99. package/dist/scheme-history.d.ts +5 -0
  100. package/dist/scheme-history.js +87 -0
  101. package/dist/scheme-log.d.ts +98 -0
  102. package/dist/scheme-log.js +266 -0
  103. package/dist/scheme-merkle.d.ts +2 -0
  104. package/dist/scheme-merkle.js +20 -0
  105. package/dist/scheme-text.d.ts +25 -0
  106. package/dist/scheme-text.js +46 -0
  107. package/dist/scheme-timing.d.ts +16 -0
  108. package/dist/scheme-timing.js +22 -0
  109. package/dist/scheme-transition.d.ts +47 -0
  110. package/dist/scheme-transition.js +101 -0
  111. package/dist/scoring.d.ts +35 -0
  112. package/dist/scoring.js +50 -0
  113. package/dist/shared.d.ts +16 -0
  114. package/dist/shared.js +20 -0
  115. package/dist/signer.d.ts +18 -0
  116. package/dist/signer.js +49 -0
  117. package/dist/signing-identity.d.ts +1 -0
  118. package/dist/signing-identity.js +11 -0
  119. package/dist/status.d.ts +113 -0
  120. package/dist/status.js +204 -0
  121. package/dist/succession.d.ts +21 -0
  122. package/dist/succession.js +67 -0
  123. package/dist/suite.d.ts +257 -0
  124. package/dist/suite.js +441 -0
  125. package/dist/vc.d.ts +29 -0
  126. package/dist/vc.js +96 -0
  127. package/dist/verification-bounds.d.ts +5 -0
  128. package/dist/verification-bounds.js +76 -0
  129. package/dist/verification-policy.d.ts +32 -0
  130. package/dist/verification-policy.js +85 -0
  131. package/dist/witness.d.ts +19 -0
  132. package/dist/witness.js +80 -0
  133. package/package.json +57 -3
@@ -0,0 +1,138 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://zanii.agency/certify/schemas/suite.schema.json",
4
+ "title": "Zanii Certify test suite (spec/suites.md)",
5
+ "type": "object",
6
+ "required": ["v", "type", "id", "version", "visibility", "cases"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "v": { "const": 1 },
10
+ "type": { "const": "certify.suite" },
11
+ "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" },
12
+ "version": { "type": "integer", "minimum": 1 },
13
+ "visibility": { "enum": ["public", "held-out"] },
14
+ "profiles": { "$ref": "#/$defs/profileRefs" },
15
+ "rubrics": { "type": "array", "minItems": 1, "maxItems": 64, "items": { "$ref": "#/$defs/rubric" } },
16
+ "cases": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/case" } }
17
+ },
18
+ "$defs": {
19
+ "profileRefs": { "type": "array", "minItems": 1, "maxItems": 64, "uniqueItems": true, "items": { "type": "string", "maxLength": 100, "pattern": "^[a-z0-9][a-z0-9-]{0,63}/[1-9][0-9]{0,8}$" } },
20
+ "teamPolicy": { "type": "object", "required": ["members", "require_distinct_owners"], "additionalProperties": false, "properties": { "members": { "type": "array", "minItems": 3, "maxItems": 64, "uniqueItems": true, "items": { "type": "object", "required": ["agent", "owner"], "additionalProperties": false, "properties": { "agent": { "anyOf": [{ "const": "$agent" }, { "type": "string", "pattern": "^did:key:z[1-9A-HJ-NP-Za-km-z]{40,60}$" }] }, "owner": { "type": "string", "pattern": "^did:key:z[1-9A-HJ-NP-Za-km-z]{40,60}$" } } } }, "require_distinct_owners": { "type": "boolean" }, "bind_run": { "type": "boolean" } } },
21
+ "scope": { "type": "string", "maxLength": 128, "pattern": "^(\\*|[a-z0-9_-]+(\\.[a-z0-9_-]+)*(\\.\\*)?)$" },
22
+ "arg": { "type": "string", "pattern": "^[A-Za-z0-9_]{1,64}$" },
23
+ "cert": { "type": "object", "required": ["v", "issuer", "subject", "scopes", "exp", "sig"], "additionalProperties": false, "properties": { "v": { "const": 1 }, "issuer": { "type": "string", "pattern": "^did:key:z[1-9A-HJ-NP-Za-km-z]{40,60}$" }, "subject": { "type": "string", "pattern": "^did:key:z[1-9A-HJ-NP-Za-km-z]{40,60}$" }, "scopes": { "type": "array", "minItems": 1, "maxItems": 64, "items": { "$ref": "#/$defs/scope" } }, "exp": { "type": "string", "minLength": 1, "maxLength": 64 }, "sig": { "type": "string", "pattern": "^ed25519:[0-9a-f]{128}$" } } },
24
+ "revocation": { "type": "object", "required": ["v", "type", "cert", "ts", "sig"], "additionalProperties": false, "properties": { "v": { "const": 1 }, "type": { "const": "revocation" }, "cert": { "$ref": "#/$defs/cert" }, "ts": { "type": "string", "minLength": 1, "maxLength": 64 }, "sig": { "type": "string", "pattern": "^ed25519:[0-9a-f]{128}$" } } },
25
+ "link": { "type": "object", "required": ["agent", "scopes", "spend_limit"], "additionalProperties": false, "properties": { "agent": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" }, "scopes": { "type": "array", "minItems": 1, "maxItems": 64, "uniqueItems": true, "items": { "$ref": "#/$defs/scope" } }, "spend_limit": { "type": "object", "required": ["amount_minor", "currency"], "additionalProperties": false, "properties": { "amount_minor": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "currency": { "type": "string", "pattern": "^[A-Z]{3}$" } } } } },
26
+ "text": {
27
+ "type": "object",
28
+ "required": ["en", "ar"],
29
+ "additionalProperties": false,
30
+ "properties": {
31
+ "en": { "type": "string", "minLength": 1, "maxLength": 4000 },
32
+ "ar": { "type": "string", "minLength": 1, "maxLength": 4000 }
33
+ }
34
+ },
35
+ "rubric": {
36
+ "type": "object",
37
+ "required": ["id", "title", "pass", "fail"],
38
+ "additionalProperties": false,
39
+ "properties": {
40
+ "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" },
41
+ "title": { "$ref": "#/$defs/text" },
42
+ "pass": { "$ref": "#/$defs/text" },
43
+ "fail": { "$ref": "#/$defs/text" }
44
+ }
45
+ },
46
+ "case": {
47
+ "type": "object",
48
+ "required": ["id", "category", "lang", "turns", "check"],
49
+ "additionalProperties": false,
50
+ "properties": {
51
+ "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,95}$" },
52
+ "category": { "type": "string", "pattern": "^[a-z][a-z0-9_]{0,63}$" },
53
+ "profiles": { "$ref": "#/$defs/profileRefs" },
54
+ "lang": { "enum": ["en", "ar", "ar-gulf"] },
55
+ "turns": {
56
+ "type": "array",
57
+ "minItems": 1,
58
+ "maxItems": 20,
59
+ "items": {
60
+ "type": "object",
61
+ "required": ["role", "content"],
62
+ "additionalProperties": false,
63
+ "properties": {
64
+ "role": { "enum": ["user", "assistant"] },
65
+ "content": { "type": "string", "minLength": 1, "maxLength": 20000 }
66
+ }
67
+ }
68
+ },
69
+ "documents": {
70
+ "type": "array",
71
+ "maxItems": 10,
72
+ "items": {
73
+ "type": "object",
74
+ "required": ["name", "text"],
75
+ "additionalProperties": false,
76
+ "properties": {
77
+ "name": { "type": "string", "minLength": 1, "maxLength": 200 },
78
+ "text": { "type": "string", "minLength": 1, "maxLength": 100000 }
79
+ }
80
+ }
81
+ },
82
+ "tools": {
83
+ "type": "array",
84
+ "maxItems": 64,
85
+ "items": {
86
+ "type": "object",
87
+ "required": ["type", "function"],
88
+ "additionalProperties": false,
89
+ "properties": {
90
+ "type": { "const": "function" },
91
+ "function": {
92
+ "type": "object",
93
+ "required": ["name"],
94
+ "properties": {
95
+ "name": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$" },
96
+ "description": { "type": "string", "maxLength": 2000 },
97
+ "parameters": { "type": "object" }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ },
103
+ "delegation": { "type": "object", "required": ["chain", "tools"], "additionalProperties": false, "properties": { "chain": { "type": "array", "minItems": 1, "maxItems": 8, "items": { "$ref": "#/$defs/link" } }, "tools": { "type": "array", "maxItems": 64, "items": { "type": "object", "required": ["name", "scope"], "additionalProperties": false, "properties": { "name": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$" }, "scope": { "$ref": "#/$defs/scope" }, "amount": { "$ref": "#/$defs/arg" }, "currency": { "$ref": "#/$defs/arg" } } } }, "certs": { "type": "array", "minItems": 1, "maxItems": 8, "items": { "$ref": "#/$defs/cert" } }, "revocations": { "type": "array", "minItems": 1, "maxItems": 64, "items": { "$ref": "#/$defs/revocation" } } } },
104
+ "sub_agent": { "type": "object", "required": ["agent", "tool", "scopes"], "additionalProperties": false, "properties": { "agent": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" }, "tool": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$" }, "scopes": { "$ref": "#/$defs/arg" }, "amount": { "$ref": "#/$defs/arg" } } },
105
+ "check": { "$ref": "#/$defs/check" }
106
+ }
107
+ },
108
+ "check": {
109
+ "type": "object",
110
+ "required": ["kind"],
111
+ "additionalProperties": false,
112
+ "properties": {
113
+ "kind": { "enum": ["not_contains", "contains_any", "tool_not_called", "max_tool_calls", "all", "judged", "within_delegation", "no_scope_escalation", "team_receipt"] },
114
+ "values": {
115
+ "type": "array",
116
+ "minItems": 1,
117
+ "maxItems": 64,
118
+ "items": { "type": "string", "minLength": 1, "maxLength": 2000 }
119
+ },
120
+ "tools": {
121
+ "type": "array",
122
+ "minItems": 1,
123
+ "maxItems": 64,
124
+ "uniqueItems": true,
125
+ "items": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$" }
126
+ },
127
+ "n": { "type": "integer", "minimum": 0, "maximum": 1000 },
128
+ "checks": { "type": "array", "minItems": 1, "maxItems": 16, "items": { "$ref": "#/$defs/check" } },
129
+ "rubric": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" },
130
+ "tool": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$" },
131
+ "threshold": { "type": "integer", "minimum": 1, "maximum": 64 },
132
+ "invocation": { "enum": ["required", "optional"] },
133
+ "policy": { "$ref": "#/$defs/teamPolicy" },
134
+ "finding": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]{0,63}$" }
135
+ }
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,5 @@
1
+ import { type SchemeLogsInput } from "./scheme-log.ts";
2
+ export declare function schemeAuthority(log: SchemeLogsInput, owner: string | undefined, groups: Record<string, readonly unknown[]>): {
3
+ reasons: string[];
4
+ canceled: Set<string>;
5
+ };
@@ -0,0 +1,15 @@
1
+ // Optional complete, checkpoint-authenticated context for standalone authority consumers.
2
+ import { jcsHash } from "@zanii/core";
3
+ import { verifySchemeLogs } from "./scheme-log.js";
4
+ import { isObj } from "./shared.js";
5
+ import { withinVerificationBudget } from "./verification-bounds.js";
6
+ export function schemeAuthority(log, owner, groups) {
7
+ if (!withinVerificationBudget({ log, groups }) || !isObj(log) || Object.keys(log).some(k => !["entries", "checkpoint", "previous", "witness", "appends", "ledgerReceipts"].includes(k)) || !Array.isArray(log?.entries) || log.entries.length > 10_000)
8
+ return { reasons: ["scheme authority context exceeds the verification budget"], canceled: new Set() };
9
+ const checked = verifySchemeLogs(log, { owner }), reasons = [...checked.reasons];
10
+ for (const [type, rows] of Object.entries(groups)) {
11
+ if (!Array.isArray(rows) || jcsHash(rows) !== jcsHash(log.entries.filter(e => isObj(e) && e.type === type)))
12
+ reasons.push(`supplied ${type} history differs from the authenticated scheme log`);
13
+ }
14
+ return { reasons, canceled: new Set(reasons.length ? [] : checked.canceled ?? []) };
15
+ }
@@ -0,0 +1,25 @@
1
+ import { type LicenceState } from "./licence.ts";
2
+ export declare const MAX_SCHEME_ENTRY_BYTES = 1048576;
3
+ export interface SchemeEntryContext {
4
+ owner: string;
5
+ /** Already authenticated newest board and the head of this entry's type. */
6
+ board: Record<string, unknown> | null;
7
+ head: {
8
+ size: number;
9
+ hash: string;
10
+ effective_at: string | null;
11
+ } | null;
12
+ maxSignedEffective: string | null;
13
+ release: (hash: string) => Record<string, unknown> | undefined;
14
+ released: (subject: string) => boolean;
15
+ withdrawn: (release: string) => boolean;
16
+ licenceState: (hash: string) => LicenceState;
17
+ statusIssuers?: readonly string[];
18
+ }
19
+ export interface SchemeEntryCheck {
20
+ ok: boolean;
21
+ reasons: string[];
22
+ effectiveAt?: string;
23
+ licenceState?: LicenceState;
24
+ }
25
+ export declare function verifySchemeEntry(doc: unknown, c: SchemeEntryContext): SchemeEntryCheck;
@@ -0,0 +1,85 @@
1
+ // One authenticated extension of a previously verified semantic prefix. The caller commits the
2
+ // entry and its returned state atomically; lookups must contain ONLY that verified prefix.
3
+ import { jcsHash, verifySignedObject } from "@zanii/core";
4
+ import { boardFieldError, boardSignedReasons, isBoard, isRelease, isWithdrawal, releaseFieldError, signersOf, subjectKey, withdrawalFieldError } from "./board.js";
5
+ import { licenceStatusFieldError } from "./licence.js";
6
+ import { isBodyRegister, registerFieldError } from "./register.js";
7
+ import { isObj, ms } from "./shared.js";
8
+ import { signingIdentityReasons } from "./signing-identity.js";
9
+ import { withinVerificationBudget } from "./verification-bounds.js";
10
+ export const MAX_SCHEME_ENTRY_BYTES = 1_048_576;
11
+ export function verifySchemeEntry(doc, c) {
12
+ const reasons = [];
13
+ const out = (extra = {}) => ({ ok: reasons.length === 0, reasons, ...extra });
14
+ if (!withinVerificationBudget(doc, { maxBytes: MAX_SCHEME_ENTRY_BYTES, maxNodes: 100_000 }))
15
+ return { ok: false, reasons: ["scheme entry exceeds the JSON verification budget"] };
16
+ if (!isObj(doc))
17
+ return { ok: false, reasons: ["not a scheme log entry"] };
18
+ const board = Boolean(isBoard(doc)), release = Boolean(isRelease(doc)), withdrawal = Boolean(isWithdrawal(doc)), register = Boolean(isBodyRegister(doc));
19
+ const status = doc.v === 1 && doc.type === "certify.licence-status" && Object.keys(doc).length === ("for_cause" in doc || "effective_at" in doc ? 12 : 10);
20
+ if (!board && !release && !withdrawal && !register && !status)
21
+ return { ok: false, reasons: ["not a scheme log entry (v1)"] };
22
+ if (c.statusIssuers !== undefined)
23
+ reasons.push(...signingIdentityReasons(c.owner, [], c.statusIssuers));
24
+ const err = board ? boardFieldError(doc) : release ? releaseFieldError(doc) : withdrawal ? withdrawalFieldError(doc) : register ? registerFieldError(doc) : licenceStatusFieldError(doc);
25
+ if (err) {
26
+ reasons.push(`entry is malformed: ${err}`);
27
+ return out();
28
+ }
29
+ const count = c.head?.size ?? 0;
30
+ if (!Number.isSafeInteger(count) || count < 0 || doc.seq !== count)
31
+ reasons.push(`entry is not at position ${count}`);
32
+ if (doc.prev !== (c.head?.hash ?? null))
33
+ reasons.push("entry does not name the previous entry of its type");
34
+ const effectiveAt = (status ? doc.at : doc.effective_from);
35
+ if (c.head?.effective_at && !(board || register ? ms(effectiveAt) > ms(c.head.effective_at) : ms(effectiveAt) >= ms(c.head.effective_at)))
36
+ reasons.push(board ? "board does not take effect after the previous board" : register ? "register does not take effect after the previous register" : "entry takes effect before the previous entry");
37
+ if (board) {
38
+ if (doc.owner !== c.owner)
39
+ reasons.push("the board log is not the trusted scheme owner's");
40
+ if (count && !c.board)
41
+ reasons.push("the authenticated previous board is missing");
42
+ if (c.maxSignedEffective && !(ms(effectiveAt) > ms(c.maxSignedEffective)))
43
+ reasons.push("the board does not take effect after every board-signed entry appended before it");
44
+ const allowed = count === 0 ? [c.owner] : (c.board?.members ?? []);
45
+ const need = count === 0 ? 1 : Number(c.board?.threshold ?? 1);
46
+ const signatures = signersOf(doc, allowed);
47
+ if (!signatures.wellFormed)
48
+ reasons.push("board carries a signature that is malformed, invalid or not by an authorised signer");
49
+ if (signatures.signers.length < need)
50
+ reasons.push(`board is signed by ${signatures.signers.length} of the ${need} previous board members required`);
51
+ }
52
+ else if (status) {
53
+ if (!verifySignedObject(doc) || doc.signer !== c.owner)
54
+ reasons.push("licence status entry is not signed by the scheme owner");
55
+ const previous = c.licenceState(doc.licence);
56
+ const allowed = doc.action === "suspend" ? previous === "active" : doc.action === "reinstate" ? previous === "suspended" : previous !== "withdrawn";
57
+ if (!allowed)
58
+ reasons.push(`entry cannot ${String(doc.action)} a licence that is ${previous}`);
59
+ return out({ effectiveAt, licenceState: doc.action === "suspend" ? "suspended" : doc.action === "reinstate" ? "active" : "withdrawn" });
60
+ }
61
+ else {
62
+ if (!c.board)
63
+ reasons.push("a board-signed entry is appended before any board");
64
+ else if (doc.board !== jcsHash(c.board))
65
+ reasons.push("entry is not signed by the newest board when it was appended");
66
+ else
67
+ reasons.push(...boardSignedReasons(doc, [c.board], release ? "release" : withdrawal ? "withdrawal" : "body register"));
68
+ if (release && c.released(subjectKey(doc.subject)))
69
+ reasons.push(`entry releases ${subjectKey(doc.subject)} again`);
70
+ if (withdrawal) {
71
+ const prior = c.release(doc.release);
72
+ if (!prior)
73
+ reasons.push("withdrawal names a release that is not in the release log");
74
+ else {
75
+ if (subjectKey(prior.subject) !== subjectKey(doc.subject))
76
+ reasons.push("withdrawal subject is not the release's subject");
77
+ if (!(ms(effectiveAt) >= ms(prior.effective_from)))
78
+ reasons.push("withdrawal takes effect before the release");
79
+ }
80
+ if (c.withdrawn(doc.release))
81
+ reasons.push("withdrawal withdraws a release again");
82
+ }
83
+ }
84
+ return out({ effectiveAt });
85
+ }
@@ -0,0 +1,5 @@
1
+ import { type SchemeAppendStatement, type SchemeLogsInput } from "./scheme-log.ts";
2
+ export declare function schemeHistoryReasons(input: SchemeLogsInput, owner: string | undefined, statusIssuers?: readonly string[], prepared?: readonly SchemeAppendStatement[]): {
3
+ reasons: string[];
4
+ canceled: string[];
5
+ };
@@ -0,0 +1,87 @@
1
+ // Transition-aware semantic replay. The enclosing verifier authenticates the final checkpoint.
2
+ import { jcsHash, receiptHash } from "@zanii/core";
3
+ import {} from "./licence.js";
4
+ import { verifySchemeEntry } from "./scheme-entry.js";
5
+ import {} from "./scheme-log.js";
6
+ import { schemePrefixStatements } from "./scheme-merkle.js";
7
+ import { verifySchemeTransition } from "./scheme-transition.js";
8
+ import { isObj, ms } from "./shared.js";
9
+ import { withinVerificationBudget } from "./verification-bounds.js";
10
+ export function schemeHistoryReasons(input, owner, statusIssuers, prepared) {
11
+ if (!owner || !Array.isArray(input.entries) || input.entries.length > 10_000 || !withinVerificationBudget(input))
12
+ return { reasons: ["transition-aware scheme history needs a trusted owner and at most 10000 bounded entries"], canceled: [] };
13
+ const reasons = [], canceled = new Set(), records = new Map();
14
+ const heads = new Map(), states = new Map();
15
+ const withdrawals = new Map();
16
+ const subjects = new Set();
17
+ let board = null, maximum = null;
18
+ const receipts = new Map((Array.isArray(input.ledgerReceipts) ? input.ledgerReceipts : []).filter(isObj).map((r) => [receiptHash(r), r]));
19
+ const statements = prepared ?? schemePrefixStatements(input.entries, owner);
20
+ const active = (hash) => canceled.has(hash) ? undefined : records.get(hash);
21
+ const withdrawn = (hash) => { const digest = withdrawals.get(hash); return digest !== undefined && !canceled.has(digest); };
22
+ const rebuildEffective = () => {
23
+ maximum = null;
24
+ for (const [type, head] of heads) {
25
+ if (!["certify.release", "certify.release-withdrawal", "certify.body-register"].includes(type))
26
+ continue;
27
+ head.effective_at = null;
28
+ }
29
+ for (const [hash, e] of records)
30
+ if (!canceled.has(hash) && ["certify.release", "certify.release-withdrawal", "certify.body-register"].includes(e.type)) {
31
+ const at = e.effective_from;
32
+ if (!maximum || ms(at) > ms(maximum))
33
+ maximum = at;
34
+ heads.get(e.type).effective_at = at;
35
+ }
36
+ };
37
+ const anchor = (index) => {
38
+ const append = input.appends?.[index];
39
+ const receipt = isObj(append) ? receipts.get(append.ledger_receipt) : undefined;
40
+ return statements[index] ? { statement: statements[index], append, receipt } : undefined;
41
+ };
42
+ for (let i = 0; i < input.entries.length; i++) {
43
+ const e = input.entries[i];
44
+ if (!isObj(e)) {
45
+ reasons.push(`entry ${i} is not a scheme entry`);
46
+ break;
47
+ }
48
+ if (e.type === "certify.board-transition") {
49
+ const next = input.entries[i + 1];
50
+ const previous = input.appends?.[i - 1];
51
+ const checked = verifySchemeTransition(e, { owner, board, nextBoard: next, index: i, previousAt: isObj(previous) ? previous.anchored_at : "", entry: active, anchor: anchor(i), nextAnchor: i + 1 < input.entries.length ? anchor(i + 1) : undefined });
52
+ reasons.push(...checked.reasons.map((r) => `entry ${i}: ${r}`));
53
+ if (!checked.ok)
54
+ break;
55
+ for (const hash of checked.canceled)
56
+ canceled.add(hash);
57
+ // A canceled release cannot leave an uncanceled withdrawal referring to it.
58
+ for (const [hash, prior] of records)
59
+ if (!canceled.has(hash) && prior.type === "certify.release-withdrawal" && canceled.has(prior.release))
60
+ reasons.push(`entry ${i}: dependent future withdrawal must be explicitly canceled with its release`);
61
+ rebuildEffective();
62
+ records.set(jcsHash(e), e);
63
+ continue;
64
+ }
65
+ const check = verifySchemeEntry(e, { owner, board, head: heads.get(e.type) ?? null, maxSignedEffective: maximum, statusIssuers,
66
+ release: active, released: (subject) => subjects.has(subject), withdrawn, licenceState: (hash) => states.get(hash) ?? "active" });
67
+ reasons.push(...check.reasons.map((r) => `entry ${i}: ${r}`));
68
+ if (!check.ok)
69
+ break;
70
+ const hash = jcsHash(e), type = e.type;
71
+ records.set(hash, e);
72
+ heads.set(type, { size: Number(e.seq) + 1, hash, effective_at: check.effectiveAt });
73
+ if (type === "certify.board")
74
+ board = e;
75
+ if (type === "certify.release") {
76
+ const s = e.subject;
77
+ subjects.add(`${s.kind} ${s.ref}`);
78
+ }
79
+ if (type === "certify.release-withdrawal")
80
+ withdrawals.set(e.release, hash);
81
+ if (type === "certify.licence-status")
82
+ states.set(e.licence, check.licenceState);
83
+ if (["certify.release", "certify.release-withdrawal", "certify.body-register"].includes(type) && (!maximum || ms(check.effectiveAt) > ms(maximum)))
84
+ maximum = check.effectiveAt;
85
+ }
86
+ return { reasons, canceled: reasons.length ? [] : [...canceled].sort() };
87
+ }
@@ -0,0 +1,98 @@
1
+ import { type Receipt, type SignedTreeHead } from "@zanii/core";
2
+ import { type Signer } from "./signer.ts";
3
+ export declare const SCHEME_LOG_TYPES: readonly ["certify.board", "certify.release", "certify.licence-status", "certify.release-withdrawal", "certify.body-register", "certify.board-transition"];
4
+ /** Section 2: the owner's signed tree head over the scheme log (leaf i = entry i). */
5
+ export declare function schemeCheckpoint(entries: readonly unknown[], ts: string, signer: Signer): Promise<SignedTreeHead>;
6
+ /** Section 2: the inclusion proof of entry `index` in the checkpoint over `entries`, hex. */
7
+ export declare const schemeInclusionProof: (entries: readonly unknown[], index: number) => string[];
8
+ /** Section 2: the proof that the log over `entries` extends its first `fromSize` entries, hex. */
9
+ export declare const schemeConsistencyProof: (entries: readonly unknown[], fromSize: number) => string[];
10
+ /** Section 2: one entry is at `index` of a checkpoint signed by the owner (a light client's check). */
11
+ export declare function verifySchemeInclusion(entry: unknown, index: number, checkpoint: unknown, proof: unknown, opts?: {
12
+ owner?: string;
13
+ }): {
14
+ ok: boolean;
15
+ reasons: string[];
16
+ };
17
+ /** Section 3: what the ledger receipt of an append commits to (`payload_hash` = jcsHash of it, `payload_encoding: jcs`). */
18
+ export interface SchemeAppendStatement {
19
+ v: 1;
20
+ type: "certify.scheme-append";
21
+ /** The scheme owner's DID, the log id. */
22
+ log: string;
23
+ index: number;
24
+ /** jcsHash of the entry appended. */
25
+ entry: string;
26
+ /** The log after the append: index + 1 entries and their Merkle root. */
27
+ size: number;
28
+ root: string;
29
+ }
30
+ export type SchemeAppend = SchemeAppendStatement & {
31
+ /** receiptHash of the Zanii ledger receipt that anchors the append. */
32
+ ledger_receipt: string;
33
+ /** The ledger receipt's `ts`: the append time. */
34
+ anchored_at: string;
35
+ signer: string;
36
+ sig: string;
37
+ };
38
+ /** Section 3: the statement for entry `index` of `entries` (the server records it on the ledger). */
39
+ export declare function schemeAppendStatement(entries: readonly unknown[], index: number, owner: string): SchemeAppendStatement;
40
+ /** Section 3: the owner's append receipt for entry `index`, bound to the ledger receipt that anchors it. */
41
+ export declare function buildSchemeAppend(entries: readonly unknown[], index: number, ledgerReceipt: Receipt, signer: Signer): Promise<SchemeAppend>;
42
+ /** The bounded equivalent when the caller maintains authenticated Merkle subtrees. */
43
+ export declare function buildSchemeAppendFromStatement(statement: SchemeAppendStatement, ledgerReceipt: Receipt, signer: Signer): Promise<SchemeAppend>;
44
+ /** One append against the authenticated entry/tree prefix; no history arrays are needed. */
45
+ export declare function verifySchemeAppend(statement: SchemeAppendStatement, entry: unknown, append: unknown, receipt: unknown, opts?: {
46
+ previousAt?: string | null;
47
+ ledger?: string | null;
48
+ }): {
49
+ ok: boolean;
50
+ reasons: string[];
51
+ };
52
+ export interface SchemeLogsInput {
53
+ /** The scheme log in append order. */
54
+ entries: readonly unknown[];
55
+ /** The owner's newest checkpoint, over all of `entries`. */
56
+ checkpoint: unknown;
57
+ /** Checkpoints this verifier saw before, each with the consistency proof from it to `checkpoint`. */
58
+ previous?: readonly {
59
+ checkpoint: unknown;
60
+ proof: unknown;
61
+ }[];
62
+ /** Require `checkpoint` co-signed by k trusted witnesses. */
63
+ witness?: {
64
+ cosignatures: readonly unknown[];
65
+ trusted: readonly string[];
66
+ k: number;
67
+ };
68
+ /** One append receipt per entry. */
69
+ appends?: readonly unknown[];
70
+ /** The Zanii ledger receipts the appends name: each append time is then the ledger's. */
71
+ ledgerReceipts?: readonly unknown[];
72
+ }
73
+ export interface SchemeLogsCheck {
74
+ ok: boolean;
75
+ reasons: string[];
76
+ size: number;
77
+ /** Entries whose append is bound to a supplied ledger receipt. */
78
+ anchored: number;
79
+ /** With `appends`: each entry's append time. */
80
+ appended?: string[];
81
+ /** With `witness`: the trusted witnesses that co-signed the checkpoint. */
82
+ witnesses?: string[];
83
+ /** Transition-aware logs only: authenticated cancellation hashes; empty on any refusal. */
84
+ canceled?: string[];
85
+ }
86
+ /**
87
+ * Section 4: the scheme log verifies: each kind of entry forms its own log (governance-board.md,
88
+ * licensing.md, body-register.md); the append rules hold in leaf order (each board-signed entry by
89
+ * the newest board appended before it, each later board taking effect after every board-signed
90
+ * entry before it); the checkpoint is the owner's over exactly these entries and extends every
91
+ * previous one; with `witness`, k trusted witnesses co-signed it; with `appends`, each entry has the
92
+ * owner's append receipt with append times never going back; with `ledgerReceipts`, each append is
93
+ * bound to its ledger receipt.
94
+ */
95
+ export declare function verifySchemeLogs(input: SchemeLogsInput, opts?: {
96
+ owner?: string;
97
+ statusIssuers?: readonly string[];
98
+ }): SchemeLogsCheck;