twin3-sdk 0.2.14-pre-alpha → 0.2.15-pre-alpha

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/README.md CHANGED
@@ -66,7 +66,8 @@ L1 also exposes a candidate discovery evidence API for xAgent ID and xHuman ID.
66
66
 
67
67
  Python and Node support [explicit Matrix comparison profiles](docs/matrix-runtime-profiles.md),
68
68
  including weighted/masked cosine and structured coverage results. Node constructor
69
- coercions still differ; Node Inbox payment admission remains Python-only. Evidence
69
+ coercions still differ. Node TaskInbox admits owner-policy receipts with
70
+ applyPayment and injected-verifier receipts with applyX402Payment. Evidence
70
71
  trail is experimental until an actual consumer supplies durable integration evidence.
71
72
 
72
73
  `(experimental)` marks a capability whose row in [`tests/capability-inventory.json`](tests/capability-inventory.json) is not yet `available`: it is importable today and its surface may still change. Everything unmarked is `available`. A name written `Class.method` is a method, not a module export. Every name in this table is checked against a real public export by [`tests/test_readme_capability_claims.py`](tests/test_readme_capability_claims.py).
@@ -115,22 +116,22 @@ pip install -e /path/to/twin3-sdk
115
116
  * Any protocol or algorithm improvement in `twin3-sdk` is reflected in both applications in **0 milliseconds**.
116
117
 
117
118
  #### 2. Production Distribution
118
- `v0.2.14-pre-alpha` is the current release candidate: `0.2.14-pre-alpha` for npm and `0.2.14a0` for PyPI. Publication is pending. Consumers must pin the exact released version and lock its registry integrity; never use a mutable range for a cryptographic protocol dependency. This release stays pre-alpha. Marketplace or sales copy may offer it for evaluation; it is not a stable release, and the npm dist-tag is `next`, not `latest`.
119
+ `v0.2.15-pre-alpha` is the current release candidate: `0.2.15-pre-alpha` for npm and `0.2.15a0` for PyPI. Publication is pending. Consumers must pin the exact released version and lock its registry integrity; never use a mutable range for a cryptographic protocol dependency. This release stays pre-alpha. Marketplace or sales copy may offer it for evaluation; it is not a stable release, and the npm dist-tag is `next`, not `latest`.
119
120
 
120
121
  `package.json` `engines` sets Node.js to `>=18` and does not set an npm engine. Any npm that can install a tarball can consume the package. Packing this repository was checked with npm 10 or newer; the stricter npm version in the release runbook applies only to Trusted Publishing.
121
122
 
122
123
  After publication, install the exact released version from the registry:
123
124
 
124
125
  ```bash
125
- npm install twin3-sdk@0.2.14-pre-alpha
126
- pip install twin3-sdk==0.2.14a0
126
+ npm install twin3-sdk@0.2.15-pre-alpha
127
+ pip install twin3-sdk==0.2.15a0
127
128
  ```
128
129
 
129
130
  For local evaluation, you can also use the packed npm tarball. Do not install from a git URL or a version range:
130
131
 
131
132
  ```bash
132
133
  npm pack --ignore-scripts
133
- npm install ./twin3-sdk-0.2.14-pre-alpha.tgz
134
+ npm install ./twin3-sdk-0.2.15-pre-alpha.tgz
134
135
  ```
135
136
 
136
137
  ---
package/index.d.ts CHANGED
@@ -848,6 +848,12 @@ export interface KyaBinding {
848
848
  receipt: string;
849
849
  }
850
850
 
