@synoi/sraid 0.2.0 → 0.4.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 (86) hide show
  1. package/README.md +156 -50
  2. package/SPEC.md +2 -2
  3. package/dist/attestation.d.ts +8 -49
  4. package/dist/attestation.d.ts.map +1 -1
  5. package/dist/attestation.js +15 -46
  6. package/dist/attestation.js.map +1 -1
  7. package/dist/capability.d.ts +59 -0
  8. package/dist/capability.d.ts.map +1 -0
  9. package/dist/capability.js +44 -0
  10. package/dist/capability.js.map +1 -0
  11. package/dist/index.d.ts +13 -13
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +26 -18
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal/attestation-core.d.ts +85 -0
  16. package/dist/internal/attestation-core.d.ts.map +1 -0
  17. package/dist/internal/attestation-core.js +78 -0
  18. package/dist/internal/attestation-core.js.map +1 -0
  19. package/dist/internal/base64-browser.d.ts +31 -0
  20. package/dist/internal/base64-browser.d.ts.map +1 -0
  21. package/dist/internal/base64-browser.js +39 -0
  22. package/dist/internal/base64-browser.js.map +1 -0
  23. package/dist/internal/base64-validate.d.ts +19 -0
  24. package/dist/internal/base64-validate.d.ts.map +1 -0
  25. package/dist/internal/base64-validate.js +33 -0
  26. package/dist/internal/base64-validate.js.map +1 -0
  27. package/dist/internal/base64.d.ts +7 -5
  28. package/dist/internal/base64.d.ts.map +1 -1
  29. package/dist/internal/base64.js +7 -19
  30. package/dist/internal/base64.js.map +1 -1
  31. package/dist/internal/content-core.d.ts +68 -0
  32. package/dist/internal/content-core.d.ts.map +1 -0
  33. package/dist/internal/content-core.js +87 -0
  34. package/dist/internal/content-core.js.map +1 -0
  35. package/dist/internal/ed25519-browser.d.ts +33 -0
  36. package/dist/internal/ed25519-browser.d.ts.map +1 -0
  37. package/dist/internal/ed25519-browser.js +79 -0
  38. package/dist/internal/ed25519-browser.js.map +1 -0
  39. package/dist/internal/mldsa-browser.d.ts +27 -0
  40. package/dist/internal/mldsa-browser.d.ts.map +1 -0
  41. package/dist/internal/mldsa-browser.js +35 -0
  42. package/dist/internal/mldsa-browser.js.map +1 -0
  43. package/dist/internal/sha256-browser.d.ts +17 -0
  44. package/dist/internal/sha256-browser.d.ts.map +1 -0
  45. package/dist/internal/sha256-browser.js +27 -0
  46. package/dist/internal/sha256-browser.js.map +1 -0
  47. package/dist/lineage.js +0 -0
  48. package/dist/lineage.js.map +1 -1
  49. package/dist/oid.d.ts +2 -52
  50. package/dist/oid.d.ts.map +1 -1
  51. package/dist/oid.js +9 -71
  52. package/dist/oid.js.map +1 -1
  53. package/dist/types.d.ts +4 -30
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js +19 -0
  56. package/dist/types.js.map +1 -1
  57. package/dist/validate.d.ts +2 -17
  58. package/dist/validate.d.ts.map +1 -1
  59. package/dist/validate.js +0 -57
  60. package/dist/validate.js.map +1 -1
  61. package/dist/verify-browser.d.ts +80 -0
  62. package/dist/verify-browser.d.ts.map +1 -0
  63. package/dist/verify-browser.js +157 -0
  64. package/dist/verify-browser.js.map +1 -0
  65. package/package.json +22 -3
  66. package/src/attestation.ts +29 -88
  67. package/src/capability.ts +69 -0
  68. package/src/index.ts +27 -36
  69. package/src/internal/attestation-core.ts +122 -0
  70. package/src/internal/base64-browser.ts +39 -0
  71. package/src/internal/base64-validate.ts +31 -0
  72. package/src/internal/base64.ts +7 -16
  73. package/src/internal/content-core.ts +88 -0
  74. package/src/internal/ed25519-browser.ts +85 -0
  75. package/src/internal/mldsa-browser.ts +39 -0
  76. package/src/internal/sha256-browser.ts +28 -0
  77. package/src/lineage.ts +0 -0
  78. package/src/oid.ts +10 -73
  79. package/src/types.ts +23 -31
  80. package/src/validate.ts +1 -64
  81. package/src/verify-browser.ts +185 -0
  82. package/dist/authority.d.ts +0 -351
  83. package/dist/authority.d.ts.map +0 -1
  84. package/dist/authority.js +0 -563
  85. package/dist/authority.js.map +0 -1
  86. package/src/authority.ts +0 -849
package/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # @synoi/sraid
2
2
 
3
- Reference TypeScript implementation of **SRAID (Self-Routing Addressable
4
- Identity Data)** — L0 of the SynOI SRAID Stack. MIT-licensed.
3
+ Reference TypeScript implementation of **SRAID**, the SynOI content-addressed
4
+ object format. MIT-licensed.
5
+
6
+ SRAID is a name, not an acronym. It has been expanded two different ways in two
7
+ different places; neither expansion added anything, so this package no longer
8
+ expands it. What it does is below.
5
9
 
6
10
  SRAID defines:
7
11
 
@@ -9,24 +13,28 @@ SRAID defines:
9
13
  - a deterministic canonical object serializer used as input to hashing and signing,
10
14
  - a content-derived **OID** (`sha256:` followed by 64 hex chars) over the canonical bytes,
