@metalabel/dfos-protocol 0.15.0 → 0.17.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.
@@ -0,0 +1,65 @@
1
+ // src/fold/linearize.ts
2
+ var byteCompare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
3
+ var compareHeadPreference = (a, b) => {
4
+ if (a.createdAt !== b.createdAt) return byteCompare(b.createdAt, a.createdAt);
5
+ return byteCompare(b.cid, a.cid);
6
+ };
7
+ var compareLinear = (a, b) => {
8
+ if (a.createdAt !== b.createdAt) return byteCompare(a.createdAt, b.createdAt);
9
+ return byteCompare(a.cid, b.cid);
10
+ };
11
+ var linearize = (ops) => [...ops].sort(compareLinear);
12
+
13
+ // src/fold/lww-map.ts
14
+ var foldLwwMap = (deltas) => {
15
+ const map = /* @__PURE__ */ new Map();
16
+ for (const delta of deltas) {
17
+ if (delta.op === "set") map.set(delta.key, delta.value);
18
+ else map.delete(delta.key);
19
+ }
20
+ return map;
21
+ };
22
+
23
+ // src/fold/index-v1.ts
24
+ var INDEX_V1_SCHEMA = "https://schemas.dfos.com/index/v1";
25
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
26
+ var parseIndexDelta = (raw) => {
27
+ if (!isRecord(raw)) return null;
28
+ const { op, key } = raw;
29
+ if (typeof key !== "string") return null;
30
+ if (op === "remove") return { op: "remove", key };
31
+ if (op === "set") {
32
+ const value = raw["value"];
33
+ if (value === void 0) return { op: "set", key };
34
+ if (!isRecord(value)) return null;
35
+ return { op: "set", key, value };
36
+ }
37
+ return null;
38
+ };
39
+ var indexDeltaStream = (ops) => {
40
+ const stream = [];
41
+ for (const op of linearize(ops)) {
42
+ const doc = op.document;
43
+ if (!isRecord(doc) || doc["$schema"] !== INDEX_V1_SCHEMA) continue;
44
+ const deltas = doc["deltas"];
45
+ if (!Array.isArray(deltas)) continue;
46
+ for (const raw of deltas) {
47
+ const delta = parseIndexDelta(raw);
48
+ if (!delta) continue;
49
+ if (delta.op === "remove") stream.push({ op: "remove", key: delta.key });
50
+ else stream.push({ op: "set", key: delta.key, value: delta.value ?? {} });
51
+ }
52
+ }
53
+ return stream;
54
+ };
55
+ var foldIndexV1 = (ops) => foldLwwMap(indexDeltaStream(ops));
56
+
57
+ export {
58
+ byteCompare,
59
+ compareHeadPreference,
60
+ compareLinear,
61
+ linearize,
62
+ foldLwwMap,
63
+ INDEX_V1_SCHEMA,
64
+ foldIndexV1
65
+ };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Byte-wise (code-point) string comparison returning -1 | 0 | 1.
3
+ *
4
+ * NOT `localeCompare` — ICU collation is locale/engine dependent with no
5
+ * determinism contract. For base32lower CIDs and ASCII ISO-8601 timestamps,
6
+ * JS `<`/`>` (UTF-16 code-unit order) equals byte-wise order, and equals the
7
+ * Go relay twin's `<`/`>` on the same strings. This is the single comparison
8
+ * primitive both head selection and the canonical fold are built on.
9
+ */
10
+ declare const byteCompare: (a: string, b: string) => number;
11
+ /** The minimum an operation must expose to be ordered: its CID and `createdAt`. */
12
+ interface OrderKey {
13
+ /** Operation CID — multibase (base32lower) string from the JWS `cid` header. */
14
+ cid: string;
15
+ /** ISO-8601 `createdAt` from the operation payload. */
16
+ createdAt: string;
17
+ }
18
+ /**
19
+ * Head-preference comparator — the web relay's `selectDeterministicHead` tip
20
+ * ordering, exported so head selection and the canonical fold cannot drift.
21
+ *
22
+ * Sorts the MORE head-preferred operation FIRST: highest `createdAt`, then
23
+ * highest CID (both byte-wise). `array.sort(compareHeadPreference)[0]` is the
24
+ * head among a set of tips.
25
+ */
26
+ declare const compareHeadPreference: (a: OrderKey, b: OrderKey) => number;
27
+ /**
28
+ * Canonical linearization comparator — the exact reverse of
29
+ * `compareHeadPreference`. Sorts ascending (lowest `createdAt` first, then
30
+ * lowest CID), so the head-preferred operation sorts LAST and, under the LWW
31
+ * fold, is applied last (last-applied wins).
32
+ */
33
+ declare const compareLinear: (a: OrderKey, b: OrderKey) => number;
34
+ /**
35
+ * Order a set of operations into the canonical total order — createdAt
36
+ * ascending, CID ascending tiebreak, branch-inclusive.
37
+ *
38
+ * Deterministic regardless of input order: CIDs are unique per operation, so
39
+ * the order is a strict total order and any permutation of the same operation
40
+ * set linearizes identically (this is what makes concurrent forks converge).
41
+ * Returns a new array; the input is not mutated.
42
+ */
43
+ declare const linearize: <T extends OrderKey>(ops: readonly T[]) => T[];
44
+
45
+ /** A single LWW-Map delta over key `key`. */
46
+ type LwwDelta<V = unknown> = {
47
+ readonly op: 'set';
48
+ readonly key: string;
49
+ readonly value: V;
50
+ } | {
51
+ readonly op: 'remove';
52
+ readonly key: string;
53
+ };
54
+ /**
55
+ * Fold an ordered sequence of deltas into a Map. Deltas are applied in the
56
+ * order given — the caller linearizes first. Insertion order of the returned
57
+ * Map reflects last-set position, but equality is by (key → value) content, so
58
+ * two ingest orders that linearize identically produce equal maps.
59
+ */
60
+ declare const foldLwwMap: <V = unknown>(deltas: Iterable<LwwDelta<V>>) => Map<string, V>;
61
+
62
+ /** Canonical `$schema` URI for index documents. */
63
+ declare const INDEX_V1_SCHEMA = "https://schemas.dfos.com/index/v1";
64
+ /**
65
+ * An index entry's metadata value. `label` and `order` are the standard hints;
66
+ * unknown keys are preserved (forward compat). The degenerate set-membership
67
+ * case is the empty object `{}`.
68
+ */
69
+ interface IndexEntry {
70
+ /** Optional display label for the entry. */
71
+ label?: string;
72
+ /** Optional ordering hint (integer, per the content number-encoding rule). */
73
+ order?: number;
74
+ [k: string]: unknown;
75
+ }
76
+ /** A single index/v1 delta. */
77
+ type IndexDelta = {
78
+ op: 'set';
79
+ key: string;
80
+ value?: IndexEntry;
81
+ } | {
82
+ op: 'remove';
83
+ key: string;
84
+ };
85
+ /** An index/v1 document — the content committed by one operation. */
86
+ interface IndexDocument {
87
+ $schema: string;
88
+ deltas: IndexDelta[];
89
+ }
90
+ /**
91
+ * An operation reduced to what the fold needs: its ordering keys plus the
92
+ * resolved content document. `document` is whatever the operation committed by
93
+ * CID (an `IndexDocument` for index chains; anything else is skipped).
94
+ */
95
+ interface FoldOperation extends OrderKey {
96
+ /** The resolved content document this operation committed (null for delete/clear). */
97
+ document: unknown;
98
+ }
99
+ /**
100
+ * Fold a set of index/v1 operations into the resolved index map.
101
+ *
102
+ * The fold is branch-INCLUSIVE — pass every operation in the chain's log, not
103
+ * just the selected-head branch. Concurrent forks converge: any permutation of
104
+ * the same operation set folds to an equal map.
105
+ *
106
+ * Precondition: the chain is not delete-terminal. If the selected head branch
107
+ * is a `delete`, the chain is deleted and the fold is moot — the caller checks
108
+ * `isDeleted` (from `verifyContentChain`) and does not fold a deleted chain.
109
+ */
110
+ declare const foldIndexV1: (ops: readonly FoldOperation[]) => Map<string, IndexEntry>;
111
+
112
+ export { type FoldOperation, INDEX_V1_SCHEMA, type IndexDelta, type IndexDocument, type IndexEntry, type LwwDelta, type OrderKey, byteCompare, compareHeadPreference, compareLinear, foldIndexV1, foldLwwMap, linearize };
@@ -0,0 +1,18 @@
1
+ import {
2
+ INDEX_V1_SCHEMA,
3
+ byteCompare,
4
+ compareHeadPreference,
5
+ compareLinear,
6
+ foldIndexV1,
7
+ foldLwwMap,
8
+ linearize
9
+ } from "../chunk-XF7T45EQ.js";
10
+ export {
11
+ INDEX_V1_SCHEMA,
12
+ byteCompare,
13
+ compareHeadPreference,
14
+ compareLinear,
15
+ foldIndexV1,
16
+ foldLwwMap,
17
+ linearize
18
+ };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader
2
2
  export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, C as CONTENT_ID_ANCHOR_RE, b as ContentOperation, c as CountersignPayload, I as IdentityOperation, M as MAX_ARTIFACT_PAYLOAD_SIZE, d as MAX_OPERATION_SIZE, e as MAX_SERVICES_ENTRIES, f as MAX_SERVICES_PAYLOAD_SIZE, g as MultikeyPublicKey, R as RevocationPayload, S as ServiceEntry, h as ServicesArray, i as Signer, V as VerifiedIdentity } from './schemas-BXye25k7.js';
