@kubun/store-credential 0.17.4 → 0.18.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.
package/lib/definition.js CHANGED
@@ -3,5 +3,7 @@ import { getCredentialMigrations } from './migrations.js';
3
3
  export const credentialStoreDefinition = {
4
4
  name: 'credential',
5
5
  migrations: getCredentialMigrations,
6
- createAPI: createCredentialStore
6
+ // Only the db and adapter: KubunDB passes a store context third, and this
7
+ // factory's third parameter means something else.
8
+ createAPI: (db, adapter)=>createCredentialStore(db, adapter)
7
9
  };
package/lib/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import type { StoreProvider } from '@kubun/db';
2
2
  import type { CredentialStoreAPI } from './api.js';
3
- export type { CredentialManifestRecord, CredentialOpInput, CredentialStoreAPI, CredentialTombstoneInput, HeldCredentialSubject, ListManifestsParams, NextGrantorSequenceParams, PutCredentialEntryParams, PutCredentialKeyParams, PutCredentialWrappingParams, PutWrappingAuthorityParams, StoredCredentialManifest, WrappingAuthority, } from './api.js';
3
+ export type { CredentialAliasKey, CredentialDeviceAliasAPI, CredentialManifestRecord, CredentialStoreAPI, DeleteDeviceAliasIfEntryParams, ListDeviceAliasOwnersParams, ListManifestsParams, NextGrantorSequenceParams, PutCredentialEntryParams, PutCredentialKeyParams, PutCredentialManifestParams, PutCredentialWrappingParams, PutWrappingAuthorityParams, SetDeviceAliasParams, StoredCredentialManifest, UpdateStoredEntryIfUnchangedParams, WrappingAuthority, } from './api.js';
4
4
  export { createCredentialStore, UNBOUNDED_REFRESH_SEQ } from './api.js';
5
5
  export { credentialStoreDefinition } from './definition.js';
6
+ export { CREDENTIAL_RECORD_TYPES, type CredentialRecordAdapterOptions, CredentialRecordError, type CredentialRecordMetas, CredentialRecordPayloadError, type CredentialRecordPayloads, CredentialRecordRowError, CredentialRecordSignatureError, type CredentialRecordType, credentialManifestRecordID, credentialRecordAdapter, credentialRecordFromRow, credentialRevocationRecordID, credentialStoreOf, type DecodedCredentialRecord, decodeCredentialRecord, encodeCredentialRecordPayload, } from './records.js';
6
7
  export declare const CREDENTIAL_STORE: "credential";
7
8
  export declare function getCredentialStore(provider: StoreProvider): Promise<CredentialStoreAPI>;
8
- export type { CredentialAlias, CredentialEntry, CredentialEntryTable, CredentialEntryWithProvenance, CredentialFactorDescriptor, CredentialGrantorCounter, CredentialGrantorCounterTable, CredentialKey, CredentialKeyBranch, CredentialKeyBranchTable, CredentialKeyOwnerDelegation, CredentialKeyOwnerDelegationTable, CredentialKeyState, CredentialKeyTable, CredentialManifest, CredentialManifestKey, CredentialManifestTable, CredentialOp, CredentialOpSubjectKind, CredentialOpTable, CredentialTables, CredentialWrapping, CredentialWrappingAuthority, CredentialWrappingAuthorityTable, CredentialWrappingTable, CredentialWrappingWithBranch, InsertCredentialAlias, InsertCredentialEntry, InsertCredentialGrantorCounter, InsertCredentialKey, InsertCredentialKeyOwnerDelegation, InsertCredentialManifest, InsertCredentialOp, InsertCredentialWrapping, InsertCredentialWrappingAuthority, KubunCredentialAliasTable, } from './tables.js';
9
+ export type { CredentialDeviceAlias, CredentialDeviceAliasTable, CredentialEntry, CredentialEntryCandidateTable, CredentialEntryTable, CredentialEntryWithProvenance, CredentialFactorDescriptor, CredentialGrantorCounter, CredentialGrantorCounterTable, CredentialKey, CredentialKeyBranch, CredentialKeyBranchTable, CredentialKeyOwnerDelegation, CredentialKeyOwnerDelegationTable, CredentialKeyState, CredentialKeyTable, CredentialManifest, CredentialManifestKey, CredentialManifestTable, CredentialRecordRow, CredentialRecordTable, CredentialRevocation, CredentialRevocationTable, CredentialTables, CredentialWrapping, CredentialWrappingAuthority, CredentialWrappingAuthorityTable, CredentialWrappingTable, CredentialWrappingWithBranch, InsertCredentialEntry, InsertCredentialGrantorCounter, InsertCredentialKey, InsertCredentialKeyOwnerDelegation, InsertCredentialManifest, InsertCredentialWrapping, InsertCredentialWrappingAuthority, } from './tables.js';
package/lib/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { createCredentialStore, UNBOUNDED_REFRESH_SEQ } from './api.js';
2
2
  export { credentialStoreDefinition } from './definition.js';