11
15
  - a hybrid **Ed25519 + ML-DSA-65** signature envelope,
12
- - an **L3 Merkle-DAG lineage** layer (`prev` edge + typed `links[]`), hashed into the OID so a head id commits its whole reachable history, and
13
- - a supersession record, the **SRO** (Supersession Record Object), that links a successor CDRO to its predecessor.
14
-
15
- The three legacy supersession mechanisms the self-asserted `supersedes`
16
- string, the standalone SRO, and the `prev` edge are unified by the lineage
17
- helpers (`lineageLinks`, `supersededOids`, `latestWins`). `latestWins` resolves
18
- a set of versions to its single head by following the identity-bound `prev`/
19
- `links` edges, giving a verifier a **latest-wins / monotone** rule instead of an
20
- unwitnessed pointer. Given the complete version set, `latestWins` will not name
21
- a superseded object as head; full rollback-replay resistance additionally
22
- requires that the superseding object cannot be withheld, which is a Resolver /
23
- transparency-log property (DESIGN, not yet deployed). The `set_complete` field
24
- on `LatestWinsResult` is the local signal that lets a caller detect an
25
- incomplete (potentially withheld) set.
26
-
27
- Higher layers Vault/Resolver (L1), Inference Broker/Resonance (L2), GAP (L3)
28
- are built on SRAID objects. This package is intentionally tiny and has no
29
- storage, no HTTP surface, and no governance logic.
16
+ - a **Merkle-DAG lineage** layer (`prev` edge + typed `links[]`), hashed into the OID so a head id commits its whole reachable history.
17
+
18
+ Supersession is expressed by two things, both identity-bound because
19
+ `cdroContentCore` hashes them: the `prev`/`links[]` Merkle edges, and the legacy
20
+ self-asserted `supersedes` string. `latestWins` resolves a version set to its
21
+ single head by following the identity-bound edges, giving a verifier a
22
+ **latest-wins / monotone** rule instead of an unwitnessed pointer. Given the
23
+ complete version set it will not name a superseded object as head; full
24
+ rollback-replay resistance additionally requires that the superseding object
25
+ cannot be withheld, which is a Resolver / transparency-log property (DESIGN, not
26
+ yet deployed). `set_complete` on `LatestWinsResult` is the local signal letting a
27
+ caller detect an incomplete (potentially withheld) set.
28
+
29
+ The standalone **SRO** was removed in 0.4.0 — see the CHANGELOG.
30
+
31
+ This package is object identity: canonical bytes, OIDs, hybrid signature
32
+ verification, lineage. It has no storage, no HTTP surface, and no policy.
33
+
34
+ It verifies **signatures, not identities**: `signer_kid` is an opaque string this
35
+ package never resolves, and nothing here checks revocation. Deciding whether a
36
+ key was trusted at signing time needs a key directory, which is a caller
37
+ concern.
30
38
 
31
39
  ## Install
32
40
 
@@ -39,63 +47,101 @@ npm install @synoi/sraid
39
47
  ```ts
40
48
  import {
41
49
  canonicalize,
42
- oidOf,
43
- verifySignature,
50
+ cdroOid,
51
+ cdroContentCore,
52
+ pae,
53
+ verifyAttestation,
44
54
  validateCdro,
55
+ type AttestationEnvelope,
45
56
  type CDRO,
46
- type SignatureEnvelope,
47
57
  } from '@synoi/sraid'
48
58
  import { ed25519 } from '@noble/curves/ed25519'
49
59
  import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js'
50
60
  import { randomBytes } from 'node:crypto'
51
61
 
52
- // 1. Build a CDRO body and derive its OID.
53
- const body = { capability: 'door.unlock', risk_class: 'B' }
54
- const oid = oidOf(body) // sha256:<64 hex chars>
55
-
56
- const cdro: CDRO<typeof body> = {
57
- oid,
62
+ // 1. Build the CDRO content core: every field EXCEPT the detached
63
+ // envelope fields (oid, signature, attestation, ...).
64
+ const core = {
58
65
  type: 'gap:capability_declaration',
59
- sraid_version: '2.0',
66
+ sraid_version: '2.0' as const,
60
67
  tenant_id: 't-home',
61
68
  created_at_ms: Date.now(),
62
69
  created_by: 'actor:skill:demo',
63
- body,
70
+ body: { capability: 'door.unlock', risk_class: 'B' },
64
71
  }
65
72
 
66
- console.log(validateCdro(cdro)) // { ok: true, errors: [] }
73
+ // 2. Identity is the hash of the WHOLE content core, not just the body.
74
+ // Use cdroOid(core) — NOT oidOf(core.body).
75
+ const cdro: CDRO<typeof core.body> = { oid: cdroOid(core), ...core }
76
+
77
+ console.log(validateCdro(cdro)) // { ok: true, errors: [] }
78
+ console.log(cdro.oid === cdroOid(cdro)) // true
67
79
 
68
- // 2. Sign the canonical bytes with both Ed25519 and ML-DSA-65.
69
- const canonical = canonicalize(cdro.body)
70
- const message = new TextEncoder().encode(canonical)
80
+ // 3. Sign the canonical content core through the DSSE PAE, which binds
81
+ // payloadType into the signed bytes (prevents cross-type replay).
82
+ const payloadType = 'application/vnd.synoi.sraid+json'
83
+ const payload = canonicalize(cdroContentCore(cdro))
84
+ const signedBytes = pae(payloadType, payload)
71
85
 
72
86
  const edPriv = new Uint8Array(randomBytes(32))
73
87
  const edPub = ed25519.getPublicKey(edPriv)
74
88
  const mlKeys = ml_dsa65.keygen(new Uint8Array(randomBytes(32)))
75
89
 
76
- const envelope: SignatureEnvelope = {
77
- ed25519: Buffer.from(ed25519.sign(message, edPriv)).toString('base64'),
78
- ml_dsa_65: Buffer.from(ml_dsa65.sign(message, mlKeys.secretKey)).toString('base64'),
79
- signer_kid: 'synoi-demo-2026-05',
90
+ const attestation: AttestationEnvelope = {
91
+ payloadType,
92
+ payload,
93
+ signatures: [
94
+ {
95
+ alg: 'ed25519',
96
+ sig: Buffer.from(ed25519.sign(signedBytes, edPriv)).toString('base64'),
97
+ keyid: 'synoi-demo-2026-05',
98
+ },
99
+ {
100
+ alg: 'ml-dsa-65',
101
+ sig: Buffer.from(ml_dsa65.sign(signedBytes, mlKeys.secretKey)).toString('base64'),
102
+ keyid: 'synoi-demo-2026-05',
103
+ },
104
+ ],
80
105
  }
81
106
 
82
- // 3. Verify.
83
- const v = verifySignature({
84
- canonical,
85
- envelope,
107
+ // 4. Attaching the attestation does NOT change identity — the six detached
108
+ // envelope fields are stripped before hashing.
109
+ const attested = { ...cdro, attestation }
110
+ console.log(cdroOid(attested) === cdro.oid) // true
111
+
112
+ // 5. Verify. `valid` is true only when BOTH signatures verify over the PAE.
113
+ const v = verifyAttestation({
114
+ envelope: attestation,
86
115
  ed25519_pub: edPub,
87
116
  ml_dsa_pub: mlKeys.publicKey,
117
+ expectedPayloadType: payloadType,
88
118
  })
89
- console.log(v) // { valid: true, reasons: [] }
119
+ console.log(v) // { valid: true, reasons: [] }
90
120
  ```