3
3
  export { AnchorKind, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, RECOGNIZED_SERVICE_TYPES, VerifiedArtifact, VerifiedContentChain, VerifiedCountersignature, VerifiedRevocation, anchorsByLabel, assertServicesWithinCap, classifyAnchor, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation } from './chain/index.js';
4
4
  export { Attenuation, AuthTokenClaims, AuthTokenCreateOptions, AuthTokenVerificationError, AuthTokenVerifyOptions, CredentialVerificationError, DFOSCredentialPayload, MAX_CREDENTIAL_SIZE, VerifiedAuthToken, VerifiedDFOSCredential, VerifiedDelegationChain, createAuthToken, createDFOSCredential, decodeDFOSCredentialUnsafe, isAttenuated, matchesResource, verifyAuthToken, verifyDFOSCredential, verifyDelegationChain } from './credentials/index.js';
5
+ export { FoldOperation, INDEX_V1_SCHEMA, IndexDelta, IndexDocument, IndexEntry, LwwDelta, OrderKey, byteCompare, compareHeadPreference, compareLinear, foldIndexV1, foldLwwMap, linearize } from './fold/index.js';
5
6
  import 'multiformats';
6
7
  import 'multiformats/cid';
7
8
  import 'zod';
package/dist/index.js CHANGED
@@ -79,6 +79,15 @@ import {
79
79
  verifyJws,
80
80
  verifyJwt
81
81
  } from "./chunk-4QQ5HK5M.js";