3
+ export { CREDENTIAL_RECORD_TYPES, CredentialRecordError, CredentialRecordPayloadError, CredentialRecordRowError, CredentialRecordSignatureError, credentialManifestRecordID, credentialRecordAdapter, credentialRecordFromRow, credentialRevocationRecordID, credentialStoreOf, decodeCredentialRecord, encodeCredentialRecordPayload } from './records.js';
3
4
  export const CREDENTIAL_STORE = 'credential';
4
5
  export function getCredentialStore(provider) {
5
6
  return provider.getStore(CREDENTIAL_STORE);
package/lib/migrations.js CHANGED
@@ -4,9 +4,13 @@ export function getCredentialMigrations(ctx) {
4
4
  const init = {
5
5
  async up (db) {
6
6
  // Credential keys: public record only, no key material.
7
- await db.schema.createTable('kubun_credential_keys').ifNotExists().addColumn('key_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('suite', 'integer', (col)=>col.notNull()).addColumn('version', 'integer', (col)=>col.notNull()).addColumn('state', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
7
+ await db.schema.createTable('kubun_credential_keys').ifNotExists().addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('suite', 'integer', (col)=>col.notNull()).addColumn('version', 'integer', (col)=>col.notNull()).addColumn('state', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_keys', [
8
+ 'owner_did',
9
+ 'key_id'
10
+ ]).execute();
8
11
  // Key-op history is retained so a branch label can be re-authenticated.
9
- await db.schema.createTable('kubun_credential_key_branches').ifNotExists().addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('key_version', 'integer', (col)=>col.notNull()).addColumn('op_hash', t.text, (col)=>col.notNull()).addColumn('author_did', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('op_jwt', t.text, (col)=>col.notNull()).addColumn('branch_id', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_key_branches', [
12
+ await db.schema.createTable('kubun_credential_key_branches').ifNotExists().addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('key_version', 'integer', (col)=>col.notNull()).addColumn('op_hash', t.text, (col)=>col.notNull()).addColumn('author_did', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('op_jwt', t.text, (col)=>col.notNull()).addColumn('branch_id', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_key_branches', [
13
+ 'owner_did',
10
14
  'key_id',
11
15
  'key_version',
12
16
  'op_hash'
@@ -14,45 +18,30 @@ export function getCredentialMigrations(ctx) {
14
18
  // Wrappings: one row per way of opening one version of one key. Keyed on
15
19
  // its own id rather than (key, recipient) — a key can carry several
16
20
  // wrappings for the same recipient, differing in their factor lists.
17
- await db.schema.createTable('kubun_credential_wrappings').ifNotExists().addColumn('wrapping_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('key_version', 'integer', (col)=>col.notNull()).addColumn('factors', t.json, (col)=>col.notNull()).addColumn('iv', t.binary, (col)=>col.notNull()).addColumn('wrapped_key', t.binary, (col)=>col.notNull()).addColumn('recipient_did', t.text).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
21
+ await db.schema.createTable('kubun_credential_wrappings').ifNotExists().addColumn('wrapping_id', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('key_version', 'integer', (col)=>col.notNull()).addColumn('factors', t.json, (col)=>col.notNull()).addColumn('iv', t.binary, (col)=>col.notNull()).addColumn('wrapped_key', t.binary, (col)=>col.notNull()).addColumn('recipient_did', t.text)// The branch of the op that wrote the row, so a fork-aware read needs no op row.
22
+ .addColumn('branch_id', t.text, (col)=>col.notNull().defaultTo('')).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_wrappings', [
23
+ 'owner_did',
24
+ 'wrapping_id'
25
+ ]).execute();
18
26
  await db.schema.createIndex('idx_kubun_credential_wrappings_key').ifNotExists().on('kubun_credential_wrappings').columns([
27
+ 'owner_did',
19
28
  'key_id',
20
29
  'key_version'
21
30
  ]).execute();
22
31
  // Entries: opaque ciphertext under a key version.
23
- await db.schema.createTable('kubun_credential_entries').ifNotExists().addColumn('entry_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('key_version', 'integer', (col)=>col.notNull()).addColumn('iv', t.binary, (col)=>col.notNull()).addColumn('ciphertext', t.binary, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).execute();
24
- await db.schema.createIndex('idx_kubun_credential_entries_key').ifNotExists().on('kubun_credential_entries').columns([
25
- 'key_id'
32
+ await db.schema.createTable('kubun_credential_entries').ifNotExists().addColumn('entry_id', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('key_version', 'integer', (col)=>col.notNull()).addColumn('iv', t.binary, (col)=>col.notNull()).addColumn('ciphertext', t.binary, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('branch_id', t.text, (col)=>col.notNull().defaultTo('')).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_entries', [
33
+ 'owner_did',
34
+ 'entry_id'
26
35
  ]).execute();
27
- // Ops: the latest signed op per subject, and the only place a deletion is
28
- // recorded. Keyed on (kind, id) rather than on the subject id alone —
29
- // nothing guarantees a key id cannot equal an entry id, and a collision
30
- // would let one subject's op tombstone another's.
31
- await db.schema.createTable('kubun_credential_ops').ifNotExists().addColumn('subject_kind', t.text, (col)=>col.notNull()).addColumn('subject_id', t.text, (col)=>col.notNull()).addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('author_did', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('op_jwt', t.text, (col)=>col.notNull()).addColumn('op_hash', t.text, (col)=>col.notNull())// The content-key branch this op joins. No index yet: partitioning by
32
- // branch is a later slice, and the merkle scan still orders by hlc.
33
- .addColumn('branch_id', t.text, (col)=>col.notNull().defaultTo('')).addColumn('deleted', 'integer', (col)=>col.notNull().defaultTo(0)).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_ops', [
34
- 'subject_kind',
35
- 'subject_id'
36
- ]).execute();
37
- await db.schema.createIndex('idx_kubun_credential_ops_key').ifNotExists().on('kubun_credential_ops').columns([
36
+ await db.schema.createIndex('idx_kubun_credential_entries_key').ifNotExists().on('kubun_credential_entries').columns([
37
+ 'owner_did',
38
38
  'key_id'
39
39
  ]).execute();
40
- // The merkle lane buckets leaves by time and reads them in stamp order, so
41
- // the scan that builds a tree is this index rather than a sort of the table.
42
- await db.schema.createIndex('idx_kubun_credential_ops_hlc').ifNotExists().on('kubun_credential_ops').columns([
43
- 'hlc'
44
- ]).execute();
45
- // A plain local pointer, not an op subject — no hlc, no ops row, no
46
- // merkle scan. Devices that resolve the same (namespace, key) converge on
47
- // it locally, so it carries nothing to replicate.
48
- await db.schema.createTable('kubun_credential_aliases').ifNotExists().addColumn('namespace', t.text, (col)=>col.notNull()).addColumn('alias_key', t.text, (col)=>col.notNull()).addColumn('entry_id', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_aliases', [
49
- 'namespace',
50
- 'alias_key'
51
- ]).addUniqueConstraint('uq_kubun_credential_aliases_entry', [
52
- 'entry_id'
53
- ]).execute();
54
40
  // Grantor-authority envelope for a wrapping; `refresh_seq` gates every upsert strictly-greater.
55
- await db.schema.createTable('kubun_credential_wrapping_authority').ifNotExists().addColumn('wrapping_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('authority_token', t.text, (col)=>col.notNull()).addColumn('refresh_seq', 'integer', (col)=>col.notNull()).execute();
41
+ await db.schema.createTable('kubun_credential_wrapping_authority').ifNotExists().addColumn('wrapping_id', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('authority_token', t.text, (col)=>col.notNull()).addColumn('refresh_seq', 'integer', (col)=>col.notNull()).addPrimaryKeyConstraint('pk_kubun_credential_wrapping_authority', [
42
+ 'owner_did',
43
+ 'wrapping_id'
44
+ ]).execute();
56
45
  // Persisted counter minting strictly-increasing refreshSeq, one per (owner, recipient, grantor) triple.
57
46
  await db.schema.createTable('kubun_credential_grantor_counters').ifNotExists().addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('recipient_did', t.text, (col)=>col.notNull()).addColumn('grantor_did', t.text, (col)=>col.notNull()).addColumn('seq', 'integer', (col)=>col.notNull()).addPrimaryKeyConstraint('pk_kubun_credential_grantor_counters', [
58
47
  'owner_did',
@@ -84,11 +73,50 @@ export function getCredentialMigrations(ctx) {
84
73
  ]).execute();
85
74
  // A controller-owned key's administer delegation chain, keyed on the key
86
75
  // itself; `refresh_seq` gates every upsert strictly-greater.
87
- await db.schema.createTable('kubun_credential_key_owner_delegation').ifNotExists().addColumn('key_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('delegation_tokens', t.json, (col)=>col.notNull()).addColumn('refresh_seq', t.bigint, (col)=>col.notNull())// Per-write revision for the replace CAS; `refresh_seq` is not a unique
76
+ await db.schema.createTable('kubun_credential_key_owner_delegation').ifNotExists().addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('delegation_tokens', t.json, (col)=>col.notNull()).addColumn('refresh_seq', t.bigint, (col)=>col.notNull())// Per-write revision for the replace CAS; `refresh_seq` is not a unique
88
77
  // version (see CredentialKeyOwnerDelegationTable).
89
- .addColumn('revision', t.bigint, (col)=>col.notNull()).execute();
78
+ .addColumn('revision', t.bigint, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_kubun_credential_key_owner_delegation', [
79
+ 'owner_did',
80
+ 'key_id'
81
+ ]).execute();
82
+ // Replicated records: every held signed copy of the selected owner-qualified decision.
83
+ await db.schema.createTable('kubun_credential_records').ifNotExists().addColumn('type', t.text, (col)=>col.notNull()).addColumn('id', t.text, (col)=>col.notNull()).addColumn('owner', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('op_jwt', t.text, (col)=>col.notNull()).addColumn('op_hash', t.text, (col)=>col.notNull()).addColumn('payload_digest', t.text).addColumn('payload', t.binary).addColumn('deleted', 'integer', (col)=>col.notNull()).addColumn('meta', t.json, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_records', [
84
+ 'type',
85
+ 'owner',
86
+ 'id',
87
+ 'op_hash'
88
+ ]).execute();
89
+ await db.schema.createTable('kubun_credential_entry_candidates').ifNotExists().addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('key_version', 'integer', (col)=>col.notNull()).addColumn('branch_id', t.text, (col)=>col.notNull()).addColumn('entry_id', t.text, (col)=>col.notNull()).addColumn('owner', t.text, (col)=>col.notNull()).addColumn('hlc', t.text, (col)=>col.notNull()).addColumn('op_jwt', t.text, (col)=>col.notNull()).addColumn('op_hash', t.text, (col)=>col.notNull()).addColumn('payload', t.binary).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_entry_candidates', [
90
+ 'owner_did',
91
+ 'entry_id',
92
+ 'op_hash'
93
+ ]).execute();
94
+ await db.schema.createIndex('idx_kubun_credential_entry_candidates_key_branch').on('kubun_credential_entry_candidates').columns([
95
+ 'owner_did',
96
+ 'key_id',
97
+ 'key_version',
98
+ 'branch_id'
99
+ ]).execute();
100
+ // Revoked recipients per key; only a revocation record writes here.
101
+ await db.schema.createTable('kubun_credential_revocations').ifNotExists().addColumn('key_id', t.text, (col)=>col.notNull()).addColumn('recipient_did', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_revocations', [
102
+ 'owner_did',
103
+ 'key_id',
104
+ 'recipient_did'
105
+ ]).execute();
106
+ // Per-device grant pointers.
107
+ await db.schema.createTable('kubun_credential_device_aliases').ifNotExists().addColumn('provider_name', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('device_did', t.text, (col)=>col.notNull()).addColumn('entry_id', t.text, (col)=>col.notNull()).addColumn('entry_owner_did', t.text, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addPrimaryKeyConstraint('pk_kubun_credential_device_aliases', [
108
+ 'provider_name',
109
+ 'owner_did',
110
+ 'device_did'
111
+ ]).addUniqueConstraint('uq_kubun_credential_device_aliases_entry', [
112
+ 'entry_id'
113
+ ]).execute();
90
114
  },
91
115
  async down (db) {
116
+ await db.schema.dropTable('kubun_credential_device_aliases').ifExists().execute();
117
+ await db.schema.dropTable('kubun_credential_revocations').ifExists().execute();
118
+ await db.schema.dropTable('kubun_credential_entry_candidates').ifExists().execute();
119
+ await db.schema.dropTable('kubun_credential_records').ifExists().execute();
92
120
  await db.schema.dropTable('kubun_credential_key_owner_delegation').ifExists().execute();
93
121
  await db.schema.dropTable('kubun_credential_manifests').ifExists().execute();
94
122
  await db.schema.dropTable('kubun_credential_provisioning_attempts').ifExists().execute();
@@ -96,8 +124,6 @@ export function getCredentialMigrations(ctx) {
96
124
  await db.schema.dropTable('kubun_credential_provisioning_epochs').ifExists().execute();
97
125
  await db.schema.dropTable('kubun_credential_grantor_counters').ifExists().execute();
98
126
  await db.schema.dropTable('kubun_credential_wrapping_authority').ifExists().execute();
99
- await db.schema.dropTable('kubun_credential_aliases').ifExists().execute();
100
- await db.schema.dropTable('kubun_credential_ops').ifExists().execute();
101
127
  await db.schema.dropTable('kubun_credential_entries').ifExists().execute();
102
128
  await db.schema.dropTable('kubun_credential_wrappings').ifExists().execute();
103
129
  await db.schema.dropTable('kubun_credential_key_branches').ifExists().execute();
@@ -0,0 +1,206 @@
1
+ import { type MethodRegistry } from '@kokuin/token';
2
+ import type { Adapter } from '@kubun/db-adapter';
3
+ import { type RecordClaim, type RecordStoreAdapter, type SignedRecord, type VerifiedRecordOp } from '@kubun/records';
4
+ import type { Kysely } from 'kysely';
5
+ import type { CredentialStoreAPI } from './api.js';
6
+ import type { CredentialFactorDescriptor, CredentialKeyState, CredentialManifestKey, CredentialRecordRow, CredentialTables } from './tables.js';
7
+ /** The replicated credential record types, each projected onto one indexed table. */
8
+ export declare const CREDENTIAL_RECORD_TYPES: readonly ["credential.key", "credential.key-branch", "credential.wrapping", "credential.wrapping-authority", "credential.entry", "credential.manifest", "credential.revocation"];
9
+ export type CredentialRecordType = (typeof CREDENTIAL_RECORD_TYPES)[number];
10
+ /**
11
+ * What each record type's payload holds, as the store decodes it. On the wire and in the
12
+ * store a payload is the canonical JSON of this object (keys sorted, UTF-8), with every
13
+ * byte string spelled as unpadded base64url; {@link encodeCredentialRecordPayload} writes
14
+ * exactly those bytes, and the store refuses any other spelling.
15
+ */
16
+ export type CredentialRecordPayloads = {
17
+ 'credential.key': {
18
+ suite: number;
19
+ state: CredentialKeyState;
20
+ };
21
+ /** The key op that introduced the branch, retained verbatim as provenance. */
22
+ 'credential.key-branch': {
23
+ authorDID: string;
24
+ hlc: string;
25
+ opJWT: string;
26
+ opHash: string;
27
+ suite: number;
28
+ };
29
+ 'credential.wrapping': {
30
+ factors: Array<CredentialFactorDescriptor>;
31
+ iv: Uint8Array;
32
+ wrappedKey: Uint8Array;
33
+ };
34
+ 'credential.wrapping-authority': {
35
+ envelope: string;
36
+ };
37
+ 'credential.entry': {
38
+ iv: Uint8Array;
39
+ ciphertext: Uint8Array;
40
+ };
41
+ 'credential.manifest': {
42
+ keys: Array<CredentialManifestKey>;
43
+ token: string;
44
+ delegationTokens: Array<string>;
45
+ };
46
+ 'credential.revocation': Record<string, never>;
47
+ };
48
+ /**
49
+ * The claim `meta` fields the store reads to address each type's indexed row. A claim may
50
+ * carry more fields for its policy or visibility; the store ignores them.
51
+ *
52
+ * - `credential.key`: `keyID` equals the record id.
53
+ * - `credential.key-branch`: the record id is the payload digest.
54
+ * - `credential.wrapping`: `recipient`, when present, is indexed normalized. `branchID`, the
55
+ * introducing key op's `${hlc}:${opHash}` label, is indexed so the fork-aware read path
56
+ * sees the record's branch; it is never empty, since an empty label reads as
57
+ * branch-agnostic and would slip past the fork filter.
58
+ * - `credential.entry`: `branchID` as for a wrapping.
59
+ * - `credential.wrapping-authority`: `wrappingID` equals the record id.
60
+ * - `credential.manifest`: the record id is {@link credentialManifestRecordID} of `recipient`
61
+ * and `grantor`; the owner is the record's owner.
62
+ * - `credential.revocation`: the record id is {@link credentialRevocationRecordID} of `keyID`
63
+ * and `revokedDID`.
64
+ */
65
+ export type CredentialRecordMetas = {
66
+ 'credential.key': {
67
+ keyID: string;
68
+ version: number;
69
+ };
70
+ 'credential.key-branch': {
71
+ keyID: string;
72
+ keyVersion: number;
73
+ branchID: string;
74
+ };
75
+ 'credential.wrapping': {
76
+ keyID: string;
77
+ keyVersion: number;
78
+ branchID: string;
79
+ recipient?: string | null;
80
+ };
81
+ 'credential.wrapping-authority': {
82
+ wrappingID: string;
83
+ refreshSeq: number;
84
+ };
85
+ 'credential.entry': {
86
+ keyID: string;
87
+ keyVersion: number;
88
+ branchID: string;
89
+ };
90
+ 'credential.manifest': {
91
+ recipient: string;
92
+ grantor: string;
93
+ epoch: number;
94
+ sequence: number;
95
+ };
96
+ 'credential.revocation': {
97
+ keyID: string;
98
+ revokedDID: string;
99
+ };
100
+ };
101
+ /** A live credential record with its payload and addressing meta decoded. */
102
+ export type DecodedCredentialRecord = {
103
+ [T in CredentialRecordType]: {
104
+ type: T;
105
+ id: string;
106
+ owner: string;
107
+ hlc: string;
108
+ meta: CredentialRecordMetas[T];
109
+ payload: CredentialRecordPayloads[T];
110
+ };
111
+ }[CredentialRecordType];
112
+ /** A failure about one credential record, naming it by `(type, id)`. */
113
+ export declare class CredentialRecordError extends Error {
114
+ #private;
115
+ constructor(type: string, id: string, message: string, options?: ErrorOptions);
116
+ get type(): string;
117
+ get id(): string;
118
+ }
119
+ /** Thrown when a record's payload or addressing meta does not have its type's shape. */
120
+ export declare class CredentialRecordPayloadError extends CredentialRecordError {
121
+ constructor(type: string, id: string, reason: string, options?: ErrorOptions);
122
+ }
123
+ /** Thrown when a record's op does not verify, or does not sign its payload. */
124
+ export declare class CredentialRecordSignatureError extends CredentialRecordError {
125
+ constructor(type: string, id: string);
126
+ }
127
+ /**
128
+ * Thrown when a stored record row cannot be read back as a record. A corrupt row is never
129
+ * reported as absent: an absent record admits any first write, so an older op would replace
130
+ * the one the row held.
131
+ */
132
+ export declare class CredentialRecordRowError extends CredentialRecordError {
133
+ constructor(type: string, id: string, reason: string);
134
+ }
135
+ /** The id of the revocation of `revokedDID` from key `keyID`. */
136
+ export declare function credentialRevocationRecordID(keyID: string, revokedDID: string): string;
137
+ /** The id of the manifest a grantor records for one recipient, under the record's owner. */
138
+ export declare function credentialManifestRecordID(recipientDID: string, grantorDID: string): string;
139
+ /** The payload bytes of a credential record: canonical JSON, byte strings as base64url. */
140
+ export declare function encodeCredentialRecordPayload<T extends CredentialRecordType>(type: T, payload: CredentialRecordPayloads[T]): Uint8Array;
141
+ /**
142
+ * Decode a live credential record's payload and the meta fields that address its indexed
143
+ * row. DIDs the store keys on are returned normalized. A type's authorizer can call this to
144
+ * refuse a record the store would not accept.
145
+ *
146
+ * @throws CredentialRecordPayloadError when the type is not a credential type, the record is
147
+ * a tombstone, or its payload or meta does not have the type's shape.
148
+ */
149
+ export declare function decodeCredentialRecord(record: Pick<SignedRecord, 'type' | 'id' | 'owner' | 'hlc' | 'payload'>, meta: Record<string, unknown>): DecodedCredentialRecord;
150
+ /**
151
+ * A stored row as the record it holds. The signature is not checked here — apply verifies
152
+ * every stored op it reads — but everything the row repeats must agree with itself.
153
+ *
154
+ * @throws CredentialRecordRowError when it does not.
155
+ */
156
+ export declare function credentialRecordFromRow(row: CredentialRecordRow): SignedRecord;
157
+ /** Keep every authenticated entry operation for branch comparison, including a losing op. */
158
+ export declare function retainCredentialEntryCandidate(tx: Kysely<CredentialTables>, record: SignedRecord, claim: RecordClaim, adapter: Adapter): Promise<void>;
159
+ export type VerifiedRecordCopies = Map<string, Map<string, {
160
+ copy: SignedRecord;
161
+ op: VerifiedRecordOp;
162
+ }>>;
163
+ /**
164
+ * Store a copy of the selected decision of its `(type, owner, id)`, and
165
+ * bring its indexed row along in the same transaction: a live record writes its row, a
166
+ * tombstone removes the row the previous op projected, and a record whose index address
167
+ * moved removes its old row. Merge decisions are the caller's; this only checks that the
168
+ * record is signed and has its type's shape, before anything is written.
169
+ *
170
+ * @throws CredentialRecordSignatureError when the op does not verify or sign the payload.
171
+ * @throws CredentialRecordPayloadError when a live record does not have its type's shape.
172
+ * @throws CredentialRecordRowError when the row it replaces is corrupt.
173
+ */
174
+ export declare function putCredentialRecord(params: {
175
+ record: SignedRecord;
176
+ methods?: MethodRegistry;
177
+ verifiedCopies: VerifiedRecordCopies;
178
+ adapter: Adapter;
179
+ write: (fn: (tx: Kysely<CredentialTables>) => Promise<void>) => Promise<void>;
180
+ lockKey: (tx: Kysely<CredentialTables>, keyID: string) => Promise<void>;
181
+ }): Promise<void>;
182
+ export type CredentialRecordAdapterOptions = {
183
+ methods?: MethodRegistry;
184
+ /** Called for each stored row `list` skips because it cannot be read back as a record. */
185
+ onCorruptRow?: (info: {
186
+ type: string;
187
+ id: string;
188
+ error: unknown;
189
+ }) => void;
190
+ };
191
+ /**
192
+ * The records primitive's view of the credential store.
193
+ *
194
+ * `get` throws {@link CredentialRecordRowError} on a corrupt row rather than answering
195
+ * absent, so apply fails closed on it. `list` skips such a row, reporting it through
196
+ * `onCorruptRow`, so one bad row does not hide every other record of its type. Rows the
197
+ * legacy verbs wrote into the indexed tables are not records and never appear.
198
+ */
199
+ export declare function credentialRecordAdapter(store: CredentialStoreAPI, options?: CredentialRecordAdapterOptions): RecordStoreAdapter;
200
+ /**
201
+ * The credential store behind an adapter {@link credentialRecordAdapter} made, bound to the
202
+ * same transaction, or `null` for any other adapter. A record type's hooks receive only the
203
+ * adapter; this lets them reach the indexed tables inside the transaction they run in,
204
+ * rather than through a store bound outside it.
205
+ */
206
+ export declare function credentialStoreOf(adapter: RecordStoreAdapter): CredentialStoreAPI | null;