@cello-protocol/protocol-types 0.0.53 → 0.0.54
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/dist/document-amendment.d.ts +128 -0
- package/dist/document-amendment.d.ts.map +1 -0
- package/dist/document-amendment.js +347 -0
- package/dist/document-amendment.js.map +1 -0
- package/dist/document-envelope.js +7 -7
- package/dist/document-envelope.js.map +1 -1
- package/dist/document-governance.d.ts +40 -0
- package/dist/document-governance.d.ts.map +1 -0
- package/dist/document-governance.js +110 -0
- package/dist/document-governance.js.map +1 -0
- package/dist/document-join-answer.d.ts +34 -0
- package/dist/document-join-answer.d.ts.map +1 -0
- package/dist/document-join-answer.js +100 -0
- package/dist/document-join-answer.js.map +1 -0
- package/dist/document-join.d.ts +83 -0
- package/dist/document-join.d.ts.map +1 -0
- package/dist/document-join.js +225 -0
- package/dist/document-join.js.map +1 -0
- package/dist/document-multisig.d.ts +81 -0
- package/dist/document-multisig.d.ts.map +1 -0
- package/dist/document-multisig.js +185 -0
- package/dist/document-multisig.js.map +1 -0
- package/dist/document-proposal.d.ts +23 -14
- package/dist/document-proposal.d.ts.map +1 -1
- package/dist/document-proposal.js +80 -7
- package/dist/document-proposal.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-MP-GOVERN-1 — the signature-requirement policy. Rulings D2/D3 (2026-08-11), as code.
|
|
3
|
+
*
|
|
4
|
+
* The policy VALIDATES the collection's claimed required set; it does not mint one. "Any single
|
|
5
|
+
* admin may act" has no single answer to mint — {a} and {b} are both acceptable claims — so the
|
|
6
|
+
* seam is a verdict on what the collection claims, and the multisig layer then demands every
|
|
7
|
+
* claimed signature actually verify.
|
|
8
|
+
*
|
|
9
|
+
* The rules:
|
|
10
|
+
* - **Single-admin kinds** — `add_holder`, `promote_admin`, `remove_holder` (of a non-admin),
|
|
11
|
+
* `change_property`: the claim is EXACTLY ONE current admin. A wider claim is refused, not
|
|
12
|
+
* welcomed as extra-safe: every claimed signer must sign for the collection to complete, so an
|
|
13
|
+
* inflated claim lets one absent co-signer veto an action the rule gives to any single admin —
|
|
14
|
+
* a governance change smuggled through the claim.
|
|
15
|
+
* - **Voluntary leave** — `remove_holder` claimed by exactly the subject themselves is always
|
|
16
|
+
* acceptable, admin or not. Leaving is theirs (D3).
|
|
17
|
+
* - **`remove_admin`** — the claim is exactly ALL OTHER admins; the subject neither required nor
|
|
18
|
+
* counted. With exactly two admins NEITHER CAN REMOVE THE OTHER — by design, and the refusal
|
|
19
|
+
* names the recourse (D3: stop working in it, duplicate it, start fresh without them).
|
|
20
|
+
* - **The holder door is not a bypass** — `remove_holder` of a CURRENT ADMIN (other than
|
|
21
|
+
* voluntary leave) is refused: holder removal drops admin status too, so allowing it under the
|
|
22
|
+
* single-admin rule would expel an admin on one signature, evading `remove_admin` and the
|
|
23
|
+
* deadlock in one move. Demote first, under remove_admin's rule.
|
|
24
|
+
*
|
|
25
|
+
* Kind/subject semantics (does the subject hold? is it already an admin? last-admin protection)
|
|
26
|
+
* are `deriveArrangement`'s job and run BEFORE this policy is consulted — it may assume a
|
|
27
|
+
* semantically coherent amendment.
|
|
28
|
+
*/
|
|
29
|
+
function canonical(ids) {
|
|
30
|
+
return [...new Set(ids)].sort();
|
|
31
|
+
}
|
|
32
|
+
/** A duplicated claim is a builder bug, refused — deduping here would hand the multisig layer a
|
|
33
|
+
* collection it THROWS on (`multisig_duplicate_signer`), escaping DeriveResult's never-throw
|
|
34
|
+
* contract. The two layers agree: duplicates refuse, loudly, at the first gate they reach. */
|
|
35
|
+
function hasDuplicates(ids) {
|
|
36
|
+
return new Set(ids).size !== ids.length;
|
|
37
|
+
}
|
|
38
|
+
function sameSet(a, b) {
|
|
39
|
+
return a.length === b.length && a.every((id, i) => id === b[i]);
|
|
40
|
+
}
|
|
41
|
+
export function documentGovernancePolicy(kind, subjectAgentId, state, claimedRequiredSet) {
|
|
42
|
+
if (hasDuplicates(claimedRequiredSet)) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
reason: "governance_claim_shape: the claimed required set names a signer more than once",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const claimed = canonical(claimedRequiredSet);
|
|
49
|
+
if (claimed.length === 0) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
reason: "governance_claim_shape: a collection nobody is required to sign authorizes nothing",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (kind === "remove_admin") {
|
|
56
|
+
// deriveArrangement already refused a non-admin subject and the last-admin case.
|
|
57
|
+
if (state.admins.size === 2) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
reason: "governance_two_admin_deadlock: with exactly two admins neither can remove the other " +
|
|
61
|
+
"(D3, by design) — the recourse is to stop working in this document, duplicate it, and " +
|
|
62
|
+
"start fresh without them",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const others = canonical([...state.admins].filter((id) => id !== subjectAgentId));
|
|
66
|
+
if (!sameSet(claimed, others)) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
reason: `governance_remove_admin_set: removing an admin takes the signatures of ALL other ` +
|
|
70
|
+
`admins [${others.join(", ")}] exactly — the subject neither required nor counted — ` +
|
|
71
|
+
`and the collection claims [${claimed.join(", ")}]`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { ok: true };
|
|
75
|
+
}
|
|
76
|
+
// Voluntary leave: the subject signing their own removal, admin or not. Checked before the
|
|
77
|
+
// admin-door rule below so a leaving admin is not told to demote themselves first.
|
|
78
|
+
if (kind === "remove_holder" &&
|
|
79
|
+
subjectAgentId !== null &&
|
|
80
|
+
claimed.length === 1 &&
|
|
81
|
+
claimed[0] === subjectAgentId) {
|
|
82
|
+
return { ok: true };
|
|
83
|
+
}
|
|
84
|
+
if (kind === "remove_holder" && subjectAgentId !== null && state.admins.has(subjectAgentId)) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: "governance_remove_admin_first: removing an admin from the document takes the " +
|
|
88
|
+
"remove_admin rule (all other admins) — a single admin cannot expel a fellow admin " +
|
|
89
|
+
"through the holder door, and holder removal drops admin status too",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// The single-admin kinds: add_holder, promote_admin, remove_holder (non-admin), change_property.
|
|
93
|
+
if (claimed.length !== 1) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
reason: `governance_claim_shape: ${kind} is a single-admin action and the collection claims ` +
|
|
97
|
+
`${claimed.length} required signers — a wider claim lets an absent co-signer veto what ` +
|
|
98
|
+
`the rule gives to any one admin`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (!state.admins.has(claimed[0])) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
reason: `governance_not_admin: ${claimed[0]} holds no admin power over this document — ` +
|
|
105
|
+
`${kind} takes a current admin's signature`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return { ok: true };
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=document-governance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-governance.js","sourceRoot":"","sources":["../src/document-governance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-MP-JOIN-1 — the invitee's ANSWER to a join offer: a signed, settle-once fact.
|
|
3
|
+
*
|
|
4
|
+
* Keyed on the ADMITTING AMENDMENT'S HASH — the amendment is what the invitee answers, and a
|
|
5
|
+
* re-invitation after a refusal is a new amendment with a new hash, so two answers can never be
|
|
6
|
+
* confused. Its own frame for the proposal-ack reason: a join offer is not in the envelope log,
|
|
7
|
+
* so `document_ack` would settle a hash of nothing.
|
|
8
|
+
*
|
|
9
|
+
* Consent is local and final the moment the operator makes it; sending the answer is
|
|
10
|
+
* best-effort, and an unreachable inviter gets no veto over the invitee's choice — the same
|
|
11
|
+
* doctrine as the proposal ack.
|
|
12
|
+
*/
|
|
13
|
+
/** Domain tag in slot 0 of the to-be-signed array. */
|
|
14
|
+
export declare const DOCUMENT_JOIN_ANSWER_DOMAIN = "CELLO-DOCUMENT-JOIN-ANSWER-v1";
|
|
15
|
+
export declare const MAX_JOIN_REFUSAL_REASON_LENGTH = 512;
|
|
16
|
+
export interface DocumentJoinAnswer {
|
|
17
|
+
type: "document_join_answer";
|
|
18
|
+
document_id: string;
|
|
19
|
+
/** Hex hash of the admitting `add_holder` amendment — the settle key. */
|
|
20
|
+
amendment_hash: string;
|
|
21
|
+
invitee_agent_id: string;
|
|
22
|
+
accepted: boolean;
|
|
23
|
+
/** Operator prose on a refusal; explicit null on an accept — never absent. */
|
|
24
|
+
refusal_reason: string | null;
|
|
25
|
+
answered_at_ms: number;
|
|
26
|
+
/** Ed25519 (RFC 8032) by the invitee over `buildDocumentJoinAnswerTbs`. */
|
|
27
|
+
signature: Uint8Array;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildDocumentJoinAnswerTbs(answer: DocumentJoinAnswer, opts?: {
|
|
30
|
+
preHash?: boolean;
|
|
31
|
+
}): Uint8Array;
|
|
32
|
+
export declare function encodeDocumentJoinAnswer(answer: DocumentJoinAnswer): Uint8Array;
|
|
33
|
+
export declare function decodeDocumentJoinAnswer(input: Uint8Array): DocumentJoinAnswer;
|
|
34
|
+
//# sourceMappingURL=document-join-answer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-join-answer.d.ts","sourceRoot":"","sources":["../src/document-join-answer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,sDAAsD;AACtD,eAAO,MAAM,2BAA2B,kCAAkC,CAAC;AAE3E,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAElD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/B,UAAU,CAcZ;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,UAAU,CAW/E;AAiBD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,GAAG,kBAAkB,CA8C9E"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-MP-JOIN-1 — the invitee's ANSWER to a join offer: a signed, settle-once fact.
|
|
3
|
+
*
|
|
4
|
+
* Keyed on the ADMITTING AMENDMENT'S HASH — the amendment is what the invitee answers, and a
|
|
5
|
+
* re-invitation after a refusal is a new amendment with a new hash, so two answers can never be
|
|
6
|
+
* confused. Its own frame for the proposal-ack reason: a join offer is not in the envelope log,
|
|
7
|
+
* so `document_ack` would settle a hash of nothing.
|
|
8
|
+
*
|
|
9
|
+
* Consent is local and final the moment the operator makes it; sending the answer is
|
|
10
|
+
* best-effort, and an unreachable inviter gets no veto over the invitee's choice — the same
|
|
11
|
+
* doctrine as the proposal ack.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import { encodeCbor, decodeCbor } from "./cbor.js";
|
|
15
|
+
/** Domain tag in slot 0 of the to-be-signed array. */
|
|
16
|
+
export const DOCUMENT_JOIN_ANSWER_DOMAIN = "CELLO-DOCUMENT-JOIN-ANSWER-v1";
|
|
17
|
+
export const MAX_JOIN_REFUSAL_REASON_LENGTH = 512;
|
|
18
|
+
export function buildDocumentJoinAnswerTbs(answer, opts = {}) {
|
|
19
|
+
const preimage = encodeCbor([
|
|
20
|
+
DOCUMENT_JOIN_ANSWER_DOMAIN,
|
|
21
|
+
answer.document_id,
|
|
22
|
+
answer.amendment_hash,
|
|
23
|
+
answer.invitee_agent_id,
|
|
24
|
+
answer.accepted,
|
|
25
|
+
answer.refusal_reason,
|
|
26
|
+
typeof answer.answered_at_ms === "number" && answer.answered_at_ms > 0xffffffff
|
|
27
|
+
? BigInt(answer.answered_at_ms)
|
|
28
|
+
: answer.answered_at_ms,
|
|
29
|
+
]);
|
|
30
|
+
if (opts.preHash === false)
|
|
31
|
+
return preimage;
|
|
32
|
+
return new Uint8Array(createHash("sha256").update(preimage).digest());
|
|
33
|
+
}
|
|
34
|
+
export function encodeDocumentJoinAnswer(answer) {
|
|
35
|
+
return encodeCbor({
|
|
36
|
+
type: answer.type,
|
|
37
|
+
document_id: answer.document_id,
|
|
38
|
+
amendment_hash: answer.amendment_hash,
|
|
39
|
+
invitee_agent_id: answer.invitee_agent_id,
|
|
40
|
+
accepted: answer.accepted,
|
|
41
|
+
refusal_reason: answer.refusal_reason,
|
|
42
|
+
answered_at_ms: answer.answered_at_ms,
|
|
43
|
+
signature: answer.signature,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function present(map, field) {
|
|
47
|
+
if (!(field in map)) {
|
|
48
|
+
throw new Error(`document_join_answer_missing_field: ${field} is mandatory and was not present`);
|
|
49
|
+
}
|
|
50
|
+
return map[field];
|
|
51
|
+
}
|
|
52
|
+
function str(map, field) {
|
|
53
|
+
const v = present(map, field);
|
|
54
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
55
|
+
throw new Error(`document_join_answer_field_type: ${field} must be a non-empty text string`);
|
|
56
|
+
}
|
|
57
|
+
return v;
|
|
58
|
+
}
|
|
59
|
+
export function decodeDocumentJoinAnswer(input) {
|
|
60
|
+
const decoded = decodeCbor(input);
|
|
61
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
|
|
62
|
+
throw new Error("document_join_answer_malformed: not a CBOR map");
|
|
63
|
+
}
|
|
64
|
+
const map = decoded;
|
|
65
|
+
const type = str(map, "type");
|
|
66
|
+
if (type !== "document_join_answer") {
|
|
67
|
+
throw new Error(`document_join_answer_type: expected document_join_answer, got ${type}`);
|
|
68
|
+
}
|
|
69
|
+
const accepted = present(map, "accepted");
|
|
70
|
+
if (typeof accepted !== "boolean") {
|
|
71
|
+
throw new Error("document_join_answer_field_type: accepted must be a boolean — consent is never inferred " +
|
|
72
|
+
"from a truthy value");
|
|
73
|
+
}
|
|
74
|
+
const reason = present(map, "refusal_reason");
|
|
75
|
+
if (reason !== null && typeof reason !== "string") {
|
|
76
|
+
throw new Error("document_join_answer_field_type: refusal_reason must be a text string or explicit null");
|
|
77
|
+
}
|
|
78
|
+
if (typeof reason === "string" && reason.length > MAX_JOIN_REFUSAL_REASON_LENGTH) {
|
|
79
|
+
throw new Error(`document_join_answer_field_type: refusal_reason exceeds ${MAX_JOIN_REFUSAL_REASON_LENGTH} chars`);
|
|
80
|
+
}
|
|
81
|
+
const answeredAt = present(map, "answered_at_ms");
|
|
82
|
+
if (typeof answeredAt !== "number" || !Number.isInteger(answeredAt)) {
|
|
83
|
+
throw new Error("document_join_answer_field_type: answered_at_ms must be an integer");
|
|
84
|
+
}
|
|
85
|
+
const signature = present(map, "signature");
|
|
86
|
+
if (!(signature instanceof Uint8Array)) {
|
|
87
|
+
throw new Error("document_join_answer_field_type: signature must be a CBOR byte string");
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
type: "document_join_answer",
|
|
91
|
+
document_id: str(map, "document_id"),
|
|
92
|
+
amendment_hash: str(map, "amendment_hash"),
|
|
93
|
+
invitee_agent_id: str(map, "invitee_agent_id"),
|
|
94
|
+
accepted,
|
|
95
|
+
refusal_reason: reason,
|
|
96
|
+
answered_at_ms: answeredAt,
|
|
97
|
+
signature: new Uint8Array(signature),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=document-join-answer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-join-answer.js","sourceRoot":"","sources":["../src/document-join-answer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-MP-JOIN-1 — the join offer: how a third party enters an existing document.
|
|
3
|
+
*
|
|
4
|
+
* The offer is a courier, not an authority. It carries the RECEIVED BYTES of the genesis
|
|
5
|
+
* proposal, the full amendment chain (the pending `add_holder` last), and the envelope-log
|
|
6
|
+
* snapshot — and the invitee's daemon DERIVES the arrangement from those bytes through the same
|
|
7
|
+
* replay every holder runs. The invitee consents to what they computed, never to what they were
|
|
8
|
+
* told: an offer whose chain does not replay admits nobody, whatever its prose.
|
|
9
|
+
*
|
|
10
|
+
* ── WHY THE SIGNATURE BINDS EVERY CARRIED BYTE ────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* The TBS commits to the hash of the genesis bytes, of each amendment, and of each log envelope.
|
|
13
|
+
* Each carried payload is ALSO independently verifiable (the genesis by its proposer's
|
|
14
|
+
* signature, each amendment by its collection, each envelope by its sender) — the offer-level
|
|
15
|
+
* binding exists so the inviter cannot assemble a misleading BUNDLE out of individually-valid
|
|
16
|
+
* pieces and later disown the assembly. Who showed the invitee what is on the record.
|
|
17
|
+
*
|
|
18
|
+
* ── NEITHER ALONE ADMITS ANYONE ───────────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* A valid offer makes a join POSSIBLE; the invitee's signed accept makes it REAL (the daemon
|
|
21
|
+
* unit wires that consent through the settle-once ack pattern). This module rules only on the
|
|
22
|
+
* offer's validity — GOVERN-1's dispositioned consent clause lands in the daemon receive path.
|
|
23
|
+
*/
|
|
24
|
+
import { type DocumentProposalEnvelope } from "./document-proposal.js";
|
|
25
|
+
import { type Arrangement, type ArrangementGenesis, type DocumentAmendmentEnvelope, type SignerPolicy } from "./document-amendment.js";
|
|
26
|
+
/** Domain tag in slot 0 of the to-be-signed array. Sibling of the other CELLO-DOCUMENT-* tags. */
|
|
27
|
+
export declare const DOCUMENT_JOIN_OFFER_DOMAIN = "CELLO-DOCUMENT-JOIN-OFFER-v1";
|
|
28
|
+
export interface DocumentJoinOffer {
|
|
29
|
+
type: "document_join_offer";
|
|
30
|
+
feature_version: number;
|
|
31
|
+
/** The admin authoring this offer — must be a claimed signer of the pending amendment. */
|
|
32
|
+
inviter_agent_id: string;
|
|
33
|
+
invitee_agent_id: string;
|
|
34
|
+
document_id: string;
|
|
35
|
+
/** The genesis proposal AS RECEIVED — the invitee re-derives `document_id` from these bytes. */
|
|
36
|
+
genesis: Uint8Array;
|
|
37
|
+
/** The full amendment chain as received bytes, ordered, the pending `add_holder` LAST. */
|
|
38
|
+
amendments: Uint8Array[];
|
|
39
|
+
/** The update-envelope log snapshot (D1's cheap path) — may be empty for a young document. */
|
|
40
|
+
envelope_log: Uint8Array[];
|
|
41
|
+
offered_at_ms: number;
|
|
42
|
+
/** Ed25519 (RFC 8032) by the inviter over `buildDocumentJoinOfferTbs`. */
|
|
43
|
+
signature: Uint8Array;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The canonical to-be-signed preimage. Payloads enter as HASHES (arrays of 32-byte digests are
|
|
47
|
+
* deterministic CBOR — the multisig precedent), so the signature binds every carried byte
|
|
48
|
+
* without re-encoding megabytes into the preimage.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildDocumentJoinOfferTbs(offer: DocumentJoinOffer, opts?: {
|
|
51
|
+
preHash?: boolean;
|
|
52
|
+
}): Uint8Array;
|
|
53
|
+
export declare function encodeDocumentJoinOffer(offer: DocumentJoinOffer): Uint8Array;
|
|
54
|
+
export declare function decodeDocumentJoinOffer(input: Uint8Array): DocumentJoinOffer;
|
|
55
|
+
/**
|
|
56
|
+
* The replay's starting facts, computed from a decoded genesis proposal — ONE implementation for
|
|
57
|
+
* every consumer (join validation, the daemon's amendment receive and accept paths), because two
|
|
58
|
+
* copies of the everyone-admin default is how two holders derive different arrangements from one
|
|
59
|
+
* genesis. Absent `admin_set` means both genesis participants govern (the bilateral default);
|
|
60
|
+
* `admin_set` itself lives on the ARRANGEMENT, not in the scalar properties bag the replay
|
|
61
|
+
* derives `change_property` against.
|
|
62
|
+
*/
|
|
63
|
+
export declare function arrangementGenesisFromProposal(genesis: DocumentProposalEnvelope): ArrangementGenesis;
|
|
64
|
+
export type JoinOfferValidation = {
|
|
65
|
+
ok: true;
|
|
66
|
+
/** Derived by REPLAY of the carried bytes — the consent surface, computed not trusted. */
|
|
67
|
+
arrangement: Arrangement;
|
|
68
|
+
genesis: DocumentProposalEnvelope;
|
|
69
|
+
amendments: DocumentAmendmentEnvelope[];
|
|
70
|
+
/** Hex hash of the pending `add_holder` — the settle key for the invitee's answer. */
|
|
71
|
+
pendingAmendmentHash: string;
|
|
72
|
+
} | {
|
|
73
|
+
ok: false;
|
|
74
|
+
reason: string;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* The invitee's ruling on an offer. Order is the disclosure-and-trust boundary: the version
|
|
78
|
+
* answer first (a mismatched build must get a sentence, not a failed signature), then the
|
|
79
|
+
* inviter's signature (nothing else is worth computing on an unauthenticated bundle), then the
|
|
80
|
+
* carried payloads each on their own signatures, then the replay.
|
|
81
|
+
*/
|
|
82
|
+
export declare function validateDocumentJoinOffer(offer: DocumentJoinOffer, policy: SignerPolicy, verify: (agentId: string, tbs: Uint8Array, signature: Uint8Array) => boolean): JoinOfferValidation;
|
|
83
|
+
//# sourceMappingURL=document-join.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-join.d.ts","sourceRoot":"","sources":["../src/document-join.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,EAKL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAC;AAEjC,kGAAkG;AAClG,eAAO,MAAM,0BAA0B,iCAAiC,CAAC;AAEzE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,OAAO,EAAE,UAAU,CAAC;IACpB,0FAA0F;IAC1F,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,8FAA8F;IAC9F,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,SAAS,EAAE,UAAU,CAAC;CACvB;AAMD;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,iBAAiB,EACxB,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/B,UAAU,CAgBZ;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,GAAG,UAAU,CAa5E;AAiCD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,UAAU,GAAG,iBAAiB,CA8B5E;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,wBAAwB,GAChC,kBAAkB,CAgBpB;AAED,MAAM,MAAM,mBAAmB,GAC3B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,0FAA0F;IAC1F,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,wBAAwB,CAAC;IAClC,UAAU,EAAE,yBAAyB,EAAE,CAAC;IACxC,sFAAsF;IACtF,oBAAoB,EAAE,MAAM,CAAC;CAC9B,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAMlC;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,KAAK,OAAO,GAC3E,mBAAmB,CAyFrB"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOD-MP-JOIN-1 — the join offer: how a third party enters an existing document.
|
|
3
|
+
*
|
|
4
|
+
* The offer is a courier, not an authority. It carries the RECEIVED BYTES of the genesis
|
|
5
|
+
* proposal, the full amendment chain (the pending `add_holder` last), and the envelope-log
|
|
6
|
+
* snapshot — and the invitee's daemon DERIVES the arrangement from those bytes through the same
|
|
7
|
+
* replay every holder runs. The invitee consents to what they computed, never to what they were
|
|
8
|
+
* told: an offer whose chain does not replay admits nobody, whatever its prose.
|
|
9
|
+
*
|
|
10
|
+
* ── WHY THE SIGNATURE BINDS EVERY CARRIED BYTE ────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* The TBS commits to the hash of the genesis bytes, of each amendment, and of each log envelope.
|
|
13
|
+
* Each carried payload is ALSO independently verifiable (the genesis by its proposer's
|
|
14
|
+
* signature, each amendment by its collection, each envelope by its sender) — the offer-level
|
|
15
|
+
* binding exists so the inviter cannot assemble a misleading BUNDLE out of individually-valid
|
|
16
|
+
* pieces and later disown the assembly. Who showed the invitee what is on the record.
|
|
17
|
+
*
|
|
18
|
+
* ── NEITHER ALONE ADMITS ANYONE ───────────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* A valid offer makes a join POSSIBLE; the invitee's signed accept makes it REAL (the daemon
|
|
21
|
+
* unit wires that consent through the settle-once ack pattern). This module rules only on the
|
|
22
|
+
* offer's validity — GOVERN-1's dispositioned consent clause lands in the daemon receive path.
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
import { encodeCbor, decodeCbor } from "./cbor.js";
|
|
26
|
+
import { DOCUMENT_FEATURE_VERSION, decodeDocumentProposal, documentIdFromProposal, buildDocumentProposalTbs, } from "./document-proposal.js";
|
|
27
|
+
import { decodeDocumentAmendment, deriveArrangement, } from "./document-amendment.js";
|
|
28
|
+
/** Domain tag in slot 0 of the to-be-signed array. Sibling of the other CELLO-DOCUMENT-* tags. */
|
|
29
|
+
export const DOCUMENT_JOIN_OFFER_DOMAIN = "CELLO-DOCUMENT-JOIN-OFFER-v1";
|
|
30
|
+
function sha256(bytes) {
|
|
31
|
+
return new Uint8Array(createHash("sha256").update(bytes).digest());
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The canonical to-be-signed preimage. Payloads enter as HASHES (arrays of 32-byte digests are
|
|
35
|
+
* deterministic CBOR — the multisig precedent), so the signature binds every carried byte
|
|
36
|
+
* without re-encoding megabytes into the preimage.
|
|
37
|
+
*/
|
|
38
|
+
export function buildDocumentJoinOfferTbs(offer, opts = {}) {
|
|
39
|
+
const preimage = encodeCbor([
|
|
40
|
+
DOCUMENT_JOIN_OFFER_DOMAIN,
|
|
41
|
+
offer.feature_version,
|
|
42
|
+
offer.inviter_agent_id,
|
|
43
|
+
offer.invitee_agent_id,
|
|
44
|
+
offer.document_id,
|
|
45
|
+
sha256(offer.genesis),
|
|
46
|
+
offer.amendments.map(sha256),
|
|
47
|
+
offer.envelope_log.map(sha256),
|
|
48
|
+
typeof offer.offered_at_ms === "number" && offer.offered_at_ms > 0xffffffff
|
|
49
|
+
? BigInt(offer.offered_at_ms)
|
|
50
|
+
: offer.offered_at_ms,
|
|
51
|
+
]);
|
|
52
|
+
if (opts.preHash === false)
|
|
53
|
+
return preimage;
|
|
54
|
+
return sha256(preimage);
|
|
55
|
+
}
|
|
56
|
+
export function encodeDocumentJoinOffer(offer) {
|
|
57
|
+
return encodeCbor({
|
|
58
|
+
type: offer.type,
|
|
59
|
+
feature_version: offer.feature_version,
|
|
60
|
+
inviter_agent_id: offer.inviter_agent_id,
|
|
61
|
+
invitee_agent_id: offer.invitee_agent_id,
|
|
62
|
+
document_id: offer.document_id,
|
|
63
|
+
genesis: offer.genesis,
|
|
64
|
+
amendments: offer.amendments,
|
|
65
|
+
envelope_log: offer.envelope_log,
|
|
66
|
+
offered_at_ms: offer.offered_at_ms,
|
|
67
|
+
signature: offer.signature,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function present(map, field) {
|
|
71
|
+
if (!(field in map)) {
|
|
72
|
+
throw new Error(`document_join_missing_field: ${field} is mandatory and was not present`);
|
|
73
|
+
}
|
|
74
|
+
return map[field];
|
|
75
|
+
}
|
|
76
|
+
function str(map, field) {
|
|
77
|
+
const v = present(map, field);
|
|
78
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
79
|
+
throw new Error(`document_join_field_type: ${field} must be a non-empty text string`);
|
|
80
|
+
}
|
|
81
|
+
return v;
|
|
82
|
+
}
|
|
83
|
+
function bytes(map, field) {
|
|
84
|
+
const v = present(map, field);
|
|
85
|
+
if (!(v instanceof Uint8Array)) {
|
|
86
|
+
throw new Error(`document_join_field_type: ${field} must be a CBOR byte string`);
|
|
87
|
+
}
|
|
88
|
+
return new Uint8Array(v);
|
|
89
|
+
}
|
|
90
|
+
function byteArray(map, field) {
|
|
91
|
+
const v = present(map, field);
|
|
92
|
+
if (!Array.isArray(v) || v.some((e) => !(e instanceof Uint8Array))) {
|
|
93
|
+
throw new Error(`document_join_field_type: ${field} must be an array of CBOR byte strings`);
|
|
94
|
+
}
|
|
95
|
+
return v.map((e) => new Uint8Array(e));
|
|
96
|
+
}
|
|
97
|
+
export function decodeDocumentJoinOffer(input) {
|
|
98
|
+
const decoded = decodeCbor(input);
|
|
99
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
|
|
100
|
+
throw new Error("document_join_malformed: not a CBOR map");
|
|
101
|
+
}
|
|
102
|
+
const map = decoded;
|
|
103
|
+
const type = str(map, "type");
|
|
104
|
+
if (type !== "document_join_offer") {
|
|
105
|
+
throw new Error(`document_join_type: expected document_join_offer, got ${type}`);
|
|
106
|
+
}
|
|
107
|
+
const featureVersion = present(map, "feature_version");
|
|
108
|
+
if (typeof featureVersion !== "number" || !Number.isInteger(featureVersion) || featureVersion < 1) {
|
|
109
|
+
throw new Error("document_join_field_type: feature_version must be a positive integer");
|
|
110
|
+
}
|
|
111
|
+
const offeredAt = present(map, "offered_at_ms");
|
|
112
|
+
if (typeof offeredAt !== "number" || !Number.isInteger(offeredAt)) {
|
|
113
|
+
throw new Error("document_join_field_type: offered_at_ms must be an integer");
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
type: "document_join_offer",
|
|
117
|
+
feature_version: featureVersion,
|
|
118
|
+
inviter_agent_id: str(map, "inviter_agent_id"),
|
|
119
|
+
invitee_agent_id: str(map, "invitee_agent_id"),
|
|
120
|
+
document_id: str(map, "document_id"),
|
|
121
|
+
genesis: bytes(map, "genesis"),
|
|
122
|
+
amendments: byteArray(map, "amendments"),
|
|
123
|
+
envelope_log: byteArray(map, "envelope_log"),
|
|
124
|
+
offered_at_ms: offeredAt,
|
|
125
|
+
signature: bytes(map, "signature"),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The replay's starting facts, computed from a decoded genesis proposal — ONE implementation for
|
|
130
|
+
* every consumer (join validation, the daemon's amendment receive and accept paths), because two
|
|
131
|
+
* copies of the everyone-admin default is how two holders derive different arrangements from one
|
|
132
|
+
* genesis. Absent `admin_set` means both genesis participants govern (the bilateral default);
|
|
133
|
+
* `admin_set` itself lives on the ARRANGEMENT, not in the scalar properties bag the replay
|
|
134
|
+
* derives `change_property` against.
|
|
135
|
+
*/
|
|
136
|
+
export function arrangementGenesisFromProposal(genesis) {
|
|
137
|
+
return {
|
|
138
|
+
documentId: documentIdFromProposal(genesis),
|
|
139
|
+
proposerAgentId: genesis.proposer_agent_id,
|
|
140
|
+
peerAgentId: genesis.peer_agent_id,
|
|
141
|
+
adminSet: genesis.properties.admin_set ?? [genesis.proposer_agent_id, genesis.peer_agent_id],
|
|
142
|
+
properties: {
|
|
143
|
+
assurance_tier: genesis.properties.assurance_tier,
|
|
144
|
+
schema_enforcement: genesis.properties.schema_enforcement,
|
|
145
|
+
topology: genesis.properties.topology,
|
|
146
|
+
append_only: genesis.properties.append_only,
|
|
147
|
+
...(genesis.properties.content_profile !== undefined
|
|
148
|
+
? { content_profile: genesis.properties.content_profile }
|
|
149
|
+
: {}),
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function refuse(reason) {
|
|
154
|
+
return { ok: false, reason };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The invitee's ruling on an offer. Order is the disclosure-and-trust boundary: the version
|
|
158
|
+
* answer first (a mismatched build must get a sentence, not a failed signature), then the
|
|
159
|
+
* inviter's signature (nothing else is worth computing on an unauthenticated bundle), then the
|
|
160
|
+
* carried payloads each on their own signatures, then the replay.
|
|
161
|
+
*/
|
|
162
|
+
export function validateDocumentJoinOffer(offer, policy, verify) {
|
|
163
|
+
if (offer.feature_version !== DOCUMENT_FEATURE_VERSION) {
|
|
164
|
+
const side = offer.feature_version > DOCUMENT_FEATURE_VERSION
|
|
165
|
+
? "upgrade this client to join it"
|
|
166
|
+
: "ask the inviter to upgrade theirs";
|
|
167
|
+
return refuse(`join_feature_version: the offer speaks document feature version ${offer.feature_version} ` +
|
|
168
|
+
`and this build speaks ${DOCUMENT_FEATURE_VERSION} — ${side}`);
|
|
169
|
+
}
|
|
170
|
+
if (!verify(offer.inviter_agent_id, buildDocumentJoinOfferTbs(offer), offer.signature)) {
|
|
171
|
+
return refuse(`join_signature_invalid: the offer claims to come from ${offer.inviter_agent_id} but its ` +
|
|
172
|
+
`signature does not verify against that agent — nothing about the bundle is trustworthy`);
|
|
173
|
+
}
|
|
174
|
+
let genesis;
|
|
175
|
+
try {
|
|
176
|
+
genesis = decodeDocumentProposal(offer.genesis);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
return refuse(`join_genesis_undecodable: ${err instanceof Error ? err.message : String(err)}`);
|
|
180
|
+
}
|
|
181
|
+
if (documentIdFromProposal(genesis) !== offer.document_id) {
|
|
182
|
+
return refuse(`join_genesis_mismatch: the carried genesis hashes to a different document than the offer ` +
|
|
183
|
+
`names — the anchor cannot be swapped`);
|
|
184
|
+
}
|
|
185
|
+
if (!verify(genesis.proposer_agent_id, buildDocumentProposalTbs(genesis), genesis.signature)) {
|
|
186
|
+
return refuse(`join_genesis_signature_invalid: the genesis proposal does not verify against its named ` +
|
|
187
|
+
`proposer ${genesis.proposer_agent_id}`);
|
|
188
|
+
}
|
|
189
|
+
if (offer.amendments.length === 0) {
|
|
190
|
+
return refuse("join_no_pending_amendment: an offer must carry the amendment chain ending in the " +
|
|
191
|
+
"add_holder that admits the invitee — an empty chain admits nobody");
|
|
192
|
+
}
|
|
193
|
+
let amendments;
|
|
194
|
+
try {
|
|
195
|
+
amendments = offer.amendments.map((a) => decodeDocumentAmendment(a));
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return refuse(`join_amendment_undecodable: ${err instanceof Error ? err.message : String(err)}`);
|
|
199
|
+
}
|
|
200
|
+
const derived = deriveArrangement(arrangementGenesisFromProposal(genesis), amendments, policy, verify);
|
|
201
|
+
if (!derived.ok) {
|
|
202
|
+
// The replay's reason IS the diagnosis — passed through, never substituted.
|
|
203
|
+
return refuse(derived.reason);
|
|
204
|
+
}
|
|
205
|
+
const pending = amendments[amendments.length - 1];
|
|
206
|
+
if (pending.body.kind !== "add_holder" || pending.body.subject_agent_id !== offer.invitee_agent_id) {
|
|
207
|
+
return refuse(`join_not_for_invitee: the chain's last amendment does not admit ${offer.invitee_agent_id} — ` +
|
|
208
|
+
`an offer is addressed to the holder its pending amendment names`);
|
|
209
|
+
}
|
|
210
|
+
if (!pending.collection.required_signers.includes(offer.inviter_agent_id)) {
|
|
211
|
+
return refuse(`join_inviter_not_signer: ${offer.inviter_agent_id} authored the offer but is not a claimed ` +
|
|
212
|
+
`signer of the admitting amendment — the offer's author must be behind the admission`);
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
ok: true,
|
|
216
|
+
arrangement: derived.arrangement,
|
|
217
|
+
genesis,
|
|
218
|
+
amendments,
|
|
219
|
+
// Unconditional: after a successful replay the subject_hash provably equals the amendment
|
|
220
|
+
// hash, and a conditional empty-string key would collide every document on "" if the
|
|
221
|
+
// invariant ever broke — better to emit the hex of whatever verified.
|
|
222
|
+
pendingAmendmentHash: Buffer.from(pending.collection.subject_hash).toString("hex"),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=document-join.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-join.js","sourceRoot":"","sources":["../src/document-join.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,GAEzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,uBAAuB,EACvB,iBAAiB,GAKlB,MAAM,yBAAyB,CAAC;AAEjC,kGAAkG;AAClG,MAAM,CAAC,MAAM,0BAA0B,GAAG,8BAA8B,CAAC;AAoBzE,SAAS,MAAM,CAAC,KAAiB;IAC/B,OAAO,IAAI,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAwB,EACxB,OAA8B,EAAE;IAEhC,MAAM,QAAQ,GAAG,UAAU,CAAC;QAC1B,0BAA0B;QAC1B,KAAK,CAAC,eAAe;QACrB,KAAK,CAAC,gBAAgB;QACtB,KAAK,CAAC,gBAAgB;QACtB,KAAK,CAAC,WAAW;QACjB,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;QACrB,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;QAC5B,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;QAC9B,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ,IAAI,KAAK,CAAC,aAAa,GAAG,UAAU;YACzE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC;YAC7B,CAAC,CAAC,KAAK,CAAC,aAAa;KACxB,CAAC,CAAC;IACH,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO,QAAQ,CAAC;IAC5C,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAwB;IAC9D,OAAO,UAAU,CAAC;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,SAAS,EAAE,KAAK,CAAC,SAAS;KAC3B,CAAC,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,GAA4B,EAAE,KAAa;IAC1D,IAAI,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,gCAAgC,KAAK,mCAAmC,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;AACpB,CAAC;AAED,SAAS,GAAG,CAAC,GAA4B,EAAE,KAAa;IACtD,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,6BAA6B,KAAK,kCAAkC,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,KAAK,CAAC,GAA4B,EAAE,KAAa;IACxD,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9B,IAAI,CAAC,CAAC,CAAC,YAAY,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,6BAA6B,KAAK,6BAA6B,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,SAAS,CAAC,GAA4B,EAAE,KAAa;IAC5D,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,UAAU,CAAC,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CAAC,6BAA6B,KAAK,wCAAwC,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,CAAe,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAiB;IACvD,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IACD,MAAM,GAAG,GAAG,OAAkC,CAAC;IAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,qBAAqB,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,yDAAyD,IAAI,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IACvD,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;QAClG,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;IAC1F,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAChD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IACD,OAAO;QACL,IAAI,EAAE,qBAAqB;QAC3B,eAAe,EAAE,cAAc;QAC/B,gBAAgB,EAAE,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC;QAC9C,gBAAgB,EAAE,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC;QAC9C,WAAW,EAAE,GAAG,CAAC,GAAG,EAAE,aAAa,CAAC;QACpC,OAAO,EAAE,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC;QAC9B,UAAU,EAAE,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC;QACxC,YAAY,EAAE,SAAS,CAAC,GAAG,EAAE,cAAc,CAAC;QAC5C,aAAa,EAAE,SAAS;QACxB,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE,WAAW,CAAC;KACnC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,8BAA8B,CAC5C,OAAiC;IAEjC,OAAO;QACL,UAAU,EAAE,sBAAsB,CAAC,OAAO,CAAC;QAC3C,eAAe,EAAE,OAAO,CAAC,iBAAiB;QAC1C,WAAW,EAAE,OAAO,CAAC,aAAa;QAClC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,aAAa,CAAC;QAC5F,UAAU,EAAE;YACV,cAAc,EAAE,OAAO,CAAC,UAAU,CAAC,cAAc;YACjD,kBAAkB,EAAE,OAAO,CAAC,UAAU,CAAC,kBAAkB;YACzD,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,QAAQ;YACrC,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,WAAW;YAC3C,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,eAAe,KAAK,SAAS;gBAClD,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,UAAU,CAAC,eAAe,EAAE;gBACzD,CAAC,CAAC,EAAE,CAAC;SACR;KACF,CAAC;AACJ,CAAC;AAcD,SAAS,MAAM,CAAC,MAAc;IAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAwB,EACxB,MAAoB,EACpB,MAA4E;IAE5E,IAAI,KAAK,CAAC,eAAe,KAAK,wBAAwB,EAAE,CAAC;QACvD,MAAM,IAAI,GACR,KAAK,CAAC,eAAe,GAAG,wBAAwB;YAC9C,CAAC,CAAC,gCAAgC;YAClC,CAAC,CAAC,mCAAmC,CAAC;QAC1C,OAAO,MAAM,CACX,mEAAmE,KAAK,CAAC,eAAe,GAAG;YACzF,yBAAyB,wBAAwB,MAAM,IAAI,EAAE,CAChE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACvF,OAAO,MAAM,CACX,yDAAyD,KAAK,CAAC,gBAAgB,WAAW;YACxF,wFAAwF,CAC3F,CAAC;IACJ,CAAC;IAED,IAAI,OAAiC,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,GAAG,sBAAsB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,OAAO,MAAM,CACX,6BAA6B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAChF,CAAC;IACJ,CAAC;IACD,IAAI,sBAAsB,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC;QAC1D,OAAO,MAAM,CACX,2FAA2F;YACzF,sCAAsC,CACzC,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,iBAAiB,EAAE,wBAAwB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7F,OAAO,MAAM,CACX,yFAAyF;YACvF,YAAY,OAAO,CAAC,iBAAiB,EAAE,CAC1C,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,MAAM,CACX,mFAAmF;YACjF,mEAAmE,CACtE,CAAC;IACJ,CAAC;IACD,IAAI,UAAuC,CAAC;IAC5C,IAAI,CAAC;QACH,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,OAAO,MAAM,CACX,+BAA+B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAClF,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,iBAAiB,CAC/B,8BAA8B,CAAC,OAAO,CAAC,EACvC,UAAU,EACV,MAAM,EACN,MAAM,CACP,CAAC;IACF,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,4EAA4E;QAC5E,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;IACnD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,gBAAgB,KAAK,KAAK,CAAC,gBAAgB,EAAE,CAAC;QACnG,OAAO,MAAM,CACX,mEAAmE,KAAK,CAAC,gBAAgB,KAAK;YAC5F,iEAAiE,CACpE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC1E,OAAO,MAAM,CACX,4BAA4B,KAAK,CAAC,gBAAgB,2CAA2C;YAC3F,qFAAqF,CACxF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,OAAO;QACP,UAAU;QACV,0FAA0F;QAC1F,qFAAqF;QACrF,sEAAsE;QACtE,oBAAoB,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;KACnF,CAAC;AACJ,CAAC"}
|