82
+ import {
83
+ INDEX_V1_SCHEMA,
84
+ byteCompare,
85
+ compareHeadPreference,
86
+ compareLinear,
87
+ foldIndexV1,
88
+ foldLwwMap,
89
+ linearize
90
+ } from "./chunk-XF7T45EQ.js";
82
91
  export {
83
92
  ARTIFACT_CID_ANCHOR_RE,
84
93
  ArtifactPayload,
@@ -92,6 +101,7 @@ export {
92
101
  DFOSCredentialPayload,
93
102
  ED25519_PRIV_MULTICODEC,
94
103
  ED25519_PUB_MULTICODEC,
104
+ INDEX_V1_SCHEMA,
95
105
  IdentityOperation,
96
106
  JwsVerificationError,
97
107
  JwtVerificationError,
@@ -111,7 +121,10 @@ export {
111
121
  assertServicesWithinCap,
112
122
  base64urlDecode,
113
123
  base64urlEncode,
124
+ byteCompare,
114
125
  classifyAnchor,
126
+ compareHeadPreference,
127
+ compareLinear,
115
128
  createAuthToken,
116
129
  createDFOSCredential,
117
130
  createJws,
@@ -125,6 +138,8 @@ export {
125
138
  deriveChainIdentifier,
126
139
  deriveContentId,
127
140
  encodeEd25519Multikey,
141
+ foldIndexV1,
142
+ foldLwwMap,
128
143
  generateId,
129
144
  generateIdNoPrefix,
130
145
  importEd25519Keypair,
@@ -133,6 +148,7 @@ export {
133
148
  isRecognizedServiceType,
134
149
  isValidEd25519Signature,
135
150
  isValidId,
151
+ linearize,
136
152
  matchesResource,
137
153
  normalizedId,
138
154
  parseDagCborCID,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metalabel/dfos-protocol",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "description": "DFOS Protocol — Ed25519 signed chain primitives, services, credentials, and verification",
6
6
  "license": "MIT",
@@ -40,6 +40,10 @@
40
40
  "./credentials": {
41
41
  "import": "./dist/credentials/index.js",
42
42
  "types": "./dist/credentials/index.d.ts"
43
+ },
44
+ "./fold": {
45
+ "import": "./dist/fold/index.js",
46
+ "types": "./dist/fold/index.d.ts"
43
47
  }
44
48
  },
45
49
  "files": [
@@ -0,0 +1,71 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.dfos.com/index/v1",
4
+ "title": "Index",
5
+ "description": "An index chain document — an LWW-Map folded via the canonical fold. Each operation's document carries an array of set/remove deltas over content-ref keys. The resolved index is the fold over ALL operations (every branch) in canonical order; the last delta touching a key wins.",
6
+ "type": "object",
7
+ "required": ["$schema", "deltas"],
8
+ "properties": {
9
+ "$schema": {
10
+ "const": "https://schemas.dfos.com/index/v1"
11
+ },
12
+ "deltas": {
13
+ "type": "array",
14
+ "description": "Ordered deltas contributed by this operation. Applied in array order within the document, then across documents in canonical fold order.",
15
+ "items": {
16
+ "type": "object",
17
+ "required": ["op"],
18
+ "properties": {
19
+ "op": {
20
+ "type": "string",
21
+ "description": "Delta operation. 'set' and 'remove' are the v1 vocabulary; deltas with other ops are valid and are skipped deterministically by readers (forward compat). Validators MUST NOT reject documents carrying additional delta shapes."
22
+ }
23
+ },
24
+ "allOf": [
25
+ {
26
+ "if": { "properties": { "op": { "const": "set" } } },
27
+ "then": {
28
+ "required": ["key"],
29
+ "properties": {
30
+ "key": {
31
+ "type": "string",
32
+ "description": "Content ref — a 31-char content chain id or a CID."
33
+ },
34
+ "value": { "$ref": "#/$defs/entry" }
35
+ }
36
+ }
37
+ },
38
+ {
39
+ "if": { "properties": { "op": { "const": "remove" } } },
40
+ "then": {
41
+ "required": ["key"],
42
+ "properties": {
43
+ "key": {
44
+ "type": "string",
45
+ "description": "Content ref to drop from the index."
46
+ }
47
+ }
48
+ }
49
+ }
50
+ ]
51
+ }
52
+ }
53
+ },
54
+ "additionalProperties": false,
55
+ "$defs": {
56
+ "entry": {
57
+ "type": "object",
58
+ "description": "Optional entry metadata for a set delta. A pure set-membership index uses the degenerate empty object {}. Unknown fields are preserved (forward compat).",
59
+ "properties": {
60
+ "label": {
61
+ "type": "string",
62
+ "description": "Optional display label."
63
+ },
64
+ "order": {
65
+ "type": "integer",
66
+ "description": "Optional ordering hint (integer per the content number-encoding rule)."
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
@@ -17,6 +17,10 @@
17
17
  "type": "string",
18
18
  "description": "Short bio or description."
19
19
  },
20
+ "avatar": {
21
+ "$ref": "#/$defs/media",
22
+ "description": "Optional avatar image as a Media object."
23
+ },
20
24
  "links": {
21
25
  "type": "array",
22
26
  "maxItems": 20,
@@ -43,5 +47,29 @@
43
47
  }
44
48
  }
45
49
  },
46
- "additionalProperties": false
50
+ "additionalProperties": false,
51
+ "$defs": {
52
+ "media": {
53
+ "type": "object",
54
+ "required": ["uri"],
55
+ "properties": {
56
+ "uri": {
57
+ "type": "string",
58
+ "format": "uri",
59
+ "description": "Canonical reference to the media — an attachment://<id> ref (opaque, host-scoped) or any other URI. Always present."
60
+ },
61
+ "cid": {
62
+ "type": "string",
63
+ "pattern": "^bafkrei[a-z2-7]{52}$",
64
+ "description": "Optional content commitment — CIDv1, raw codec (0x55), sha2-256, base32 lowercase, computed over the media bytes exactly as stored/served. May be absent when no cid has been computed for the media."
65
+ },
66
+ "href": {
67
+ "type": "string",
68
+ "format": "uri",
69
+ "description": "Optional resolution hint — a plain URL where the bytes may currently be fetched. Implementation-dependent and non-normative; carries no integrity promise."
70
+ }
71
+ },
72
+ "additionalProperties": false
73
+ }
74
+ }
47
75
  }