851
+ export declare class DidResolutionError extends Error { readonly code: string; }
852
+ export declare function didWebForHost(host: string): string;
853
+ export declare function didWebUrl(did: string): string;
854
+ export declare function resolveDidWeb(did: string, fetch: (method: string, url: string, headers: Record<string, string>, body: null) => [number, string | Uint8Array | null], maxBytes?: number): { did: string; url: string; method_id: string; key_id: string; public_key: string; };
855
+ export declare function multibaseEd25519(raw: Uint8Array, multicodec?: boolean): string;
856
+ export declare const AuthorityAnchor: { readonly SUPPORTED_KINDS: readonly string[]; isValidKind(kind: string): boolean; };
851
857
  export declare function createKyaChallenge(params: {
852
858
  owner: string;
853
859
  identity: KyaIdentity;
@@ -1032,6 +1038,54 @@ export declare function verifyEnvelopeSignature(envelope: Envelope, publicKey: C
1032
1038
 
1033
1039
  export declare function scanText(text: string, options?: { source?: string; now?: number }): ShieldVerdict;
1034
1040
 
1041
+ export declare const MEMORY_SCHEMA: string;
1042
+ export declare const REVOCATION_SCHEMA: string;
1043
+
1044
+ export declare class MemoryRejected extends Error {
1045
+ readonly code: string;
1046
+ }
1047
+
1048
+ export interface MemoryShieldStamp {
1049
+ verdict: string;
1050
+ reason_codes: string[];
1051
+ receipt: unknown;
1052
+ }
1053
+
1054
+ export interface MemoryEntry {
1055
+ schema: string;
1056
+ id: string;
1057
+ text: string;
1058
+ origin: string;
1059
+ kind: "data" | "instruction";
1060
+ treat_as: "data" | "instruction";
1061
+ actor: string;
1062
+ source: string;
1063
+ source_epoch: number;
1064
+ at: number;
1065
+ shield: MemoryShieldStamp | null;
1066
+ flagged: boolean;
1067
+ }
1068
+
1069
+ export interface MemoryRevocation {
1070
+ schema: string;
1071
+ source: string;
1072
+ epoch: number;
1073
+ actor: string;
1074
+ reason: string;
1075
+ at: number;
1076
+ removed: number;
1077
+ }
1078
+
1079
+ export declare class MemoryGuard {
1080
+ constructor(store: { entries?: MemoryEntry[]; revocations?: Record<string, MemoryRevocation>; purges?: unknown[] }, ensemble?: unknown, intel?: unknown);
1081
+ revokedEpoch(source: string): number | null;
1082
+ purgeSource(source: string, actor: string, reason: string, now?: number, epoch?: number): MemoryRevocation;
1083
+ write(text: string, origin: string, actor: string, kind?: "data" | "instruction", source?: string, sourceEpoch?: number, now?: number): MemoryEntry;
1084
+ recall(kinds?: Array<"data" | "instruction">, origins?: string[] | null, includeFlagged?: boolean): MemoryEntry[];
1085
+ instructions(): MemoryEntry[];
1086
+ purgeOrigin(origin: string, actor: string, reason: string): number;
1087
+ }
1088
+
1035
1089
  export declare const TASK_EXPORT_SCHEMA: string;
1036
1090
 
1037
1091
  export declare function taskTrustLine(task: Record<string, unknown>, row: Record<string, unknown> | null): string;
@@ -1552,8 +1606,8 @@ export interface TaskInboxOptions {
1552
1606
  ensemble?: null | undefined;
1553
1607
  intel?: null | undefined;
1554
1608
  requirePlanDiff?: true;
1555
- replayStore?: null | undefined;
1556
- replay_store?: null | undefined;
1609
+ replayStore?: { consume(key: string, expiresAt: number, now?: number): boolean | Promise<boolean> } | null;
1610
+ replay_store?: { consume(key: string, expiresAt: number, now?: number): boolean | Promise<boolean> } | null;
1557
1611
  }
1558
1612
 
1559
1613
  export declare class IntakeRejected extends Error {
@@ -1614,6 +1668,10 @@ export declare class TaskInbox {
1614
1668
  }): TaskInboxTask;
1615
1669
  clearReview(taskId: string, actor: string, reason: string, now?: number): TaskInboxTask;
1616
1670
  clearReview(input: { taskId: string; actor: string; reason: string; now?: number }): TaskInboxTask;
1671
+ getPolicy(): Record<string, any> | null;
1672
+ setPolicy(policy: Record<string, any>): Record<string, any>;
1673
+ quoteFor(identity: { kind: string; value: string }, now?: number): Record<string, any> | null;
1674
+ applyPayment(identity: { kind: string; value: string }, receipt: Record<string, any>, ownerPublicKey: CryptoKeyInput, quoteBody?: Record<string, any> | null, actor?: string, now?: number): Promise<Record<string, any>>;
1617
1675
  recordPlanDiff(taskId: string, before: any[], after: any[], actor: string,
1618
1676
  authorized?: string[], now?: number): Record<string, any>;
1619
1677
  recordPlanDiff(input: {
@@ -1624,6 +1682,19 @@ export declare class TaskInbox {
1624
1682
  authorized?: string[];
1625
1683
  now?: number;
1626
1684
  }): Record<string, any>;
1685
+ getPolicy(): Record<string, any> | null;
1686
+ setPolicy(policy: Record<string, any>): Record<string, any>;
1687
+ applyX402Payment(
1688
+ identity: { kind: string; value: string },
1689
+ demand: Record<string, any>,
1690
+ receipt: Record<string, any>,
1691
+ publicKey: CryptoKeyInput,
1692
+ chainVerifier: { verifyPayment?: (demand: Record<string, any>, receipt: Record<string, any>) => Promise<Record<string, any>> | Record<string, any>; verify_payment?: (demand: Record<string, any>, receipt: Record<string, any>) => Promise<Record<string, any>> | Record<string, any> } | ((demand: Record<string, any>, receipt: Record<string, any>) => Promise<Record<string, any>> | Record<string, any>),
1693
+ replayStore?: { reserve: Function; commit: Function; release: Function } | null,
1694
+ minimumConfirmations?: number,
1695
+ actor?: string,
1696
+ now?: number,
1697
+ ): Promise<Record<string, any>>;
1627
1698
  expireStale(now?: number): string[];
1628
1699
  verifyAuditChain(taskId: string): boolean;
1629
1700
  }
package/js/index.js CHANGED
@@ -86,6 +86,12 @@ module.exports = {
86
86
  X402_FETCH_BOUNDS: discovery.X402_FETCH_BOUNDS,
87
87
  keyId: trust.keyId,
88
88
  createKyaChallenge: trust.createKyaChallenge,
89
+ AuthorityAnchor: trust.AuthorityAnchor,
90
+ DidResolutionError: trust.DidResolutionError,
91
+ didWebForHost: trust.didWebForHost,
92
+ didWebUrl: trust.didWebUrl,
93
+ resolveDidWeb: trust.resolveDidWeb,
94
+ multibaseEd25519: trust.multibaseEd25519,
89
95
  verifyKyaChallenge: trust.verifyKyaChallenge,
90
96
  completeKyaChallenge: trust.completeKyaChallenge,
91
97
  verifyKyaBinding: trust.verifyKyaBinding,
@@ -154,6 +160,10 @@ module.exports = {
154
160
  exportTask: comm.exportTask,
155
161
  taskTrustLine: comm.taskTrustLine,
156
162
  scanText: shield.scanText,
163
+ MEMORY_SCHEMA: shield.MEMORY_SCHEMA,
164
+ REVOCATION_SCHEMA: shield.REVOCATION_SCHEMA,
165
+ MemoryGuard: shield.MemoryGuard,
166
+ MemoryRejected: shield.MemoryRejected,
157
167
  PLANDIFF_SCHEMA: shield.PLANDIFF_SCHEMA,
158
168
  PLANDIFF_AUTHORITIES: shield.PLANDIFF_AUTHORITIES,
159
169
  PLANDIFF_CONSEQUENTIAL: shield.PLANDIFF_CONSEQUENTIAL,
package/js/index.mjs CHANGED
@@ -15,6 +15,7 @@ export const AGENT_CARD_SCHEMA = api.AGENT_CARD_SCHEMA;
15
15
  export const AI_EXPOSURE_SCHEMA = api.AI_EXPOSURE_SCHEMA;
16
16
  export const APPEAL_SCHEMA = api.APPEAL_SCHEMA;
17
17
  export const AUTHORITY_AMBIGUOUS = api.AUTHORITY_AMBIGUOUS;
18
+ export const AuthorityAnchor = api.AuthorityAnchor;
18
19
  export const AuthorizationLeaseError = api.AuthorizationLeaseError;
19
20
  export const BUNDLE_MAX_AGE_SECONDS = api.BUNDLE_MAX_AGE_SECONDS;
20
21
  export const CARD_CANONICALIZATION = api.CARD_CANONICALIZATION;
@@ -43,6 +44,7 @@ export const DISCLOSURE_STATES = api.DISCLOSURE_STATES;
43
44
  export const DISCOVERY_SUMMARY_SCHEMA = api.DISCOVERY_SUMMARY_SCHEMA;
44
45
  export const DISCOVERY_VIEW_SCHEMA = api.DISCOVERY_VIEW_SCHEMA;
45
46
  export const DenseVector256 = api.DenseVector256;
47
+ export const DidResolutionError = api.DidResolutionError;
46
48
  export const DisclosureError = api.DisclosureError;
47
49
  export const DiscoveryEvidenceError = api.DiscoveryEvidenceError;
48
50
  export const ENTERPRISE_MATRIX_SCHEMA = api.ENTERPRISE_MATRIX_SCHEMA;
@@ -63,6 +65,9 @@ export const InMemoryNonceStore = api.InMemoryNonceStore;
63
65
  export const InMemorySignCountStore = api.InMemorySignCountStore;
64
66
  export const IntakeRejected = api.IntakeRejected;
65
67
  export const MAX_FEDERATED_PENALTY_PER_PEER = api.MAX_FEDERATED_PENALTY_PER_PEER;
68
+ export const MEMORY_SCHEMA = api.MEMORY_SCHEMA;
69
+ export const MemoryGuard = api.MemoryGuard;
70
+ export const MemoryRejected = api.MemoryRejected;
66
71
  export const NONCE_RETENTION = api.NONCE_RETENTION;
67
72
  export const PLANDIFF_AUTHORITIES = api.PLANDIFF_AUTHORITIES;
68
73
  export const PLANDIFF_AUTHORIZED_BY_KIND = api.PLANDIFF_AUTHORIZED_BY_KIND;
@@ -79,6 +84,7 @@ export const REPLAY_FINAL_EXPIRY = api.REPLAY_FINAL_EXPIRY;
79
84
  export const REPLAY_RESERVATION_SECONDS = api.REPLAY_RESERVATION_SECONDS;
80
85
  export const REPUTATION_BUNDLE_SCHEMA = api.REPUTATION_BUNDLE_SCHEMA;
81
86
  export const REPUTATION_HALF_LIFE_SECONDS = api.REPUTATION_HALF_LIFE_SECONDS;
87
+ export const REVOCATION_SCHEMA = api.REVOCATION_SCHEMA;
82
88
  export const SETTLEMENT_RECEIPT_SCHEMA = api.SETTLEMENT_RECEIPT_SCHEMA;
83
89
  export const SOVEREIGN_CARD_SCHEMA = api.SOVEREIGN_CARD_SCHEMA;
84
90
  export const STALE_AFTER = api.STALE_AFTER;
@@ -153,6 +159,8 @@ export const descriptorBody = api.descriptorBody;
153
159
  export const descriptorBytes = api.descriptorBytes;
154
160
  export const descriptorDigest = api.descriptorDigest;
155
161
  export const descriptorEtag = api.descriptorEtag;
162
+ export const didWebForHost = api.didWebForHost;
163
+ export const didWebUrl = api.didWebUrl;
156
164
  export const diffPlans = api.diffPlans;
157
165
  export const disclose = api.disclose;
158
166
  export const disclosureStateOf = api.disclosureStateOf;
@@ -188,6 +196,7 @@ export const maxTierFor = api.maxTierFor;
188
196
  export const mayClaim = api.mayClaim;
189
197
  export const mayDeny = api.mayDeny;
190
198
  export const migrateCardToEmit = api.migrateCardToEmit;
199
+ export const multibaseEd25519 = api.multibaseEd25519;
191
200
  export const normalizeAction = api.normalizeAction;
192
201
  export const normalizeAiExposure = api.normalizeAiExposure;
193
202
  export const normalizeCounterpartyStore = api.normalizeCounterpartyStore;
@@ -207,6 +216,7 @@ export const receiptForRecord = api.receiptForRecord;
207
216
  export const recordExecutionOutcome = api.recordExecutionOutcome;
208
217
  export const resolveAppeal = api.resolveAppeal;
209
218
  export const resolveDeclarationEndpoints = api.resolveDeclarationEndpoints;
219
+ export const resolveDidWeb = api.resolveDidWeb;
210
220
  export const resolveDocumentPath = api.resolveDocumentPath;
211
221
  export const scanText = api.scanText;
212
222
  export const signA2AAgentCard = api.signA2AAgentCard;
@@ -2,6 +2,7 @@
2
2
  const core = require("./core.js");
3
3
  const shield = require("./shield.js");
4
4
  const trust = require("./trust.js");
5
+ const settlement = require("./settlement.js");
5
6
 
6
7
  const commCounterparty = require("../comm/counterparty.js");
7
8
  function envelopeBuilderNumber(value, code) {
@@ -684,7 +685,7 @@ function taskAudit(task, state, actor, reason, at, receipt = null) {
684
685
  task.history.push(entry);
685
686
  task.audit_head = entry.digest;
686
687
  }
687
- const TASK_INBOX_UNSUPPORTED_OPTIONS = Object.freeze(["ensemble", "intel", "replayStore", "replay_store"]);
688
+ const TASK_INBOX_UNSUPPORTED_OPTIONS = Object.freeze(["ensemble", "intel"]);
688
689
  const TASK_INBOX_UNSUPPORTED_OPTION_SET = new Set(TASK_INBOX_UNSUPPORTED_OPTIONS);
689
690
  function taskInboxUnsupportedOption(name) {
690
691
  const error = new core.ProtocolError("inbox_option_not_supported");
@@ -715,6 +716,90 @@ function taskInboxCopyOwnEnumerableOptions(target, source) {
715
716
  }
716
717
  }
717
718
  }
719
+ function x402Demand(value) {
720
+ const source = core.isPlainObject(value) ? value : {};
721
+ return {
722
+ schema: source.schema || "twin3-settlement-demand-v1",
723
+ demand_id: String(source.demand_id || ""),
724
+ recipient: String(source.recipient || ""),
725
+ amount: source.amount,
726
+ currency: source.currency || "USD",
727
+ network: source.network || "base",
728
+ token_address: source.token_address == null ? null : source.token_address,
729
+ expires_at: source.expires_at,
730
+ memo: source.memo || "",
731
+ };
732
+ }
733
+ function x402Receipt(value) {
734
+ const source = core.isPlainObject(value) ? value : {};
735
+ return {
736
+ schema: source.schema || "twin3-settlement-receipt-v1",
737
+ demand_id: String(source.demand_id || ""),
738
+ tx_hash: String(source.tx_hash || ""),
739
+ payer: String(source.payer || ""),
740
+ amount: source.amount,
741
+ currency: String(source.currency || ""),
742
+ settled_at: source.settled_at,
743
+ recipient: String(source.recipient || ""),
744
+ signature: String(source.signature || ""),
745
+ public_key: source.public_key == null ? null : source.public_key,
746
+ network: source.network || "base",
747
+ };
748
+ }
749
+ function x402JournalDigest(journal) {
750
+ return core.crypto.createHash("sha256")
751
+ .update(core.legacySortedBytes({ binding: journal.binding, context: journal.context, credit: journal.credit }, "payment_journal_unserializable"))
752
+ .digest("hex");
753
+ }
754
+ function x402SameCanonical(left, right) {
755
+ return core.legacySortedBytes(left, "payment_journal_unserializable")
756
+ .equals(core.legacySortedBytes(right, "payment_journal_unserializable"));
757
+ }
758
+ function x402Context(policy, row) {
759
+ const source = row || {};
760
+ return {
761
+ policy,
762
+ kya_binding: source.kya_binding == null ? null : source.kya_binding,
763
+ key_possession_verified: Boolean(source.key_possession_verified),
764
+ bound_key_id: source.bound_key_id == null ? null : source.bound_key_id,
765
+ bound_public_key: source.bound_public_key == null ? null : source.bound_public_key,
766
+ handshake_receipt: source.handshake_receipt == null ? null : source.handshake_receipt,
767
+ bound_wallet: source.bound_wallet == null ? null : source.bound_wallet,
768
+ };
769
+ }
770
+ function x402BindingError(row) {
771
+ const binding = row && row.kya_binding;
772
+ if (!core.pythonTruthy(row && row.key_possession_verified) || !core.isPlainObject(binding))
773
+ return "payment_current_binding_required";
774
+ if (binding.key_id !== row.bound_key_id || binding.public_key !== row.bound_public_key || binding.receipt !== row.handshake_receipt)
775
+ return "payment_current_binding_mismatch";
776
+ return null;
777
+ }
778
+ function x402ChainAdapter(chainVerifier, demand, receipt) {
779
+ return {
780
+ async verifyPayment() {
781
+ const method = typeof chainVerifier === "function"
782
+ ? chainVerifier
783
+ : (chainVerifier && (chainVerifier.verifyPayment || chainVerifier.verify_payment));
784
+ if (typeof method !== "function")
785
+ core.fail("chain_verifier_invalid");
786
+ const evidence = await method.call(chainVerifier, demand, receipt);
787
+ if (!evidence || typeof evidence !== "object")
788
+ return evidence;
789
+ return {
790
+ txHash: evidence.txHash || evidence.tx_hash,
791
+ network: evidence.network,
792
+ payer: evidence.payer,
793
+ recipient: evidence.recipient,
794
+ amount: evidence.amount,
795
+ currency: evidence.currency,
796
+ confirmations: evidence.confirmations,
797
+ finalized: evidence.finalized,
798
+ tokenAddress: evidence.tokenAddress || evidence.token_address,
799
+ };
800
+ },
801
+ };
802
+ }
718
803
  class TaskInbox {
719
804
  constructor(...args) {
720
805
  if (args.length > 4) {
@@ -770,6 +855,10 @@ class TaskInbox {
770
855
  if (!Number.isFinite(this._rateWindow))
771
856
  throw new core.ProtocolError("rate_window_invalid");
772
857
  this._requirePlanDiff = true;
858
+ const replay = options.replay_store !== undefined && options.replay_store !== null
859
+ ? options.replay_store
860
+ : (options.replayStore !== undefined && options.replayStore !== null ? options.replayStore : null);
861
+ this._nonceStore = replay;
773
862
  }
774
863
  _now(override) { return taskNow(this._clock, override); }
775
864
  _partition() {
@@ -803,6 +892,254 @@ class TaskInbox {
803
892
  part.rate[senderKey].push(at);
804
893
  return true;
805
894
  }
895
+ getPolicy() {
896
+ const policy = this._partition().policy;
897
+ if (!policy)
898
+ return null;
899
+ return structuredClone(policy);
900
+ }
901
+ setPolicy(policy) {
902
+ const normalized = settlement.normalizePolicy(policy);
903
+ this._partition().policy = normalized;
904
+ return structuredClone(normalized);
905
+ }
906
+ async applyX402Payment(identity, demand, receipt, publicKey, chainVerifier, replayStore = undefined, minimumConfirmations = 1, actor = "x402", now = undefined) {
907
+ const at = this._now(now);
908
+ if (typeof at !== "number" || !Number.isFinite(at))
909
+ core.fail("verification_time_invalid");
910
+ identity = structuredClone(identity);
911
+ this._parties.ensure(identity, { now: at });
912
+ if (this._parties.isFrozen(identity))
913
+ core.fail("counterparty_frozen");
914
+ this._parties.assertAccessReady(identity, { now: at });
915
+ const store = replayStore != null ? replayStore : this._nonceStore;
916
+ if (store == null)
917
+ core.fail("payment_replay_store_required");
918
+ const policy = this.getPolicy();
919
+ if (!policy)
920
+ core.fail("payment_policy_not_configured");
921
+ demand = x402Demand(demand);
922
+ receipt = x402Receipt(receipt);
923
+ const payout = String(policy.payout_address || "").trim();
924
+ if (!payout)
925
+ core.fail("payment_payout_address_required");
926
+ if (demand.recipient.trim().toLowerCase() !== payout.toLowerCase())
927
+ core.fail("payment_demand_recipient_mismatch");
928
+ const price = core.isPlainObject(policy.price) ? policy.price : {};
929
+ if (Number(demand.amount) < Number(price.amount || 0))
930
+ core.fail("payment_demand_amount_insufficient");
931
+ const policyCurrency = String(price.currency || "").trim().toUpperCase();
932
+ if (!policyCurrency || demand.currency.toUpperCase() !== policyCurrency)
933
+ core.fail("payment_demand_currency_mismatch");
934
+ const partyRow = this._parties.get(identity, { now: at }) || {};
935
+ const boundWallet = String(partyRow.bound_wallet || "").trim().toLowerCase();
936
+ const payer = receipt.payer.trim().toLowerCase();
937
+ const senderValue = String(identity.value || "").trim().toLowerCase();
938
+ const senderKey = counterpartyKey(identity).toLowerCase();
939
+ if (boundWallet) {
940
+ if (payer !== boundWallet)
941
+ core.fail("payment_payer_mismatch");
942
+ }
943
+ else if (payer !== senderValue && payer !== senderKey) {
944
+ core.fail("payment_payer_mismatch");
945
+ }
946
+ const part = this._partition();
947
+ if (!core.isPlainObject(part.payment_journal))
948
+ part.payment_journal = {};
949
+ const journalBook = part.payment_journal;
950
+ const txHash = receipt.tx_hash.toLowerCase();
951
+ if (!publicKey)
952
+ core.fail("trusted_receipt_public_key_required");
953
+ let issuerKey;
954
+ try {
955
+ issuerKey = core.exportEd25519PublicKey(publicKey);
956
+ }
957
+ catch (error) {
958
+ core.fail("receipt_signature_invalid", error);
959
+ }
960
+ const binding = {
961
+ owner: this._owner,
962
+ identity_key: counterpartyKey(identity),
963
+ demand,
964
+ receipt,
965
+ issuer_public_key: issuerKey,
966
+ minimum_confirmations: minimumConfirmations,
967
+ };
968
+ const existingKey = Object.prototype.hasOwnProperty.call(journalBook, receipt.tx_hash) ? receipt.tx_hash : txHash;
969
+ if (Object.prototype.hasOwnProperty.call(journalBook, existingKey)) {
970
+ const journal = journalBook[existingKey];
971
+ let valid = false;
972
+ try {
973
+ valid = journal.schema === "twin3-x402-credit-journal-v1"
974
+ && journal.binding_digest === x402JournalDigest(journal)
975
+ && (journal.status === "pending_credit" || journal.status === "credited");
976
+ }
977
+ catch {
978
+ valid = false;
979
+ }
980
+ if (!valid)
981
+ core.fail("payment_journal_incomplete");
982
+ if (!x402SameCanonical(journal.binding, binding))
983
+ core.fail("payment_journal_binding_mismatch");
984
+ if (journal.status === "credited")
985
+ core.fail("payment_receipt_replayed");
986
+ return this._creditX402Journal(journal, at);
987
+ }
988
+ const bindingError = x402BindingError(partyRow);
989
+ if (bindingError)
990
+ throw new TierTransitionError(bindingError);
991
+ const splitAt = binding.identity_key.indexOf(":");
992
+ const journal = {
993
+ schema: "twin3-x402-credit-journal-v1",
994
+ binding,
995
+ context: x402Context(policy, partyRow),
996
+ credit: {
997
+ identity: { kind: binding.identity_key.slice(0, splitAt), value: binding.identity_key.slice(splitAt + 1) },
998
+ receipt: {
999
+ payment_id: `x402:${demand.network.toLowerCase()}:${txHash}`,
1000
+ quote_id: demand.demand_id,
1001
+ amount: String(receipt.amount),
1002
+ currency: receipt.currency,
1003
+ reference: receipt.tx_hash,
1004
+ paid_at: receipt.settled_at,
1005
+ },
1006
+ tier_seconds: core.pythonInt(policy.paid_tier_seconds, "paid_tier_seconds_invalid"),
1007
+ actor: String(actor),
1008
+ at,
1009
+ },
1010
+ status: "pending_credit",
1011
+ };
1012
+ journal.binding_digest = x402JournalDigest(journal);
1013
+ await settlement.verifyX402Payment({
1014
+ demandAmount: demand.amount,
1015
+ receiptAmount: receipt.amount,
1016
+ demandRecipient: demand.recipient,
1017
+ receiptRecipient: receipt.recipient,
1018
+ demandCurrency: demand.currency,
1019
+ receiptCurrency: receipt.currency,
1020
+ demandNetwork: demand.network,
1021
+ receiptNetwork: receipt.network,
1022
+ txHash: receipt.tx_hash,
1023
+ payer: receipt.payer,
1024
+ signature: receipt.signature,
1025
+ demandId: demand.demand_id,
1026
+ receiptDemandId: receipt.demand_id,
1027
+ settledAt: receipt.settled_at,
1028
+ trustedPublicKey: issuerKey,
1029
+ demandExpiresAt: demand.expires_at,
1030
+ demandSchema: demand.schema,
1031
+ receiptSchema: receipt.schema,
1032
+ tokenAddress: demand.token_address || undefined,
1033
+ now: at,
1034
+ chainVerifier: x402ChainAdapter(chainVerifier, demand, receipt),
1035
+ replayStore: store,
1036
+ minimumConfirmations,
1037
+ });
1038
+ journalBook[txHash] = journal;
1039
+ return this._creditX402Journal(journal, at);
1040
+ }
1041
+ _creditX402Journal(journal, at) {
1042
+ const credit = journal.credit;
1043
+ const identity = credit.identity;
1044
+ const current = this._parties.get(identity, { now: at }) || {};
1045
+ if (current.frozen)
1046
+ core.fail("counterparty_frozen");
1047
+ this._parties.assertAccessReady(identity, { now: at });
1048
+ if (!x402SameCanonical(journal.context, x402Context(this.getPolicy(), current)))
1049
+ core.fail("payment_recovery_pending");
1050
+ const originalEnd = credit.at + credit.tier_seconds;
1051
+ for (const entry of this._parties.ledger(identity)) {
1052
+ if (entry.kind === "payment" && entry.payment_id === credit.receipt.payment_id) {
1053
+ const drifted = ["quote_id", "amount", "currency", "reference"].some((key) => entry[key] !== credit.receipt[key]);
1054
+ if (drifted || typeof entry.paid_until !== "number" || !Number.isFinite(entry.paid_until) || entry.paid_until > originalEnd)
1055
+ core.fail("payment_recovery_pending");
1056
+ journal.status = "credited";
1057
+ journal.credited_at = entry.at;
1058
+ return current;
1059
+ }
1060
+ }
1061
+ const remaining = Math.floor(originalEnd - at);
1062
+ if (at < credit.at || remaining <= 0)
1063
+ core.fail("payment_recovery_pending");
1064
+ const row = this._parties.recordPayment(identity, structuredClone(credit.receipt), remaining, credit.actor, { now: at });
1065
+ journal.status = "credited";
1066
+ journal.credited_at = at;
1067
+ return row;
1068
+ }
1069
+ quoteFor(identity, now = undefined) {
1070
+ const at = this._now(now);
1071
+ this._parties.ensure(identity, { now: at });
1072
+ if (this._parties.isFrozen(identity))
1073
+ core.fail("counterparty_frozen");
1074
+ this._parties.assertAccessReady(identity, { now: at });
1075
+ const policy = this._partition().policy;
1076
+ if (!policy)
1077
+ return null;
1078
+ const offer = settlement.quoteForCounterparty(policy, this._parties.get(identity, { now: at }), { now: at });
1079
+ if (offer && core.isPlainObject(offer) && offer.quote_id) {
1080
+ const part = this._partition();
1081
+ if (!core.isPlainObject(part.quotes))
1082
+ part.quotes = {};
1083
+ part.quotes[offer.quote_id] = structuredClone(offer);
1084
+ }
1085
+ return offer;
1086
+ }
1087
+ async applyPayment(identity, receipt, ownerPublicKey, quoteBody = undefined, actor = "billing", now = undefined) {
1088
+ void quoteBody;
1089
+ const at = this._now(now);
1090
+ this._parties.ensure(identity, { now: at });
1091
+ if (this._parties.isFrozen(identity))
1092
+ core.fail("counterparty_is_frozen");
1093
+ this._parties.assertAccessReady(identity, { now: at });
1094
+ const policy = this.getPolicy();
1095
+ const partyRow = this._parties.get(identity, { now: at });
1096
+ if (partyRow && !partyRow.verified)
1097
+ core.fail("unverified_identity_cannot_pay");
1098
+ if (partyRow && core.isPlainObject(receipt)) {
1099
+ for (const entry of Array.isArray(partyRow.ledger) ? partyRow.ledger : []) {
1100
+ if (entry.kind !== "payment")
1101
+ continue;
1102
+ const match = (core.pythonTruthy(receipt.receipt_id) && entry.receipt_id === receipt.receipt_id)
1103
+ || (core.pythonTruthy(receipt.signature) && core.secureEquals(entry.signature, receipt.signature))
1104
+ || (core.pythonTruthy(receipt.quote_id) && core.pythonTruthy(receipt.reference)
1105
+ && entry.quote_id === receipt.quote_id && entry.reference === receipt.reference);
1106
+ if (match)
1107
+ return this._parties.recordPayment(identity, receipt, 0, actor, { now: at });
1108
+ }
1109
+ }
1110
+ if (!policy)
1111
+ core.fail("payment_policy_not_configured");
1112
+ const quoteId = core.isPlainObject(receipt) ? receipt.quote_id : null;
1113
+ let canonical = null;
1114
+ if (core.pythonTruthy(quoteId)) {
1115
+ const quotes = this._partition().quotes;
1116
+ canonical = core.isPlainObject(quotes) ? quotes[quoteId] : null;
1117
+ if (canonical == null)
1118
+ core.fail("quote_not_found");
1119
+ }
1120
+ const verified = settlement.verifyReceipt({
1121
+ receipt,
1122
+ ownerPublicKey,
1123
+ owner: this._owner,
1124
+ identity,
1125
+ quote: canonical,
1126
+ now: at,
1127
+ });
1128
+ const grantsSeconds = canonical && canonical.grants_seconds != null
1129
+ ? core.pythonInt(canonical.grants_seconds, "grants_seconds_invalid")
1130
+ : core.pythonInt(policy.paid_tier_seconds, "paid_tier_seconds_invalid");
1131
+ const receiptId = core.pythonTruthy(verified.receipt_id)
1132
+ ? verified.receipt_id
1133
+ : (core.pythonTruthy(verified.quote_id) && core.pythonTruthy(verified.reference)
1134
+ ? `${verified.quote_id}:${verified.reference}`
1135
+ : verified.signature);
1136
+ if (this._nonceStore != null && core.pythonTruthy(receiptId)) {
1137
+ const consumed = await this._nonceStore.consume(`offchain:${this._owner}:${receiptId}`, at + grantsSeconds, at);
1138
+ if (!consumed)
1139
+ return this._parties.recordPayment(identity, verified, 0, actor, { now: at });
1140
+ }
1141
+ return this._parties.recordPayment(identity, verified, grantsSeconds, actor, { now: at });
1142
+ }
806
1143
  _nonceCheck(part, senderKey, nonce, deadline, at) {
807
1144
  const seen = part.nonces;
808
1145
  for (const key of Object.keys(seen)) {
@@ -879,7 +1216,30 @@ class TaskInbox {
879
1216
  throw new IntakeRejected("sender_unverified_pinned_to_G0");
880
1217
  }
881
1218
  if ((TASK_TIER_ORDER[tier] ?? 0) < TASK_TIER_ORDER[requestedTier]) {
882
- throw new IntakeRejected(`sender_tier_${tier}_below_${requestedTier}`);
1219
+ const policy = part.policy;
1220
+ if (policy && tier === "G0") {
1221
+ let offer;
1222
+ try {
1223
+ offer = settlement.quoteForCounterparty(policy, this._parties.get(sender, { now: at }), { now: at });
1224
+ }
1225
+ catch (error) {
1226
+ throw new IntakeRejected(taskErrorCode(error, "payment_required"), null, null, error);
1227
+ }
1228
+ if (offer && offer.free_allowance) {
1229
+ this._parties.noteFreeAllowanceUse(sender, { now: at });
1230
+ tier = "G1";
1231
+ }
1232
+ else if (offer) {
1233
+ if (core.isPlainObject(offer) && offer.quote_id) {
1234
+ if (!core.isPlainObject(part.quotes))
1235
+ part.quotes = {};
1236
+ part.quotes[offer.quote_id] = structuredClone(offer);
1237
+ }
1238
+ throw new IntakeRejected("payment_required", null, offer);
1239
+ }
1240
+ }
1241
+ if ((TASK_TIER_ORDER[tier] ?? 0) < TASK_TIER_ORDER[requestedTier])
1242
+ throw new IntakeRejected(`sender_tier_${tier}_below_${requestedTier}`);
883
1243
  }
884
1244
  let signatureVerified = false;
885
1245
  let bound;
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+ const shield = require("./internal/shield.js");
5
+
6
+ const MEMORY_SCHEMA = "twin3-shield-memory-entry-v1";
7
+ const REVOCATION_SCHEMA = "twin3-shield-memory-revocation-v1";
8
+ const ORIGINS = new Set(["owner", "system", "counterparty", "tool", "web", "file", "subagent"]);
9
+ const INSTRUCTION_ORIGINS = new Set(["owner", "system"]);
10
+
11
+ class MemoryRejected extends Error {
12
+ constructor(code) {
13
+ super(code);
14
+ this.name = "MemoryRejected";
15
+ this.code = code;
16
+ }
17
+ }
18
+
19
+ function reject(code) {
20
+ throw new MemoryRejected(code);
21
+ }
22
+
23
+ function requireStore(store) {
24
+ if (store == null || typeof store !== "object" || Array.isArray(store)) reject("memory_store_required");
25
+ if (!Object.prototype.hasOwnProperty.call(store, "entries")) store.entries = [];
26
+ return store;
27
+ }
28
+
29
+ function isNonnegativeInteger(value) {
30
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
31
+ }
32
+
33
+ function legacySortedJson(value) {
34
+ const ordered = {};
35
+ for (const key of Object.keys(value).sort()) ordered[key] = value[key];
36
+ return Buffer.from(JSON.stringify(ordered));
37
+ }
38
+
39
+ function clone(value) {
40
+ return structuredClone(value);
41
+ }
42
+
43
+ class MemoryGuard {
44
+ constructor(store, ensemble = null, intel = null) {
45
+ if (ensemble != null || intel != null) reject("memory_scanner_unsupported");
46
+ this._store = requireStore(store);
47
+ }
48
+
49
+ _revocations() {
50
+ if (!Object.prototype.hasOwnProperty.call(this._store, "revocations") || this._store.revocations == null)
51
+ this._store.revocations = {};
52
+ return this._store.revocations;
53
+ }
54
+
55
+ revokedEpoch(source) {
56
+ const entry = this._revocations()[source == null ? "" : String(source)];
57
+ return entry ? entry.epoch : null;
58
+ }
59
+
60
+ _isRevoked(source, sourceEpoch) {
61
+ const revoked = this.revokedEpoch(source);
62
+ if (revoked == null) return false;
63
+ const epoch = sourceEpoch == null || sourceEpoch === false ? 0 : sourceEpoch;
64
+ return Number(epoch) <= Number(revoked);
65
+ }
66
+
67
+ purgeSource(source, actor, reason, now = undefined, epoch = undefined) {
68
+ const at = now == null ? Math.floor(Date.now() / 1000) : Math.trunc(Number(now));
69
+ source = source == null ? "" : String(source);
70
+ if (!source.trim()) reject("memory_source_required");
71
+ if (epoch !== undefined && !isNonnegativeInteger(epoch)) reject("memory_revocation_epoch_invalid");
72
+ const current = this.revokedEpoch(source);
73
+ if (epoch === undefined) epoch = current == null ? 1 : current + 1;
74
+ if (current != null && epoch <= current) reject("memory_revocation_not_monotonic");
75
+ const revocations = this._revocations();
76
+ revocations[source] = {
77
+ schema: REVOCATION_SCHEMA,
78
+ source,
79
+ epoch,
80
+ actor: actor == null ? "" : String(actor),
81
+ reason: reason == null ? "" : String(reason),
82
+ at,
83
+ };
84
+ const before = this._store.entries.length;
85
+ this._store.entries = this._store.entries.filter(
86
+ (entry) => !(String(entry.source || "") === source && this._isRevoked(source, entry.source_epoch)),
87
+ );
88
+ revocations[source].removed = before - this._store.entries.length;
89
+ return clone(revocations[source]);
90
+ }
91
+
92
+ write(text, origin, actor, kind = "data", source = "", sourceEpoch = 0, now = undefined) {
93
+ const at = now == null ? Math.floor(Date.now() / 1000) : Math.trunc(Number(now));
94
+ if (!ORIGINS.has(origin)) reject("memory_origin_unknown");
95
+ if (kind !== "data" && kind !== "instruction") reject("memory_kind_unknown");
96
+ if (kind === "instruction" && !INSTRUCTION_ORIGINS.has(origin)) reject("external_origin_cannot_write_instruction");
97
+ if (typeof text !== "string" || !text.trim()) reject("memory_text_required");
98
+ if (!isNonnegativeInteger(sourceEpoch)) reject("memory_source_epoch_invalid");
99
+ if (this._isRevoked(source, sourceEpoch)) reject("memory_source_revoked");
100
+ let verdict = null;
101
+ if (!INSTRUCTION_ORIGINS.has(origin)) {
102
+ const scanned = shield.scanText(text, { source: `memory:${origin}`, now: at });
103
+ verdict = scanned;
104
+ if (scanned.verdict === "block") reject("memory_blocked:" + scanned.reason_codes.join(","));
105
+ }
106
+ const storedSource = source == null ? "" : String(source);
107
+ const entry = {
108
+ schema: MEMORY_SCHEMA,
109
+ id: "mem_" + crypto.createHash("sha256").update(legacySortedJson({
110
+ t: text,
111
+ o: origin,
112
+ at,
113
+ n: this._store.entries.length,
114
+ })).digest("hex").slice(0, 20),
115
+ text,
116
+ origin,
117
+ kind,
118
+ treat_as: kind === "instruction" ? "instruction" : "data",
119
+ actor: actor == null ? "" : String(actor),
120
+ source: storedSource,
121
+ source_epoch: sourceEpoch,
122
+ at,
123
+ shield: verdict
124
+ ? { verdict: verdict.verdict, reason_codes: verdict.reason_codes, receipt: verdict.receipt }
125
+ : null,
126
+ flagged: Boolean(verdict && verdict.verdict === "flag"),
127
+ };
128
+ this._store.entries.push(entry);
129
+ return clone(entry);
130
+ }
131
+
132
+ recall(kinds = ["data", "instruction"], origins = null, includeFlagged = false) {
133
+ const allowed = new Set(kinds);
134
+ const originFilter = origins == null ? null : new Set(origins);
135
+ const out = [];
136
+ for (const entry of this._store.entries) {
137
+ if (!allowed.has(entry.kind)) continue;
138
+ if (this._isRevoked(entry.source, entry.source_epoch)) continue;
139
+ if (originFilter && !originFilter.has(entry.origin)) continue;
140
+ if (entry.flagged && !includeFlagged) continue;
141
+ const item = clone(entry);
142
+ if (!INSTRUCTION_ORIGINS.has(item.origin)) item.treat_as = "data";
143
+ out.push(item);
144
+ }
145
+ return out;
146
+ }
147
+
148
+ instructions() {
149
+ return this.recall(["instruction"]).filter((entry) => INSTRUCTION_ORIGINS.has(entry.origin));
150
+ }
151
+
152
+ purgeOrigin(origin, actor, reason) {
153
+ const before = this._store.entries.length;
154
+ this._store.entries = this._store.entries.filter((entry) => entry.origin !== origin);
155
+ const removed = before - this._store.entries.length;
156
+ if (!Object.prototype.hasOwnProperty.call(this._store, "purges")) this._store.purges = [];
157
+ this._store.purges.push({
158
+ origin,
159
+ actor: String(actor),
160
+ reason: String(reason),
161
+ removed,
162
+ at: Math.floor(Date.now() / 1000),
163
+ });
164
+ return removed;
165
+ }
166
+ }
167
+
168
+ module.exports = {
169
+ MEMORY_SCHEMA,
170
+ REVOCATION_SCHEMA,
171
+ MemoryGuard,
172
+ MemoryRejected,
173
+ };
package/js/shield.d.ts CHANGED
@@ -51,6 +51,10 @@ export {
51
51
  planDigest,
52
52
  recordExecutionOutcome,
53
53
  scanText,
54
+ MEMORY_SCHEMA,
55
+ REVOCATION_SCHEMA,
56
+ MemoryGuard,
57
+ MemoryRejected,
54
58
  toolAuthorityMatches,
55
59
  validateToolCatalog,
56
60
  verifyAuthorizationLease,
package/js/shield.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  const impl = require("./internal/shield.js");
3
+ const memory = require("./memory-guard.js");
3
4
 
4
5
  module.exports = {
5
6
  AuthorizationLeaseError: impl.authorizationLeaseApi.AuthorizationLeaseError,
@@ -16,6 +17,10 @@ module.exports = {
16
17
  createInMemoryExecutionClaimStore: impl.authorizationLeaseApi.createInMemoryExecutionClaimStore,
17
18
  recordExecutionOutcome: impl.authorizationLeaseApi.recordExecutionOutcome,
18
19
  scanText: impl.scanText,
20
+ MEMORY_SCHEMA: memory.MEMORY_SCHEMA,
21
+ REVOCATION_SCHEMA: memory.REVOCATION_SCHEMA,
22
+ MemoryGuard: memory.MemoryGuard,
23
+ MemoryRejected: memory.MemoryRejected,
19
24
  PLANDIFF_SCHEMA: impl.PLANDIFF_SCHEMA,
20
25
  PLANDIFF_AUTHORITIES: impl.AUTHORITIES,
21
26
  PLANDIFF_CONSEQUENTIAL: impl.CONSEQUENTIAL,
package/js/shield.mjs CHANGED
@@ -13,6 +13,10 @@ export const claimExecution = api.claimExecution;
13
13
  export const createInMemoryExecutionClaimStore = api.createInMemoryExecutionClaimStore;
14
14
  export const recordExecutionOutcome = api.recordExecutionOutcome;
15
15
  export const scanText = api.scanText;
16
+ export const MEMORY_SCHEMA = api.MEMORY_SCHEMA;
17
+ export const REVOCATION_SCHEMA = api.REVOCATION_SCHEMA;
18
+ export const MemoryGuard = api.MemoryGuard;
19
+ export const MemoryRejected = api.MemoryRejected;
16
20
  export const PLANDIFF_SCHEMA = api.PLANDIFF_SCHEMA;
17
21
  export const PLANDIFF_AUTHORITIES = api.PLANDIFF_AUTHORITIES;
18
22
  export const PLANDIFF_CONSEQUENTIAL = api.PLANDIFF_CONSEQUENTIAL;
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+
3
+ const core = require("../internal/core.js");
4
+
5
+ const DID_WEB_RE = /^did:web:([A-Za-z0-9.\-]+(?:%3A\d{2,5})?)((?::[A-Za-z0-9._\-%]+)*)$/;
6
+ const NUMERIC_LABEL_RE = /^(?:\d+|0x[0-9a-f]*)$/i;
7
+ const B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
8
+ const ANCHOR_KINDS = new Set(["duns", "lei", "human_id", "passkey"]);
9
+
10
+ class DidResolutionError extends Error {
11
+ constructor(code) {
12
+ super(code);
13
+ this.name = "DidResolutionError";
14
+ this.code = code;
15
+ }
16
+ }
17
+
18
+ function b58decode(text) {
19
+ let num = 0n;
20
+ for (const char of String(text)) {
21
+ const index = B58_ALPHABET.indexOf(char);
22
+ if (index < 0)
23
+ throw new DidResolutionError("did_key_invalid:invalid_multibase_key");
24
+ num = num * 58n + BigInt(index);
25
+ }
26
+ let hex = num.toString(16);
27
+ if (hex.length % 2)
28
+ hex = "0" + hex;
29
+ const body = num === 0n ? Buffer.alloc(0) : Buffer.from(hex, "hex");
30
+ const pad = text.length - text.replace(/^1+/, "").length;
31
+ return Buffer.concat([Buffer.alloc(pad), body]);
32
+ }
33
+
34
+ function b58encode(raw) {
35
+ let num = BigInt("0x" + Buffer.from(raw).toString("hex"));
36
+ let out = "";
37
+ while (num > 0n) {
38
+ const rem = num % 58n;
39
+ num /= 58n;
40
+ out = B58_ALPHABET[Number(rem)] + out;
41
+ }
42
+ let pad = 0;
43
+ for (const byte of raw) {
44
+ if (byte !== 0)
45
+ break;
46
+ pad += 1;
47
+ }
48
+ return "1".repeat(pad) + (out || (raw.length ? "" : ""));
49
+ }
50
+
51
+ function expandIpv6(text) {
52
+ const halves = text.toLowerCase().split("::");
53
+ if (halves.length > 2)
54
+ return null;
55
+ const left = halves[0] ? halves[0].split(":") : [];
56
+ const right = halves.length === 2 ? (halves[1] ? halves[1].split(":") : []) : [];
57
+ if (halves.length === 1 && left.length !== 8)
58
+ return null;
59
+ const missing = 8 - left.length - right.length;
60
+ if (missing < 0)
61
+ return null;
62
+ const parts = [...left, ...Array(missing).fill("0"), ...right];
63
+ if (parts.length !== 8)
64
+ return null;
65
+ const values = [];
66
+ for (const part of parts) {
67
+ if (!/^[0-9a-f]{0,4}$/i.test(part))
68
+ return null;
69
+ values.push(parseInt(part || "0", 16));
70
+ }
71
+ return values;
72
+ }
73
+
74
+ function forbiddenAddress(text) {
75
+ if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(text)) {
76
+ const parts = text.split(".").map((part) => Number(part));
77
+ if (parts.some((part) => part > 255))
78
+ return false;
79
+ const [a, b] = parts;
80
+ if (a === 0 || a === 10 || a === 127 || a >= 224)
81
+ return true;
82
+ if (a === 169 && b === 254)
83
+ return true;
84
+ if (a === 172 && b >= 16 && b <= 31)
85
+ return true;
86
+ if (a === 192 && b === 168)
87
+ return true;
88
+ return false;
89
+ }
90
+ const hextets = expandIpv6(text);
91
+ if (!hextets)
92
+ return false;
93
+ const first = hextets[0];
94
+ if (hextets.every((part) => part === 0))
95
+ return true;
96
+ if (hextets.slice(0, 7).every((part) => part === 0) && hextets[7] === 1)
97
+ return true;
98
+ if ((first & 0xffc0) === 0xfe80)
99
+ return true;
100
+ if ((first & 0xfe00) === 0xfc00)
101
+ return true;
102
+ if ((first & 0xff00) === 0xff00)
103
+ return true;
104
+ return false;
105
+ }
106
+
107
+ function hostnameOf(host) {
108
+ if (host.startsWith("[")) {
109
+ const end = host.indexOf("]");
110
+ if (end < 1)
111
+ throw new DidResolutionError("did_host_invalid");
112
+ return host.slice(1, end);
113
+ }
114
+ const colon = host.lastIndexOf(":");
115
+ if (colon > 0 && host.indexOf(":") === colon && /^\d+$/.test(host.slice(colon + 1)))
116
+ return host.slice(0, colon);
117
+ return host;
118
+ }
119
+
120
+ function refuseNonpublicHost(host) {
121
+ let name = String(host || "");
122
+ name = hostnameOf(name);
123
+ const lowered = name.toLowerCase().replace(/\.+$/, "");
124
+ if (lowered === "localhost" || lowered.endsWith(".localhost"))
125
+ throw new DidResolutionError("did_host_forbidden");
126
+ if (forbiddenAddress(lowered))
127
+ throw new DidResolutionError("did_host_forbidden");
128
+ const labels = lowered.split(".");
129
+ if (NUMERIC_LABEL_RE.test(labels[labels.length - 1] || ""))
130
+ throw new DidResolutionError("did_host_forbidden");
131
+ }
132
+
133
+ function didWebForHost(host) {
134
+ host = String(host || "").trim().toLowerCase().replace(/\/+$/, "");
135
+ if (!host || host.includes("/") || host.includes(" "))
136
+ throw new DidResolutionError("did_host_invalid");
137
+ refuseNonpublicHost(host);
138
+ return "did:web:" + host;
139
+ }
140
+
141
+ function didWebUrl(did) {
142
+ const match = DID_WEB_RE.exec(String(did || "").trim());
143
+ if (!match)
144
+ throw new DidResolutionError("did_not_web");
145
+ const host = decodeURIComponent(match[1]).toLowerCase();
146
+ const path = match[2].split(":").filter(Boolean).map((part) => decodeURIComponent(part));
147
+ if (path.some((part) => part === "" || part === "." || part === ".."))
148
+ throw new DidResolutionError("did_path_invalid");
149
+ refuseNonpublicHost(host);
150
+ if (path.length)
151
+ return "https://" + host + "/" + path.map((part) => encodeURIComponent(part)).join("/") + "/did.json";
152
+ return "https://" + host + "/.well-known/did.json";
153
+ }
154
+
155
+ function rawEd25519(multibase) {
156
+ const text = String(multibase || "");
157
+ if (!text.startsWith("z"))
158
+ throw new DidResolutionError("did_key_invalid:public_key_format_unknown");
159
+ let raw = b58decode(text.slice(1));
160
+ if (raw.length === 34 && raw[0] === 0xed && raw[1] === 0x01)
161
+ raw = raw.subarray(2);
162
+ if (raw.length !== 32)
163
+ throw new DidResolutionError("did_key_invalid:invalid_ed25519_public_key_length");
164
+ return raw;
165
+ }
166
+
167
+ function keyFromMethod(method) {
168
+ const kind = String(method.type || "");
169
+ try {
170
+ if (kind === "Ed25519VerificationKey2020" && method.publicKeyMultibase)
171
+ return core.loadEd25519PublicKey("ed25519:" + core.b64url(rawEd25519(method.publicKeyMultibase)));
172
+ if ((kind === "JsonWebKey2020" || kind === "JsonWebKey") && core.isPlainObject(method.publicKeyJwk)) {
173
+ const jwk = method.publicKeyJwk;
174
+ if (jwk.kty === "OKP" && jwk.crv === "Ed25519" && jwk.x) {
175
+ const raw = core.b64urlDecode(String(jwk.x));
176
+ if (raw.length !== 32)
177
+ throw new DidResolutionError("did_jwk_invalid");
178
+ return core.loadEd25519PublicKey("ed25519:" + core.b64url(raw));
179
+ }
180
+ }
181
+ }
182
+ catch (error) {
183
+ if (error instanceof DidResolutionError)
184
+ throw error;
185
+ throw new DidResolutionError("did_key_invalid:" + (error.code || error.name || "Error"));
186
+ }
187
+ return null;
188
+ }
189
+
190
+ function resolveDidWeb(did, fetch, maxBytes = 64 * 1024) {
191
+ did = String(did || "").trim();
192
+ const url = didWebUrl(did);
193
+ if (typeof fetch !== "function")
194
+ throw new DidResolutionError("did_resolver_not_configured");
195
+ let status;
196
+ let body;
197
+ try {
198
+ [status, body] = fetch("GET", url, { Accept: "application/did+json, application/json" }, null);
199
+ }
200
+ catch (error) {
201
+ throw new DidResolutionError("did_fetch_failed:" + (error && error.name ? error.name : "Error"));
202
+ }
203
+ if (status !== 200)
204
+ throw new DidResolutionError("did_http_" + status);
205
+ const encoded = body == null ? null : Buffer.from(typeof body === "string" ? body : body);
206
+ if (encoded == null || encoded.length > maxBytes)
207
+ throw new DidResolutionError("did_document_too_large");
208
+ let doc;
209
+ try {
210
+ doc = JSON.parse(encoded.toString("utf8"));
211
+ }
212
+ catch {
213
+ throw new DidResolutionError("did_document_invalid_json");
214
+ }
215
+ if (!core.isPlainObject(doc) || String(doc.id || "") !== did)
216
+ throw new DidResolutionError("did_document_id_mismatch");
217
+ const methods = doc.verificationMethod;
218
+ if (!Array.isArray(methods) || methods.length === 0)
219
+ throw new DidResolutionError("did_no_verification_method");
220
+ for (const method of methods) {
221
+ if (!core.isPlainObject(method))
222
+ continue;
223
+ const controller = String(method.controller || did);
224
+ if (controller !== did)
225
+ continue;
226
+ const key = keyFromMethod(method);
227
+ if (key) {
228
+ return {
229
+ did,
230
+ url,
231
+ method_id: String(method.id || ""),
232
+ key_id: core.keyId(key),
233
+ public_key: core.exportEd25519PublicKey(key),
234
+ };
235
+ }
236
+ }
237
+ throw new DidResolutionError("did_no_ed25519_key");
238
+ }
239
+
240
+ function multibaseEd25519(raw, multicodec = true) {
241
+ const bytes = Buffer.from(raw);
242
+ if (bytes.length !== 32)
243
+ throw new DidResolutionError("invalid_ed25519_public_key_length");
244
+ const prefixed = multicodec ? Buffer.concat([Buffer.from([0xed, 0x01]), bytes]) : bytes;
245
+ return "z" + b58encode(prefixed);
246
+ }
247
+
248
+ const AuthorityAnchor = Object.freeze({
249
+ SUPPORTED_KINDS: Object.freeze(["duns", "lei", "human_id", "passkey"]),
250
+ isValidKind(kind) {
251
+ return ANCHOR_KINDS.has(String(kind || "").trim().toLowerCase());
252
+ },
253
+ });
254
+
255
+ module.exports = {
256
+ DidResolutionError,
257
+ didWebForHost,
258
+ didWebUrl,
259
+ resolveDidWeb,
260
+ multibaseEd25519,
261
+ AuthorityAnchor,
262
+ };
package/js/trust.d.ts CHANGED
@@ -9,6 +9,12 @@ export {
9
9
  ExternalSignerErrorCode,
10
10
  ExternalSignerError,
11
11
  createKyaChallengeWithSigner,
12
+ AuthorityAnchor,
13
+ DidResolutionError,
14
+ didWebForHost,
15
+ didWebUrl,
16
+ resolveDidWeb,
17
+ multibaseEd25519,
12
18
  signDelegationWithSigner,
13
19
  signEnvelopeWithSigner,
14
20
  ChallengeStore,
package/js/trust.js CHANGED
@@ -3,6 +3,7 @@ const core = require("./internal/core.js");
3
3
  const impl = require("./internal/trust.js");
4
4
  const comm = require("./internal/comm.js");
5
5
  const registration = require("./trust/passkey-registration.js");
6
+ const didweb = require("./trust/didweb.js");
6
7
  const externalSigner = require("./trust/external-signer.js")({
7
8
  canonicalJsonBuffer: core.canonicalJsonBuffer,
8
9
  loadEd25519PublicKey: core.loadEd25519PublicKey,
@@ -23,6 +24,12 @@ module.exports = {
23
24
  ...externalSigner,
24
25
  keyId: core.keyId,
25
26
  createKyaChallenge: impl.createKyaChallenge,
27
+ AuthorityAnchor: didweb.AuthorityAnchor,
28
+ DidResolutionError: didweb.DidResolutionError,
29
+ didWebForHost: didweb.didWebForHost,
30
+ didWebUrl: didweb.didWebUrl,
31
+ resolveDidWeb: didweb.resolveDidWeb,
32
+ multibaseEd25519: didweb.multibaseEd25519,
26
33
  verifyKyaChallenge: impl.verifyKyaChallenge,
27
34
  completeKyaChallenge: impl.completeKyaChallenge,
28
35
  verifyKyaBinding: impl.verifyKyaBinding,
package/js/trust.mjs CHANGED
@@ -7,6 +7,12 @@ export const signDelegationWithSigner = api.signDelegationWithSigner;
7
7
  export const signEnvelopeWithSigner = api.signEnvelopeWithSigner;
8
8
  export const keyId = api.keyId;
9
9
  export const createKyaChallenge = api.createKyaChallenge;
10
+ export const AuthorityAnchor = api.AuthorityAnchor;
11
+ export const DidResolutionError = api.DidResolutionError;
12
+ export const didWebForHost = api.didWebForHost;
13
+ export const didWebUrl = api.didWebUrl;
14
+ export const resolveDidWeb = api.resolveDidWeb;
15
+ export const multibaseEd25519 = api.multibaseEd25519;
10
16
  export const verifyKyaChallenge = api.verifyKyaChallenge;
11
17
  export const completeKyaChallenge = api.completeKyaChallenge;
12
18
  export const verifyKyaBinding = api.verifyKyaBinding;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twin3-sdk",
3
- "version": "0.2.14-pre-alpha",
3
+ "version": "0.2.15-pre-alpha",
4
4
  "description": "Universal Capability Motherboard for Autonomous AI Agents and Self-Sovereign Humans (JS/Node runtime)",
5
5
  "type": "commonjs",
6
6
  "main": "./js/index.js",
@@ -104,6 +104,7 @@
104
104
  "js/matrix.d.ts",
105
105
  "js/matrix.js",
106
106
  "js/matrix.mjs",
107
+ "js/memory-guard.js",
107
108
  "js/settlement.d.ts",
108
109
  "js/settlement.js",
109
110
  "js/settlement.mjs",
@@ -112,7 +113,8 @@
112
113
  "js/shield.mjs",
113
114
  "js/trust.d.ts",
114
115
  "js/trust.js",
115
- "js/trust.mjs"
116
+ "js/trust.mjs",
117
+ "js/trust/didweb.js"
116
118
  ],
117
119
  "engines": {
118
120
  "node": ">=18"