91
121
 
122
+ > **Identity is over the content core, not the body.** `oidOf(cdro.body)` is
123
+ > *not* a CDRO's OID; use `cdroOid`. `verifySignature` / `SignatureEnvelope`
124
+ > (bare-bytes, no payload-type binding) remain exported for compatibility but
125
+ > are superseded by the attestation path above.
126
+
92
127
  ## Surface
93
128
 
94
129
  ```ts
95
130
  canonicalize(value: unknown): string
131
+
132
+ // CDRO identity — hash the content core. THIS is an object's OID.
133
+ cdroOid(cdro: unknown): string
134
+ cdroContentCore(cdro: unknown): Record<string, unknown>
135
+ CDRO_ENVELOPE_FIELDS: readonly string[] // the six stripped fields
136
+
137
+ // Value-level hashing. oidOf(cdro.body) is NOT the CDRO's OID.
96
138
  oidOf(canonical: unknown): string
97
139
  oidOfCanonical(canonical: string | Uint8Array): string
98
140
 
141
+ // DSSE attestation — payload-type bound, both signatures required.
142
+ verifyAttestation(input): { valid: boolean; reasons: string[] }
143
+ pae(payloadType: string, payload: string | Uint8Array): Uint8Array
144
+
99
145
  verifySignature(args: {
100
146
  canonical: string | Uint8Array
101
147
  envelope: SignatureEnvelope
@@ -109,13 +155,69 @@ validateSignatureEnvelope(x: unknown): { ok: boolean; errors: string[] }
109
155
 
110
156
  // Types
111
157
  interface CDRO<TBody> { /* envelope */ }
112
- interface SRO { /* supersession record */ }
113
158
  interface SignatureEnvelope { ed25519: string; ml_dsa_65: string; signer_kid: string }
114
159
  ```
115
160
 
161
+ ## Browser, Chrome extension, service worker
162
+
163
+ The default (`.`) entry statically imports `node:crypto` in three places
164
+ (`ed25519.ts`, `mldsa.ts`, `oid.ts`), so `import '@synoi/sraid'` breaks any
165
+ browser bundle. Import the `./verify-browser` subpath instead. It carries exactly
166
+ what a v2 hybrid DSSE receipt verifier needs, with no static `node:crypto`
167
+ anywhere in its graph.
168
+
169
+ ```ts
170
+ import { verifyAttestation, canonicalize, cdroContentCore } from '@synoi/sraid/verify-browser'
171
+
172
+ const v = await verifyAttestation({
173
+ envelope: receipt.attestation,
174
+ ed25519_pub, // raw 32 bytes
175
+ ml_dsa_pub, // raw 1952 bytes
176
+ expectedPayloadType: 'application/vnd.synoi.gap+json',
177
+ })
178
+ // { valid: true, reasons: [] } only when BOTH signatures verify over the PAE
179
+ ```
180
+
181
+ ```ts
182
+ // The subpath surface. Four of these are ASYNC where the node entry is sync,
183
+ // because WebCrypto is Promise-based. Everything else is shared byte-for-byte
184
+ // with the node entry, so the two agree on the same input.
185
+ verifyAttestation(input): Promise<{ valid: boolean; reasons: string[] }> // ASYNC
186
+ cdroOid(cdro: unknown): Promise<string> // ASYNC
187
+ oidOf(canonical: unknown): Promise<string> // ASYNC
188
+ oidOfCanonical(canonical: string | Uint8Array): Promise<string> // ASYNC
189
+
190
+ canonicalize(value: unknown): string // pure, identical to the node entry
191
+ cdroContentCore(cdro: unknown): Record<string, unknown>
192
+ pae(payloadType: string, payload: string | Uint8Array): Uint8Array
193
+ CDRO_ENVELOPE_FIELDS: readonly string[]
194
+ ALG_ED25519, ALG_ML_DSA_65
195
+ ```
196
+
197
+ Crypto backends, since browsers have no native ML-DSA and no `node:crypto`:
198
+ Ed25519 verify on WebCrypto (RFC 8032 cofactored, matching the node path) with a
199
+ `@noble/curves` fallback below the WebCrypto-Ed25519 support floor; ML-DSA-65
200
+ verify on `@noble/post-quantum`; SHA-256 on WebCrypto `subtle.digest`.
201
+
202
+ The node default entry is unchanged and keeps its synchronous `node:crypto` fast
203
+ paths. This subpath is purely additive. Added in 0.3.0; earlier versions export
204
+ only `.` and `./canonicalize`.
205
+
206
+ ## Authority verification
207
+
208
+ Capability-grant and delegation-chain verification is NOT in this package. It is
209
+ authorization policy, not object identity, and it moved to
210
+ [`@synoi/authority-verify`](https://github.com/synoi/synoi-authority-verify) in
211
+ 0.4.0.
212
+
213
+ `capabilityCovers`, `AuthorityResolver` and `GrantStatus` did NOT move - they are
214
+ a pure string predicate and a live-status contract, and remain in the surface
215
+ above.
216
+
116
217
  ## Canonical form
117
218
 
118
- The canonical form is recursive JCS-lite JSON:
219
+ The canonical form is a strict RFC 8785 (JCS) profile, tested against the
220
+ RFC 8785 vectors (`test/rfc8785-conformance.test.ts`):
119
221
 
120
222
  - primitives via `JSON.stringify`,
121
223
  - objects emit keys in lexicographic order,
@@ -125,7 +227,7 @@ The canonical form is recursive JCS-lite JSON:
125
227
 
126
228
  This canonical form is a wire contract. **Any byte-level change to the
127
229
  serializer changes every OID and invalidates every previously-created
128
- signature.** Treat it as frozen for SRAID v1.0; evolve it only via a new,
230
+ signature.** Treat it as frozen for SRAID v2.0; evolve it only via a new,
129
231
  explicitly versioned canonical profile.
130
232
 
131
233
  ## OID format
@@ -134,8 +236,12 @@ explicitly versioned canonical profile.
134
236
  OID = "sha256:" + lowercase_hex( sha256( canonicalize(content) ) )
135
237
  ```
136
238
 
137
- The hash input is the OBJECT MINUS its own `oid` and `signature` fields, so
138
- signature rotation does not change the OID.
239
+ The hash input is the object MINUS the six detached envelope fields
240
+ (`oid`, `signature`, `ml_dsa_signature`, `signature_key_id`,
241
+ `signature_algorithm`, `attestation`) — see `CDRO_ENVELOPE_FIELDS`. Everything
242
+ else is hashed, including `authority`, `sensitivity`, `prev`, `links` and
243
+ `supersedes`, so those are identity-bound and cannot be silently stripped.
244
+ Signing or rotating a signature never changes the OID.
139
245
 
140
246
  ## Why hybrid signatures
141
247
 
@@ -146,4 +252,4 @@ in this package returns `valid: true` only when BOTH succeed.
146
252
 
147
253
  ## License
148
254
 
149
- MIT see [`LICENSE`](./LICENSE).
255
+ MIT, see [`LICENSE`](./LICENSE).
package/SPEC.md CHANGED
@@ -1,4 +1,4 @@
1
- # SRAID - Self-Routing Addressable Identity Data, Core Object Specification
1
+ # SRAID - Core Object Specification
2
2
 
3
3
  **Core Specification, Version 1.0**
4
4
 
@@ -38,7 +38,7 @@ A SRAID object (the Canonical Data Record Object, or **CDRO**) is a map with the
38
38
 
39
39
  The `oid`, `attestation`, and `signature` members are excluded from the OID input (Section 4) so that the same content produces the same OID and re-signing never changes identity. All other members are part of the canonical content and therefore contribute to the OID.
40
40
 
41
- **Lineage fields are independent.** An object MAY carry `supersedes`, `prev`, and one or more `links[]` simultaneously. `supersedes` asserts a version-replacement relationship (this object is the authoritative successor of the referenced OID); `prev` is the hash-chain backpointer for ordered append; `links[]` are typed semantic edges (L3). Each is validated for canonical-OID format in isolation. The validator does NOT enforce agreement among them (for example it does not require `supersedes === prev`), nor does it resolve or fetch the referenced objects. Higher layers (Vault/Resolver, GAP) are responsible for any cross-field lineage semantics. The signed supersession receipt (SRO, type `sraid:sro`) is distinct from the self-asserted `supersedes` field on a CDRO: the SRO is an independently signed object authored by a governing actor; `supersedes` is a producer-asserted claim inside the envelope. Both fields are format-validated but neither implies the other.
41
+ **Lineage fields are independent.** An object MAY carry `supersedes`, `prev`, and one or more `links[]` simultaneously. `supersedes` asserts a version-replacement relationship (this object is the authoritative successor of the referenced OID); `prev` is the hash-chain backpointer for ordered append; `links[]` are typed semantic edges (L3). Each is validated for canonical-OID format in isolation. The validator does NOT enforce agreement among them (for example it does not require `supersedes === prev`), nor does it resolve or fetch the referenced objects. Higher layers (Vault/Resolver, GAP) are responsible for any cross-field lineage semantics. The standalone supersession receipt (SRO, type `sraid:sro`) was REMOVED in 0.4.0: nothing in any language consumed it, `lineage.ts` never handled it, and the canon had already retired the term (ADR_022 section 0). A caller recording WHY something was superseded should put that in the successor's own `body`, where it is hashed and signed.
42
42
 
43
43
  ## 3. Canonical form
44
44
 
@@ -30,57 +30,16 @@
30
30
  * Identity stability: signatures live in a detached `signatures[]` array and
31
31
  * are NEVER part of the OID hash input (see `cdroContentCore` in oid.ts), so
32
32
  * adding, rotating, or replacing a signature never changes the object's OID.
33
- */
34
- import type { AttestationEnvelope } from './types.js';
35
- /** The single supported algorithm identifiers for the JSON profile. */
36
- export declare const ALG_ED25519 = "ed25519";
37
- export declare const ALG_ML_DSA_65 = "ml-dsa-65";
38
- /**
39
- * The DSSE Pre-Authentication Encoding (PAE).
40
- *
41
- * Per the DSSE spec:
42
- *
43
- * PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body
44
- *
45
- * where:
46
- * - SP is a single ASCII space (0x20),
47
- * - LEN(x) is the ASCII-decimal byte length of x's UTF-8 encoding,
48
- * - type and body are the raw UTF-8 bytes (NOT base64).
49
33
  *
50
- * Binding the payloadType (and both lengths) into the signed bytes is what
51
- * makes a signature non-transferable across object types. Returns the raw
52
- * bytes to be signed/verified.
34
+ * NODE ENTRY: this module verifies via node:crypto (Ed25519 native, ML-DSA-65
35
+ * native-with-@noble-fallback) and is SYNCHRONOUS. The PAE, algorithm ids,
36
+ * types, AND-policy lookup, and envelope-shape predicate are shared with the
37
+ * browser verify surface via ./internal/attestation-core.ts (pure, no
38
+ * node:crypto); only the verify orchestration below is node-specific. The
39
+ * browser-safe async equivalent lives in ./verify-browser.ts.
53
40
  */
54
- export declare function pae(payloadType: string, payload: string | Uint8Array): Uint8Array;
55
- export interface VerifyAttestationInput {
56
- /**
57
- * The DSSE attestation envelope to verify. Its `payloadType` is bound into
58
- * the PAE; its `payload` is the canonical UTF-8 string that was signed.
59
- */
60
- envelope: AttestationEnvelope;
61
- /** Raw 32-byte Ed25519 public key. */
62
- ed25519_pub: Uint8Array;
63
- /** Raw ML-DSA-65 public key bytes. */
64
- ml_dsa_pub: Uint8Array;
65
- /**
66
- * Optional. If supplied, the verifier asserts the envelope's `payloadType`
67
- * equals this value before verifying signatures — an explicit type-pinning
68
- * check on top of the structural PAE binding. A mismatch fails with
69
- * `payload-type-mismatch`.
70
- */
71
- expectedPayloadType?: string;
72
- }
73
- export interface VerifyAttestationResult {
74
- /** True only when BOTH required signatures verified over the same PAE. */
75
- valid: boolean;
76
- /**
77
- * Human-readable failure reasons. Empty when valid. Possible values:
78
- * 'envelope-malformed', 'payload-type-mismatch', 'missing-ed25519',
79
- * 'missing-ml-dsa-65', 'ed25519-malformed', 'ml-dsa-malformed',
80
- * 'ed25519-invalid', 'ml-dsa-invalid'.
81
- */
82
- reasons: string[];
83
- }
41
+ import { ALG_ED25519, ALG_ML_DSA_65, pae, type VerifyAttestationInput, type VerifyAttestationResult } from './internal/attestation-core.js';
42
+ export { ALG_ED25519, ALG_ML_DSA_65, pae, type VerifyAttestationInput, type VerifyAttestationResult, };
84
43
  /**
85
44
  * Verify a hybrid DSSE attestation envelope. Returns `valid: true` only when
86
45
  * the envelope carries BOTH an `ed25519` and an `ml-dsa-65` signature and
@@ -1 +1 @@
1
- {"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAwB,MAAM,YAAY,CAAA;AAE3E,uEAAuE;AACvE,eAAO,MAAM,WAAW,YAAY,CAAA;AACpC,eAAO,MAAM,aAAa,cAAc,CAAA;AAExC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,CAYjF;AAED,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,sCAAsC;IACtC,WAAW,EAAE,UAAU,CAAA;IACvB,sCAAsC;IACtC,UAAU,EAAE,UAAU,CAAA;IACtB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAA;IACd;;;;;OAKG;IACH,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,uBAAuB,CAsExF"}
1
+ {"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAKH,OAAO,EACL,WAAW,EACX,aAAa,EAGb,GAAG,EACH,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC7B,MAAM,gCAAgC,CAAA;AAIvC,OAAO,EACL,WAAW,EACX,aAAa,EACb,GAAG,EACH,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,uBAAuB,CA+DxF"}
@@ -30,39 +30,21 @@
30
30
  * Identity stability: signatures live in a detached `signatures[]` array and
31
31
  * are NEVER part of the OID hash input (see `cdroContentCore` in oid.ts), so
32
32
  * adding, rotating, or replacing a signature never changes the object's OID.
33
+ *
34
+ * NODE ENTRY: this module verifies via node:crypto (Ed25519 native, ML-DSA-65
35
+ * native-with-@noble-fallback) and is SYNCHRONOUS. The PAE, algorithm ids,
36
+ * types, AND-policy lookup, and envelope-shape predicate are shared with the
37
+ * browser verify surface via ./internal/attestation-core.ts (pure, no
38
+ * node:crypto); only the verify orchestration below is node-specific. The
39
+ * browser-safe async equivalent lives in ./verify-browser.ts.
33
40
  */
34
41
  import { verifyEd25519 } from './ed25519.js';
35
42
  import { decodeBase64Strict } from './internal/base64.js';
36
43
  import { verifyMlDsa65 } from './mldsa.js';
37
- /** The single supported algorithm identifiers for the JSON profile. */
38
- export const ALG_ED25519 = 'ed25519';
39
- export const ALG_ML_DSA_65 = 'ml-dsa-65';
40
- /**
41
- * The DSSE Pre-Authentication Encoding (PAE).
42
- *
43
- * Per the DSSE spec:
44
- *
45
- * PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body
46
- *
47
- * where:
48
- * - SP is a single ASCII space (0x20),
49
- * - LEN(x) is the ASCII-decimal byte length of x's UTF-8 encoding,
50
- * - type and body are the raw UTF-8 bytes (NOT base64).
51
- *
52
- * Binding the payloadType (and both lengths) into the signed bytes is what
53
- * makes a signature non-transferable across object types. Returns the raw
54
- * bytes to be signed/verified.
55
- */
56
- export function pae(payloadType, payload) {
57
- const enc = new TextEncoder();
58
- const typeBytes = enc.encode(payloadType);
59
- const bodyBytes = typeof payload === 'string' ? enc.encode(payload) : payload;
60
- const prefix = enc.encode(`DSSEv1 ${typeBytes.length} ${payloadType} ${bodyBytes.length} `);
61
- const out = new Uint8Array(prefix.length + bodyBytes.length);
62
- out.set(prefix, 0);
63
- out.set(bodyBytes, prefix.length);
64
- return out;
65
- }
44
+ import { ALG_ED25519, ALG_ML_DSA_65, findSig, isWellFormedEnvelope, pae, } from './internal/attestation-core.js';
45
+ // Re-export the crypto-agnostic core so the public surface is unchanged:
46
+ // `import { pae, ALG_ED25519, ALG_ML_DSA_65 } from '@synoi/sraid'` still works.
47
+ export { ALG_ED25519, ALG_ML_DSA_65, pae, };
66
48
  /**
67
49
  * Verify a hybrid DSSE attestation envelope. Returns `valid: true` only when
68
50
  * the envelope carries BOTH an `ed25519` and an `ml-dsa-65` signature and
@@ -73,12 +55,7 @@ export function pae(payloadType, payload) {
73
55
  export function verifyAttestation(input) {
74
56
  const reasons = [];
75
57
  const env = input.envelope;
76
- if (env === null ||
77
- typeof env !== 'object' ||
78
- typeof env.payloadType !== 'string' ||
79
- env.payloadType.length === 0 ||
80
- typeof env.payload !== 'string' ||
81
- !Array.isArray(env.signatures)) {
58
+ if (!isWellFormedEnvelope(env)) {
82
59
  return { valid: false, reasons: ['envelope-malformed'] };
83
60
  }
84
61
  if (input.expectedPayloadType !== undefined &&
@@ -137,18 +114,10 @@ export function verifyAttestation(input) {
137
114
  return { valid: edOk && mlOk && !!edEntry && !!mlEntry, reasons };
138
115
  }
139
116
  // ── Helpers ───────────────────────────────────────────────────────────────────
140
- function findSig(sigs, alg) {
141
- for (const s of sigs) {
142
- if (s && typeof s === 'object' && s.alg === alg && typeof s.sig === 'string') {
143
- return s;
144
- }
145
- }
146
- return undefined;
147
- }
148
117
  // Strict standard base64. Throws Error('base64-malformed') on any deviation
149
- // (illegal char, bad length, bad padding) rather than silently truncating.
150
- // Both call sites wrap this in try/catch and map the throw to a *-malformed
151
- // reason, so verifyAttestation never throws.
118
+ // (illegal char, bad length, bad padding) rather than silently truncating. The
119
+ // call sites wrap this in try/catch and map the throw to a *-malformed reason,
120
+ // so verifyAttestation never throws.
152
121
  function fromBase64(s) {
153
122
  return decodeBase64Strict(s);
154
123
  }
@@ -1 +1 @@
1
- {"version":3,"file":"attestation.js","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG1C,uEAAuE;AACvE,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAA;AACpC,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAA;AAExC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,GAAG,CAAC,WAAmB,EAAE,OAA4B;IACnE,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAA;IAC7B,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACzC,MAAM,SAAS,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;IAE7E,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CACvB,UAAU,SAAS,CAAC,MAAM,IAAI,WAAW,IAAI,SAAS,CAAC,MAAM,GAAG,CACjE,CAAA;IACD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;IAC5D,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAClB,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACjC,OAAO,GAAG,CAAA;AACZ,CAAC;AAiCD;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAA6B;IAC7D,MAAM,OAAO,GAAa,EAAE,CAAA;IAE5B,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAA;IAC1B,IACE,GAAG,KAAK,IAAI;QACZ,OAAO,GAAG,KAAK,QAAQ;QACvB,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ;QACnC,GAAG,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC;QAC5B,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;QAC/B,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAC9B,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,oBAAoB,CAAC,EAAE,CAAA;IAC1D,CAAC;IAED,IACE,KAAK,CAAC,mBAAmB,KAAK,SAAS;QACvC,GAAG,CAAC,WAAW,KAAK,KAAK,CAAC,mBAAmB,EAC7C,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,uBAAuB,CAAC,EAAE,CAAA;IAC7D,CAAC;IAED,8DAA8D;IAC9D,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IAEjD,uEAAuE;IACvE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,aAAa,CAAC,CAAA;IAEtD,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IAC7C,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;IAE/C,IAAI,IAAI,GAAG,KAAK,CAAA;IAChB,IAAI,IAAI,GAAG,KAAK,CAAA;IAEhB,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,KAAK,GAAsB,IAAI,CAAA;QACnC,IAAI,CAAC;YACH,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;QACnC,CAAC;QACD,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC;gBACH,IAAI,GAAG,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;YACzD,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,GAAG,KAAK,CAAA;YACd,CAAC;YACD,IAAI,CAAC,IAAI;gBAAE,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;QAC5C,CAAC;IACH,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,KAAK,GAAsB,IAAI,CAAA;QACnC,IAAI,CAAC;YACH,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;QAClC,CAAC;QACD,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC;gBACH,IAAI,GAAG,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;YACxD,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,GAAG,KAAK,CAAA;YACd,CAAC;YACD,IAAI,CAAC,IAAI;gBAAE,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,IAAI,IAAI,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,CAAA;AACnE,CAAC;AAED,iFAAiF;AAEjF,SAAS,OAAO,CACd,IAAqC,EACrC,GAAW;IAEX,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC7E,OAAO,CAAC,CAAA;QACV,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,4EAA4E;AAC5E,2EAA2E;AAC3E,4EAA4E;AAC5E,6CAA6C;AAC7C,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAA;AAC9B,CAAC"}
1
+ {"version":3,"file":"attestation.js","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @synoi/sraid — capability.ts
3
+ *
4
+ * Capability-pattern matching and the live-status contract.
5
+ *
6
+ * These stay in the core entry, while the grant/delegation VERIFIERS moved to
7
+ * `@synoi/sraid/authority` in 0.4.0, because they are different things:
8
+ *
9
+ * - `capabilityCovers` is a pure, total string predicate with no crypto, no
10
+ * I/O and no policy. It decides whether one dotted-taxonomy pattern covers
11
+ * another, and it is the leaf primitive that grant stores build on
12
+ * (synoi-app's `in-memory-engine.ts` and `app-store/store.ts` both call it
13
+ * directly rather than going through a verifier).
14
+ * - `GrantStatus` / `AuthorityResolver` are the CONTRACT for live grant
15
+ * state. The contract is a type; satisfying it is somebody else's job.
16
+ * Keeping it here lets a store, a resolver and a verifier agree on shape
17
+ * without any of them depending on the verifier.
18
+ *
19
+ * Nothing in this module makes, or can make, a live claim.
20
+ */
21
+ /**
22
+ * Match a capability `target` against a grant `pattern`. Pure string logic,
23
+ * re-stated here so the core stays dependency-free (the same rule lives in
24
+ * `@synoi/gap-types` `capabilityMatches`; the object layer must not depend on
25
+ * the protocol layer).
26
+ *
27
+ * - exact match → true
28
+ * - '*' → match-all
29
+ * - 'skill.*' matches 'skill.create' and deeper (segment-boundary only).
30
+ * A non-boundary 'admin.us*' must NOT match 'admin.users.delete'
31
+ * (privilege-escalation footgun) — only a '.'-anchored '*' is a wildcard.
32
+ */
33
+ export declare function capabilityCovers(pattern: string, target: string): boolean;
34
+ /**
35
+ * The live-state interface that the OID Resolver implements. It is the ONLY
36
+ * source of truth for the two properties that cannot be checked offline:
37
+ * whether a grant currently exists (resolves) and whether it has been revoked.
38
+ *
39
+ * RESOLVER-DEPENDENT: undeployed today (SRAID_FOUNDATION_PUNCHLIST C). Defined
40
+ * here so callers, a future resolver, and the out-of-core verifier all agree
41
+ * on one contract.
42
+ */
43
+ export interface AuthorityResolver {
44
+ /**
45
+ * Resolve a grant OID to its current status. Implementations should return
46
+ * `{ exists: false }` for an unknown OID and `{ exists: true, revoked: true,
47
+ * revoked_at_ms }` for a revoked one.
48
+ */
49
+ resolveGrantStatus(grantOid: string): Promise<GrantStatus> | GrantStatus;
50
+ }
51
+ export interface GrantStatus {
52
+ /** Whether the grant OID resolves to a known, published grant. */
53
+ exists: boolean;
54
+ /** Whether the grant has been revoked. Only meaningful when `exists`. */
55
+ revoked?: boolean;
56
+ /** When the revocation took effect, if revoked. */
57
+ revoked_at_ms?: number;
58
+ }
59
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAQzE;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAAA;CACzE;AAED,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,MAAM,EAAE,OAAO,CAAA;IACf,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @synoi/sraid — capability.ts
3
+ *
4
+ * Capability-pattern matching and the live-status contract.
5
+ *
6
+ * These stay in the core entry, while the grant/delegation VERIFIERS moved to
7
+ * `@synoi/sraid/authority` in 0.4.0, because they are different things:
8
+ *
9
+ * - `capabilityCovers` is a pure, total string predicate with no crypto, no
10
+ * I/O and no policy. It decides whether one dotted-taxonomy pattern covers
11
+ * another, and it is the leaf primitive that grant stores build on
12
+ * (synoi-app's `in-memory-engine.ts` and `app-store/store.ts` both call it
13
+ * directly rather than going through a verifier).
14
+ * - `GrantStatus` / `AuthorityResolver` are the CONTRACT for live grant
15
+ * state. The contract is a type; satisfying it is somebody else's job.
16
+ * Keeping it here lets a store, a resolver and a verifier agree on shape
17
+ * without any of them depending on the verifier.
18
+ *
19
+ * Nothing in this module makes, or can make, a live claim.
20
+ */
21
+ /**
22
+ * Match a capability `target` against a grant `pattern`. Pure string logic,
23
+ * re-stated here so the core stays dependency-free (the same rule lives in
24
+ * `@synoi/gap-types` `capabilityMatches`; the object layer must not depend on
25
+ * the protocol layer).
26
+ *
27
+ * - exact match → true
28
+ * - '*' → match-all
29
+ * - 'skill.*' matches 'skill.create' and deeper (segment-boundary only).
30
+ * A non-boundary 'admin.us*' must NOT match 'admin.users.delete'
31
+ * (privilege-escalation footgun) — only a '.'-anchored '*' is a wildcard.
32
+ */
33
+ export function capabilityCovers(pattern, target) {
34
+ if (pattern === target)
35
+ return true;
36
+ if (pattern === '*')
37
+ return true;
38
+ if (pattern.endsWith('.*')) {
39
+ const prefix = pattern.slice(0, -1); // keep trailing '.', e.g. 'skill.'
40
+ return target.startsWith(prefix);
41
+ }
42
+ return false;
43
+ }
44
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,MAAc;IAC9D,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IACnC,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IAChC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA,CAAC,mCAAmC;QACvE,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;IAClC,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  /**
2
2
  * @synoi/sraid — public surface.
3
3
  *
4
- * SRAID (Self-Routing Addressable Identity Data) is L0 of the SynOI SRAID Stack:
4
+ * SRAID is the object-identity layer: it defines what a signed object IS and
5
+ * how anyone re-derives and verifies its identity offline.
5
6
  *
6
- * L0 SRAID ← this package
7
- * L1 Vault / Resolver
8
- * L2 Inference Broker / Resonance
9
- * L3 GAP (Governed Action Protocol)
10
- * L4 Apps
7
+ * Scope, precisely:
8
+ * IN canonical serializer, OID computation, hybrid Ed25519 + ML-DSA-65
9
+ * attestation verification, lineage resolution, shape validators.
10
+ * OUT storage, transport, key resolution, revocation, policy, enforcement.
11
11
  *
12
- * This package is intentionally small: types, canonical serializer,
13
- * OID computation, hybrid signature verification, and shape validators.
14
- * No storage, no HTTP, no governance those are higher layers.
12
+ * It verifies SIGNATURES, not IDENTITIES: `signer_kid` is an opaque string
13
+ * this package never resolves, and nothing here checks revocation. Deciding
14
+ * whether a key was trusted at signing time is a caller/directory concern.
15
15
  */
16
- export type { AttestationEnvelope, AttestationSignature, AuthorityBlock, AuthorityDecision, CDRO, LineageLink, LinkRel, SRO, SROBody, SensitivityTier, SignatureEnvelope, } from './types.js';
16
+ export type { AttestationEnvelope, AttestationSignature, AuthorityBlock, AuthorityDecision, CDRO, LineageLink, LinkRel, SensitivityTier, SignatureEnvelope, } from './types.js';
17
17
  export { canonicalize } from './canonicalize.js';
18
18
  export { oidOf, oidOfCanonical, cdroOid, cdroContentCore, CDRO_ENVELOPE_FIELDS } from './oid.js';
19
19
  export { verifySignature, type VerifySignatureInput, type VerifySignatureResult, } from './signature.js';
@@ -22,7 +22,7 @@ export { evictKeyFromCaches } from './internal/key-cache.js';
22
22
  export { verifyAttestation, pae, ALG_ED25519, ALG_ML_DSA_65, type VerifyAttestationInput, type VerifyAttestationResult, } from './attestation.js';
23
23
  export { lineageLinks, supersededOids, latestWins, type LatestWinsResult, } from './lineage.js';
24
24
  export { SENSITIVITY_TIERS, SENSITIVITY_DEFAULT, isSensitivityTier, sensitivityRank, sensitivityMax, sensitivityCarryForward, sensitivityMonotoneCheck, } from './sensitivity.js';
25
- export { verifyAuthority, capabilityCovers, type VerifyAuthorityInput, type VerifyAuthorityResult, type AuthorityResolver, type GrantStatus, type GrantBodyShape, } from './authority.js';
26
- export { verifyDelegationChain, MAX_DELEGATION_DEPTH, type VerifyDelegationChainInput, type VerifyDelegationChainResult, type HopResult, type LinkPubkeys, } from './authority.js';
27
- export { validateCdro, validateSro, validateSignatureEnvelope, validateAttestationEnvelope, validateAuthorityBlock, validateLineageLink, type ValidationResult, } from './validate.js';
25
+ export { capabilityCovers } from './capability.js';
26
+ export type { AuthorityResolver, GrantStatus } from './capability.js';
27
+ export { validateCdro, validateSignatureEnvelope, validateAttestationEnvelope, validateAuthorityBlock, validateLineageLink, type ValidationResult, } from './validate.js';
28
28
  //# sourceMappingURL=index.d.ts.map