@lapxo/bound 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +40 -121
  2. package/TARGET.bound +65 -32
  3. package/dist/cli/owner.js +2 -1
  4. package/dist/cli/place.js +7 -26
  5. package/dist/cli/retained-judgment.js +25 -0
  6. package/dist/cli/standing-result.js +4 -3
  7. package/dist/cli/verb.js +36 -3
  8. package/dist/engine/encoding.js +2 -0
  9. package/dist/engine/index.d.ts +2 -0
  10. package/dist/engine/index.js +2 -0
  11. package/dist/engine/ports.d.ts +32 -0
  12. package/dist/engine/ports.js +103 -0
  13. package/dist/fold/admission-history.js +6 -11
  14. package/dist/fold/bootstrap-anchor.js +28 -0
  15. package/dist/fold/ceilings.js +13 -0
  16. package/dist/fold/own.js +7 -5
  17. package/dist/fold/signers.js +5 -9
  18. package/dist/host/admitted-place.js +6 -0
  19. package/dist/host/contextual-place.js +2 -2
  20. package/dist/host/io.js +2 -2
  21. package/dist/host/native/module-syntax.js +8 -4
  22. package/dist/host/reference-authenticated.d.ts +12 -0
  23. package/dist/host/reference-authenticated.js +47 -31
  24. package/dist/host/reference-contracts.js +6 -0
  25. package/dist/host/reference-export.d.ts +33 -0
  26. package/dist/host/reference-export.js +81 -0
  27. package/dist/host/reference-native-api.d.ts +3 -0
  28. package/dist/host/reference-native-api.js +3 -0
  29. package/dist/host/reference-native.d.ts +21 -1
  30. package/dist/host/reference-native.js +34 -10
  31. package/dist/host/reference-profile.js +2 -1
  32. package/dist/host/reference-standing.js +18 -0
  33. package/dist/host/refusals.d.ts +14 -0
  34. package/dist/host/refusals.js +43 -0
  35. package/dist/host/signing.js +7 -1
  36. package/dist/observe/files.js +11 -1
  37. package/dist/wasm/object.js +2 -2
  38. package/docs/contracts/authenticated-reference.publication.json +2 -2
  39. package/docs/contracts/host-ports.md +23 -0
  40. package/docs/contracts/host-ports.publication.json +25 -0
  41. package/docs/contracts/native-publication.inventory.json +7 -1
  42. package/docs/contracts/optional-wasm-adapter.publication.json +1 -1
  43. package/docs/contracts/readme.publication.json +25 -0
  44. package/docs/contracts/reference-backend.md +24 -0
  45. package/docs/contracts/reference-backend.publication.json +25 -0
  46. package/docs/contracts/reference-export.md +31 -0
  47. package/docs/contracts/reference-export.publication.json +25 -0
  48. package/docs/contracts/reference-port.md +8 -1
  49. package/docs/contracts/reference-port.publication.json +1 -1
  50. package/docs/contracts/refusals.bound +94 -0
  51. package/docs/contracts/refusals.md +47 -0
  52. package/docs/contracts/refusals.publication.json +25 -0
  53. package/docs/contracts/standing.bound +9 -0
  54. package/docs/contracts/standing.md +24 -0
  55. package/docs/contracts/standing.publication.json +25 -0
  56. package/package.json +52 -6
  57. package/receipts.bound +9703 -7690
  58. package/samples/authenticated-reference/compatibility.json +306 -0
  59. package/samples/authenticated-reference/yes.json +2 -2
  60. package/samples/engine-place/inputs/closure.json +1 -0
  61. package/samples/host-ports/no.json +16 -0
  62. package/samples/host-ports/yes.json +9 -0
  63. package/samples/readme/no.json +1 -0
  64. package/samples/readme/yes.json +1 -0
  65. package/samples/reference-backend/no.json +1 -0
  66. package/samples/reference-backend/world-fixture.json +8 -0
  67. package/samples/reference-backend/yes.json +1 -0
  68. package/samples/reference-export/no.json +5 -0
  69. package/samples/reference-export/yes.json +15 -0
  70. package/samples/reference-port/no/catalog/TARGET.bound +33 -0
  71. package/samples/reference-port/no/route-notes/TARGET.bound +33 -0
  72. package/samples/refusals/no.json +4 -0
  73. package/samples/refusals/yes.json +9 -0
  74. package/samples/standing/no.json +4 -0
  75. package/samples/standing/yes.json +21 -0
  76. package/vectors/native/authenticated-reference-formation.vector.json +55 -1
  77. package/vectors/native/authenticated-reference.vector.json +32 -396
  78. package/vectors/native/host-ports.vector.json +107 -0
  79. package/vectors/native/readme.vector.json +76 -0
  80. package/vectors/native/reference-backend.vector.json +84 -0
  81. package/vectors/native/reference-export.vector.json +78 -0
  82. package/vectors/native/refusals.vector.json +95 -0
  83. package/vectors/native/standing.vector.json +116 -0
  84. package/vectors/wasm-object.cli.json +16 -61
  85. package/dist/cli/accept.d.ts +0 -28
  86. package/dist/cli/act-result.d.ts +0 -12
  87. package/dist/cli/args.d.ts +0 -11
  88. package/dist/cli/custodian.d.ts +0 -1
  89. package/dist/cli/host.d.ts +0 -1
  90. package/dist/cli/names.d.ts +0 -25
  91. package/dist/cli/native-descendant.d.ts +0 -1
  92. package/dist/cli/native.d.ts +0 -1
  93. package/dist/cli/observe.d.ts +0 -1
  94. package/dist/cli/owner.d.ts +0 -37
  95. package/dist/cli/pass.d.ts +0 -6
  96. package/dist/cli/place.d.ts +0 -76
  97. package/dist/cli/release-result.d.ts +0 -5
  98. package/dist/cli/standing-result.d.ts +0 -5
  99. package/dist/cli/takes.d.ts +0 -30
  100. package/dist/fold/admission-history.d.ts +0 -6
  101. package/dist/fold/ceilings.d.ts +0 -24
  102. package/dist/fold/claims.d.ts +0 -29
  103. package/dist/fold/closed.d.ts +0 -15
  104. package/dist/fold/configures.d.ts +0 -4
  105. package/dist/fold/digests.d.ts +0 -29
  106. package/dist/fold/evidence.d.ts +0 -32
  107. package/dist/fold/fold-worker.d.ts +0 -1
  108. package/dist/fold/kept.d.ts +0 -29
  109. package/dist/fold/keys.d.ts +0 -17
  110. package/dist/fold/laid.d.ts +0 -17
  111. package/dist/fold/nos.d.ts +0 -8
  112. package/dist/fold/object.d.ts +0 -3
  113. package/dist/fold/observed.d.ts +0 -35
  114. package/dist/fold/opened.d.ts +0 -5
  115. package/dist/fold/own.d.ts +0 -24
  116. package/dist/fold/paid.d.ts +0 -11
  117. package/dist/fold/place-inputs.d.ts +0 -17
  118. package/dist/fold/places.d.ts +0 -24
  119. package/dist/fold/rank.d.ts +0 -3
  120. package/dist/fold/reach.d.ts +0 -18
  121. package/dist/fold/readers.d.ts +0 -47
  122. package/dist/fold/receipts.d.ts +0 -8
  123. package/dist/fold/region-declarations.d.ts +0 -2
  124. package/dist/fold/region.d.ts +0 -72
  125. package/dist/fold/release-policy.d.ts +0 -67
  126. package/dist/fold/resolved.d.ts +0 -18
  127. package/dist/fold/roles.d.ts +0 -17
  128. package/dist/fold/second.d.ts +0 -18
  129. package/dist/fold/shards.d.ts +0 -29
  130. package/dist/fold/signed.d.ts +0 -23
  131. package/dist/fold/signers.d.ts +0 -24
  132. package/dist/fold/stranger.d.ts +0 -3
  133. package/dist/fold/views.d.ts +0 -27
  134. package/dist/fold/wire.d.ts +0 -19
  135. package/dist/fold/zoom.d.ts +0 -16
  136. package/dist/host/act-cost.d.ts +0 -13
  137. package/dist/host/adapters/keys/ed25519.d.ts +0 -7
  138. package/dist/host/adapters/modules/javascript.d.ts +0 -3
  139. package/dist/host/adapters/transport/http.d.ts +0 -5
  140. package/dist/host/admitted-place.d.ts +0 -5
  141. package/dist/host/archive.d.ts +0 -11
  142. package/dist/host/capsule-process.d.ts +0 -21
  143. package/dist/host/capsule.d.ts +0 -19
  144. package/dist/host/cell-receipts.d.ts +0 -2
  145. package/dist/host/content-needed.d.ts +0 -5
  146. package/dist/host/content-store.d.ts +0 -8
  147. package/dist/host/contextual-place.d.ts +0 -59
  148. package/dist/host/digest.d.ts +0 -3
  149. package/dist/host/hash.d.ts +0 -9
  150. package/dist/host/io.d.ts +0 -11
  151. package/dist/host/lifetime.d.ts +0 -1
  152. package/dist/host/located.d.ts +0 -4
  153. package/dist/host/native/bounded-file.d.ts +0 -8
  154. package/dist/host/native/command.d.ts +0 -36
  155. package/dist/host/native/control-bridge-worker.d.ts +0 -1
  156. package/dist/host/native/control-bridge.d.ts +0 -30
  157. package/dist/host/native/control-selection.d.ts +0 -29
  158. package/dist/host/native/custody.d.ts +0 -5
  159. package/dist/host/native/deadline.d.ts +0 -12
  160. package/dist/host/native/descendants.d.ts +0 -1
  161. package/dist/host/native/generated-custodian.d.ts +0 -122
  162. package/dist/host/native/generated.d.ts +0 -217
  163. package/dist/host/native/image.d.ts +0 -36
  164. package/dist/host/native/jobs.d.ts +0 -75
  165. package/dist/host/native/loader.d.ts +0 -80
  166. package/dist/host/native/materialize.d.ts +0 -22
  167. package/dist/host/native/module-closure.d.ts +0 -27
  168. package/dist/host/native/module-syntax.d.ts +0 -8
  169. package/dist/host/native/native-binary.d.ts +0 -54
  170. package/dist/host/native/native-program.d.ts +0 -56
  171. package/dist/host/native/node-jobs.d.ts +0 -84
  172. package/dist/host/native/node-launch.d.ts +0 -72
  173. package/dist/host/native/preload.d.ts +0 -1
  174. package/dist/host/native/reading.d.ts +0 -91
  175. package/dist/host/native/receipt-environment.d.ts +0 -3
  176. package/dist/host/native/references.d.ts +0 -5
  177. package/dist/host/native/root.d.ts +0 -17
  178. package/dist/host/native/route.d.ts +0 -66
  179. package/dist/host/native/runtime.d.ts +0 -4
  180. package/dist/host/native-reader-context.d.ts +0 -3
  181. package/dist/host/objects.d.ts +0 -6
  182. package/dist/host/place-clock-selection.d.ts +0 -55
  183. package/dist/host/ports/ingress.d.ts +0 -24
  184. package/dist/host/ports/local-act.d.ts +0 -44
  185. package/dist/host/ports/place-act.d.ts +0 -46
  186. package/dist/host/ports/publication.d.ts +0 -10
  187. package/dist/host/ports/signing.d.ts +0 -31
  188. package/dist/host/ports/store.d.ts +0 -2
  189. package/dist/host/ports/transport.d.ts +0 -1
  190. package/dist/host/process-lifetime.d.ts +0 -11
  191. package/dist/host/provide.d.ts +0 -48
  192. package/dist/host/read-only.d.ts +0 -3
  193. package/dist/host/reading-closures.d.ts +0 -47
  194. package/dist/host/reading-context.d.ts +0 -29
  195. package/dist/host/reference.d.ts +0 -6
  196. package/dist/host/release.d.ts +0 -14
  197. package/dist/host/root-clock-source.d.ts +0 -85
  198. package/dist/host/selected-capsules.d.ts +0 -8
  199. package/dist/host/selected-topos.d.ts +0 -24
  200. package/dist/host/signing.d.ts +0 -3
  201. package/dist/host/source-content.d.ts +0 -12
  202. package/dist/host/timing.d.ts +0 -2
  203. package/dist/host/walk.d.ts +0 -6
  204. package/dist/judge/late.d.ts +0 -7
  205. package/dist/judge/take.d.ts +0 -59
  206. package/dist/judge/widening.d.ts +0 -1
  207. package/dist/land/act.d.ts +0 -30
  208. package/dist/land/alias.d.ts +0 -10
  209. package/dist/land/held.d.ts +0 -7
  210. package/dist/land/inert.d.ts +0 -26
  211. package/dist/land/leaving.d.ts +0 -14
  212. package/dist/land/ledger.d.ts +0 -33
  213. package/dist/land/owned.d.ts +0 -23
  214. package/dist/land/sign.d.ts +0 -15
  215. package/dist/land/standing-pin.d.ts +0 -3
  216. package/dist/land/vouched.d.ts +0 -21
  217. package/dist/land/withdraw.d.ts +0 -14
  218. package/dist/observe/files.d.ts +0 -14
  219. package/dist/observe/identity.d.ts +0 -21
  220. package/dist/observe/match.d.ts +0 -1
  221. package/dist/observe/regions.d.ts +0 -33
  222. package/dist/observe/run.d.ts +0 -31
  223. package/dist/observe/runner.d.ts +0 -20
  224. package/dist/render/capsule.d.ts +0 -24
  225. package/dist/render/field.d.ts +0 -3
  226. package/dist/render/names.d.ts +0 -3
  227. package/dist/render/open.d.ts +0 -27
  228. package/dist/render/page.d.ts +0 -3
  229. package/dist/render/receipts.d.ts +0 -3
  230. package/dist/render/shape.d.ts +0 -16
  231. package/dist/render/target.d.ts +0 -18
  232. package/dist/render/view.d.ts +0 -22
  233. package/dist/wasm/node-worker.d.ts +0 -1
  234. package/dist/wasm/object.d.ts +0 -8
  235. package/dist/wasm/worker.d.ts +0 -1
@@ -1,42 +1,61 @@
1
+ import { BoundRefusal } from "./refusals.js";
1
2
  import { parse, canonical, authorityOf, authorityAtEpoch, signedBytes, objectHistory, validateObjectContext } from '@lapxo/topos/wire';
2
- import { cellInputs } from '@lapxo/topos/cell-inputs';
3
- import { semanticStandingBytes } from '@lapxo/topos/standing';
4
3
  import { decodeReference } from "./reference-profile.js";
5
- function fail(why) { throw Error('REFUSE·authenticated-reference ' + why); }
4
+ import { referenceStanding, referenceContracts, referenceContextBytes } from "./reference-standing.js";
5
+ function fail(code, why) { throw new BoundRefusal('authenticated-reference/' + code, 'REFUSE·authenticated-reference ' + why); }
6
6
  const utf8 = (s) => new TextEncoder().encode(s);
7
7
  /** Prospective authenticated-standing@1 host composition; no effects and no authority from transport. */
8
8
  export async function inspectAuthenticatedReference(fragment, trust) {
9
9
  if (new Set(trust.signers.map(s => s.id)).size !== trust.signers.length)
10
- fail('duplicate host signer');
10
+ fail('context', 'duplicate host signer');
11
11
  const reference = decodeReference(fragment);
12
12
  if (!Number.isSafeInteger(trust.maxBytes) || trust.maxBytes < 1 || !Number.isSafeInteger(trust.maxRecords) || trust.maxRecords < 1)
13
- fail('resource budget');
14
- const received = await trust.fetchBytes(reference.source, reference.blob);
13
+ fail('budget', 'resource budget');
14
+ if (trust.contracts !== undefined) {
15
+ if (!referenceContracts(trust.contracts))
16
+ fail('context', 'contract identities');
17
+ const snapshot = await trust.digestBytes(referenceContextBytes({ ...trust, semanticIdentity: 'sha256:' + reference.digest, contracts: trust.contracts }));
18
+ if (snapshot !== trust.snapshot)
19
+ fail('context', 'context snapshot');
20
+ }
21
+ let received;
22
+ try {
23
+ received = await trust.fetchBytes(reference.source, reference.blob);
24
+ }
25
+ catch {
26
+ return fail('transport', 'content port failed');
27
+ }
15
28
  if (received.byteLength > trust.maxBytes)
16
- fail('bytes budget');
29
+ fail('budget', 'bytes budget');
17
30
  if (typeof SharedArrayBuffer !== 'undefined' && received.buffer instanceof SharedArrayBuffer)
18
- fail('shared transport bytes');
31
+ fail('transport', 'shared transport bytes');
19
32
  const bytes = new Uint8Array(received);
20
33
  if (await trust.digestBytes(bytes) !== reference.blob)
21
- fail('blob integrity');
22
- const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
34
+ fail('integrity', 'blob integrity');
35
+ let text;
36
+ try {
37
+ text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
38
+ }
39
+ catch {
40
+ return fail('framing', 'carrier UTF-8');
41
+ }
23
42
  let packet;
24
43
  try {
25
44
  packet = JSON.parse(text);
26
45
  }
27
46
  catch {
28
- fail('carrier framing');
47
+ fail('framing', 'carrier framing');
29
48
  }
30
49
  if (!Array.isArray(packet) || packet.length !== 3 || packet[0] !== 'authenticated-standing@1' || !Array.isArray(packet[1]) || packet[1].length < 1 || packet[1].length > trust.maxRecords || packet[1].some((x) => typeof x !== 'string') || typeof packet[2] !== 'string' || JSON.stringify(packet) + '\n' !== text)
31
- fail('carrier framing');
50
+ fail('framing', 'carrier framing');
32
51
  const originals = packet[1], proofLine = packet[2];
33
52
  const parsed = [...originals, proofLine].map(line => { const p = parse(line); if (p.kind !== 'fact' || canonical(p.value.fields) !== line)
34
- fail('original canonical'); return p.value.fields; });
53
+ fail('wire', 'original canonical'); return p.value.fields; });
35
54
  const proof = parsed[parsed.length - 1];
36
55
  if (proof.scope !== trust.proofScope || proof.at !== 'witness:' + trust.snapshot || proof.role !== 'writes' || proof.form !== 'alphabet' || proof.measure !== 'digest' || proof.epoch !== String(trust.epoch))
37
- fail('untrusted snapshot binding');
56
+ fail('context', 'untrusted snapshot binding');
38
57
  if (proof.value !== await trust.digestBytes(utf8(JSON.stringify(['authenticated-standing@1', originals]) + '\n')))
39
- fail('snapshot commitment');
58
+ fail('integrity', 'snapshot commitment');
40
59
  // Async crypto port is called on exact native signed bytes; authorityOf consumes only those results.
41
60
  const verifiedCrypto = new Set();
42
61
  const key = (f, pub) => JSON.stringify([signedBytes(f), pub, f.sig, trust.algorithms]);
@@ -47,38 +66,35 @@ export async function inspectAuthenticatedReference(fragment, trust) {
47
66
  const verifies = (f, pub) => verifiedCrypto.has(key(f, pub));
48
67
  const authentic = authorityOf(proof, trust.signers, verifies);
49
68
  if (authentic.kind !== 'admitted')
50
- fail('proof ' + authentic.kind + ' ' + authentic.why);
69
+ fail('authority', 'proof ' + authentic.kind + ' ' + authentic.why);
51
70
  const signer = trust.signers.find(s => s.id === proof.by);
52
71
  if (!signer)
53
- fail('proof signer');
72
+ fail('authority', 'proof signer');
54
73
  const current = authorityAtEpoch(proof, signer, trust.epoch, trust.proofScope, 'authority');
55
74
  if (current.kind !== 'admitted')
56
- fail('current proof ' + current.kind + ' ' + current.why);
75
+ fail('authority', 'current proof ' + current.kind + ' ' + current.why);
57
76
  const verified = new Set();
58
77
  for (let i = 0; i < originals.length; i++) {
59
78
  const admitted = authorityOf(parsed[i], trust.signers, verifies);
60
79
  if (admitted.kind !== 'admitted')
61
- fail('original ' + admitted.kind + ' ' + admitted.why);
80
+ fail('authority', 'original ' + admitted.kind + ' ' + admitted.why);
62
81
  verified.add(originals[i]);
63
82
  }
64
83
  const history = objectHistory(originals);
65
84
  if (history.kind !== 'fact')
66
- fail('wire ' + history.why);
85
+ fail('wire', 'wire ' + history.why);
67
86
  if (history.value.configuration.some(f => f.value === 'withdraw'))
68
- fail('configuration history requires admitted standing fold');
87
+ fail('history', 'configuration history requires admitted standing fold');
69
88
  const context = { ...trust.objects, admit: (r) => verified.has(canonical(r.fields)) && trust.objects.admit(r) };
70
89
  const checked = validateObjectContext(history.value.objects, context);
71
90
  if (checked.kind !== 'fact')
72
- throw Error('REFUSE·' + checked.why);
73
- const membership = new Set([...cellInputs(checked.value, 'semantic-live@2').values()].flat().map(l => semanticStandingBytes([l])));
74
- const fields = (line) => { const p = parse(line); if (p.kind !== 'fact')
75
- fail('original parse'); return p.value.fields; };
76
- const effective = [...originals].sort((a, b) => Number(fields(a).epoch) - Number(fields(b).epoch));
77
- const live = effective.filter(line => { const f = fields(line); return f.type === undefined || membership.has(semanticStandingBytes([line])); });
78
- const semantic = semanticStandingBytes(live), semanticIdentity = await trust.digestBytes(utf8(semantic));
91
+ throw new BoundRefusal('authenticated-reference/object-context', 'REFUSE·' + checked.why);
92
+ const semantic = referenceStanding(originals, checked.value), semanticIdentity = await trust.digestBytes(utf8(semantic));
79
93
  if (semanticIdentity !== 'sha256:' + reference.digest)
80
- fail('standing identity');
81
- const authorityContextIdentity = await trust.digestBytes(utf8(JSON.stringify(['reference-inspection-trust@1', trust.snapshot, trust.epoch, trust.proofScope, trust.policy, trust.formImplementation, trust.signers, trust.algorithms])));
82
- return { reference, records: checked.value, semanticIdentity, authorityContextIdentity, semanticStanding: semantic, originalRecords: originals };
94
+ fail('identity', 'standing identity');
95
+ const authorityContextIdentity = await trust.digestBytes(utf8(JSON.stringify(trust.contracts ? ['reference-inspection-trust@2', trust.snapshot, trust.epoch, trust.proofScope, trust.policy, trust.formImplementation, trust.signers, trust.algorithms, trust.contracts] : ['reference-inspection-trust@1', trust.snapshot, trust.epoch, trust.proofScope, trust.policy, trust.formImplementation, trust.signers, trust.algorithms])));
96
+ return { reference, records: checked.value, semanticIdentity, authorityContextIdentity, semanticStanding: semantic, originalRecords: originals, ...(trust.contracts ? { contracts: { ...trust.contracts } } : {}) };
83
97
  }
84
98
  export { encodeReference, decodeReference } from "./reference-profile.js";
99
+ export { BoundRefusal, readRefusalCatalog, describeRefusal } from "./refusals.js";
100
+ export { readContent, retainContent, invokeSigningPort } from "../engine/ports.js";
@@ -0,0 +1,6 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { sha256 } from "./hash.js";
3
+ /** Exact shipped interpretation contracts; this helper grants no authority. */
4
+ export function installedReferenceContracts() {
5
+ return { refusals: sha256(readFileSync(new URL('../../docs/contracts/refusals.bound', import.meta.url))), standing: sha256(readFileSync(new URL('../../docs/contracts/standing.bound', import.meta.url))) };
6
+ }
@@ -0,0 +1,33 @@
1
+ import type { SigningPort } from '../engine/ports.ts';
2
+ export { BoundRefusal as ReferenceExportRefusal } from './refusals.ts';
3
+ /** Signs a public snapshot proof; never lands, refreshes evidence or changes the Root. */
4
+ export declare function exportAuthenticatedReference(root: string, source: string, options?: {
5
+ readonly key?: string;
6
+ readonly keyFile?: string;
7
+ readonly signer?: string;
8
+ readonly hostSigner?: {
9
+ readonly name: string;
10
+ readonly sign: SigningPort;
11
+ };
12
+ }): Promise<{
13
+ profile: string;
14
+ reference: string;
15
+ carrier: string;
16
+ context: {
17
+ snapshot: string;
18
+ profile: string;
19
+ semanticIdentity: string;
20
+ epoch: number;
21
+ proofScope: string;
22
+ policy: string;
23
+ formImplementation: string;
24
+ signers: readonly import("@lapxo/topos/wire").Signer[];
25
+ algorithms: readonly string[];
26
+ contracts: {
27
+ refusals: string;
28
+ standing: string;
29
+ };
30
+ maxBytes: number;
31
+ maxRecords: number;
32
+ };
33
+ }>;
@@ -0,0 +1,81 @@
1
+ import { installedReferenceContracts } from "./reference-contracts.js";
2
+ import { BoundRefusal, describeRefusal, readRefusalCatalog } from "./refusals.js";
3
+ import { readFileSync } from 'node:fs';
4
+ import { authorityAtEpoch, canonical, parse } from '@lapxo/topos/wire';
5
+ import { sha256 as hash } from "./hash.js";
6
+ import { signerFor } from "./signing.js";
7
+ import { signConsentLot } from "../land/sign.js";
8
+ import { underRootAuthority } from "../land/act.js";
9
+ import { storeOf } from "../land/ledger.js";
10
+ import { authorityFor, rootSigner } from "../fold/signers.js";
11
+ import { admissionAnchor, admissionHistoryAt } from "../fold/admission-history.js";
12
+ import { authenticatedRootStanding } from "./admitted-place.js";
13
+ import { eraOf } from "../fold/digests.js";
14
+ import { epochOf } from "../cli/owner.js";
15
+ import { fieldOf } from "../fold/claims.js";
16
+ import { prepareNativeReference } from "./reference-native.js";
17
+ import { referenceContextBytes, referenceContracts, referenceStanding } from "./reference-standing.js";
18
+ import { encodeReference } from "./reference-profile.js";
19
+ const catalog = () => readFileSync(new URL('../../docs/contracts/refusals.bound', import.meta.url), 'utf8');
20
+ export { BoundRefusal as ReferenceExportRefusal } from "./refusals.js";
21
+ function fail(code) {
22
+ const refusal = new BoundRefusal('reference-export/' + code, 'REFUSE·reference-export/' + code);
23
+ const entry = describeRefusal(refusal, readRefusalCatalog(catalog()));
24
+ throw new BoundRefusal(entry.code, 'REFUSE·' + entry.code + ' ' + entry.meaning);
25
+ }
26
+ /** Signs a public snapshot proof; never lands, refreshes evidence or changes the Root. */
27
+ export async function exportAuthenticatedReference(root, source, options = {}) {
28
+ try {
29
+ encodeReference({ class: 'standing', algorithm: 'sha256', digest: '0'.repeat(64), blob: 'sha256:' + '0'.repeat(64), source });
30
+ }
31
+ catch {
32
+ fail('source');
33
+ }
34
+ return underRootAuthority(storeOf(root), 'reference export', async () => {
35
+ const history = admissionHistoryAt(root, Number.MAX_SAFE_INTEGER), standing = authenticatedRootStanding(root), epoch = epochOf(history), era = eraOf(standing);
36
+ const authority = authorityFor(history, admissionAnchor(root), era.admitted);
37
+ const one = (scope, measure = 'id', form = 'alphabet') => { const rows = standing.filter(line => fieldOf(line, 'scope') === scope && fieldOf(line, 'value') !== 'withdraw'); if (rows.length !== 1 || fieldOf(rows[0], 'role') !== 'writes' || fieldOf(rows[0], 'measure') !== measure || fieldOf(rows[0], 'form') !== form || authority.of(rows[0]).kind !== 'admitted')
38
+ fail('context'); return fieldOf(rows[0], 'value'); };
39
+ if (one('reference/authentication') !== 'authenticated-standing@1')
40
+ fail('context');
41
+ const proofScope = one('reference/proof-scope'), contracts = { refusals: one('reference/contracts/refusals'), standing: one('reference/contracts/standing') };
42
+ const installed = installedReferenceContracts();
43
+ if (!referenceContracts(contracts) || contracts.refusals !== installed.refusals || contracts.standing !== installed.standing)
44
+ fail('context');
45
+ const count = one('reference/records'), maxRecords = Number(count), range = /^(0|[1-9][0-9]*)\.\.(0|[1-9][0-9]*)$/.exec(one('reference/export-bytes', 'bytes', 'interval'));
46
+ if (!/^(0|[1-9][0-9]*)$/.test(count) || !Number.isSafeInteger(maxRecords) || maxRecords < 1 || !range || Number(range[1]) > Number(range[2]) || !Number.isSafeInteger(Number(range[2])) || Number(range[2]) < 1)
47
+ fail('budget');
48
+ const maxBytes = Number(range[2]);
49
+ const originals = standing.filter(line => fieldOf(line, 'value') !== 'withdraw' || fieldOf(line, 'type') !== '');
50
+ if (!originals.length || originals.length > maxRecords || Buffer.byteLength(originals.join('\n')) > maxBytes)
51
+ fail('budget');
52
+ if (originals.some(line => authority.of(line).kind !== 'admitted'))
53
+ fail('history');
54
+ const key = options.key ?? rootSigner(root).id, signer = authority.admitted.find(s => s.id === key);
55
+ const stamp = canonical({ scope: proofScope, role: 'writes', form: 'alphabet', measure: 'digest', value: hash(JSON.stringify(['authenticated-standing@1', originals]) + '\n'), by: 'target', at: 'witness:' + 'sha256:' + '0'.repeat(64) });
56
+ const probe = parse(stamp);
57
+ if (!signer || probe.kind !== 'fact' || authorityAtEpoch({ ...probe.value.fields, by: key, epoch: String(epoch) }, signer, epoch, proofScope, 'authority').kind !== 'admitted')
58
+ fail('authority');
59
+ const held = await prepareNativeReference(root), semantic = referenceStanding(originals, held.records), semanticIdentity = hash(semantic);
60
+ const context = { profile: 'reference-export-context@1', semanticIdentity, epoch, proofScope, policy: held.policy, formImplementation: held.formImplementation, signers: authority.admitted, algorithms: era.admitted, contracts, maxBytes, maxRecords };
61
+ const snapshot = hash(referenceContextBytes(context));
62
+ if (Buffer.byteLength(JSON.stringify({ originals, context, snapshot }) + '\n') > maxBytes)
63
+ fail('budget');
64
+ const drafted = canonical({ ...probe.value.fields, at: 'witness:' + snapshot });
65
+ let signed;
66
+ try {
67
+ signed = await signConsentLot([drafted], { epoch, by: key, algorithm: era.era }, signer.publicKey, era.admitted, signerFor(standing, key, options.signer, options.keyFile, options.hostSigner));
68
+ }
69
+ catch {
70
+ fail('signer');
71
+ }
72
+ if (signed.kind !== 'fact')
73
+ fail('signer');
74
+ const carrier = JSON.stringify(['authenticated-standing@1', originals, signed.lines[0]]) + '\n';
75
+ const reference = encodeReference({ class: 'standing', algorithm: 'sha256', digest: semanticIdentity.slice(7), blob: hash(carrier), source });
76
+ const result = { profile: 'reference-export@1', reference, carrier, context: { ...context, snapshot } };
77
+ if (Buffer.byteLength(JSON.stringify(result) + '\n') > maxBytes)
78
+ fail('budget');
79
+ return result;
80
+ });
81
+ }
@@ -0,0 +1,3 @@
1
+ /** Native host boundary. The platform-free reference inspector remains /reference. */
2
+ export { openAuthenticatedReference } from './reference-native.ts';
3
+ export { exportAuthenticatedReference } from './reference-export.ts';
@@ -0,0 +1,3 @@
1
+ /** Native host boundary. The platform-free reference inspector remains /reference. */
2
+ export { openAuthenticatedReference } from "./reference-native.js";
3
+ export { exportAuthenticatedReference } from "./reference-export.js";
@@ -1,6 +1,26 @@
1
+ import { type ContentReadPort } from '../engine/ports.ts';
1
2
  export declare function authenticatedReferenceSelected(standing: readonly string[]): boolean;
2
- export declare function openAuthenticatedReference(root: string, fragment: string): Promise<{
3
+ /** One context preparation shared by native export and native inspection. */
4
+ export declare function prepareNativeReference(root: string): Promise<{
5
+ policy: string;
6
+ formImplementation: string;
7
+ records: readonly import("@lapxo/topos/wire").ObjectRecord[];
8
+ context: import("@lapxo/topos/wire").ObjectContext;
9
+ configuration: Readonly<Record<string, string>>[];
10
+ }>;
11
+ export declare function openAuthenticatedReference(root: string, fragment: string, options?: {
12
+ readonly signal?: AbortSignal;
13
+ readonly content?: {
14
+ readonly name: string;
15
+ readonly read: ContentReadPort;
16
+ };
17
+ }): Promise<{
18
+ backend: string;
3
19
  readings: readonly import("../engine/object.ts").ObjectReading[];
20
+ contracts?: {
21
+ refusals: string;
22
+ standing: string;
23
+ } | undefined;
4
24
  reference: import("./reference-profile.ts").StandingReference;
5
25
  records: readonly import("@lapxo/topos/wire").ObjectRecord[];
6
26
  semanticIdentity: string;
@@ -1,3 +1,7 @@
1
+ import { readContent } from "../engine/ports.js";
2
+ import { installedReferenceContracts } from "./reference-contracts.js";
3
+ import { BoundRefusal } from "./refusals.js";
4
+ import { foldObjectsWithWasm } from "../wasm/object.js";
1
5
  /** Native ports for authenticated #bound opening; trusted policy belongs to the selected local place. */
2
6
  import { sha256 as hash, isSha256 } from "./hash.js";
3
7
  import { canonical, parse, transportSelection } from '@lapxo/topos/wire';
@@ -22,29 +26,49 @@ export function authenticatedReferenceSelected(standing) {
22
26
  throw Error('REFUSE·reference conflicting or unknown authentication profile');
23
27
  return true;
24
28
  }
25
- export async function openAuthenticatedReference(root, fragment) {
29
+ /** One context preparation shared by native export and native inspection. */
30
+ export async function prepareNativeReference(root) {
31
+ const store = storeOf(root), held = await admittedObjects(root, signedOwnerLines(store), () => { }, true);
32
+ const policy = held.configuration.map(fields => canonical(fields));
33
+ const implementations = [...new Set(held.records.filter(r => r.record === 'cell').map(r => r.fields.topos))].sort().map(pin => { const selected = toposAt(pin, store), selectors = selected.parts.filter(f => f.scope === 'wire/topos/context-view'); if (selectors.length !== 1)
34
+ throw Error('REFUSE·reference host context selector'); const offers = selected.parts.filter(f => f.kind === 'check' && f.view === selectors[0].value); if (offers.length !== 1)
35
+ throw Error('REFUSE·reference host context offer'); const offer = offers[0], closure = [...new Set((offer.restsOn ?? '').split('|').filter(Boolean))].sort(); for (const digest of closure)
36
+ contentAt(digest, store); return [pin, offer.value, closure]; });
37
+ return { ...held, policy: hash(semanticStandingBytes(policy)), formImplementation: hash(JSON.stringify(implementations)) };
38
+ }
39
+ export async function openAuthenticatedReference(root, fragment, options = {}) {
26
40
  const store = storeOf(root), standing = lockStanding(store), era = eraOf(standing), authority = authorityFor(lockLines(store), rootSigner(root), era.admitted);
27
41
  if (!authenticatedReferenceSelected(standing))
28
42
  throw Error('REFUSE·reference no admitted authenticated-standing@1 profile');
29
- const one = (scope) => { const found = standing.flatMap(line => { const p = parse(line); return p.kind === 'fact' && p.value.fields.scope === scope ? [{ line, fields: p.value.fields }] : []; }); if (found.length !== 1 || found[0].fields.role !== 'writes' || found[0].fields.measure !== 'id' || authority.of(found[0].line).kind !== 'admitted')
30
- throw Error('REFUSE·reference missing, conflicting or unauthenticated ' + scope); return found[0].fields.value; };
43
+ const one = (scope) => { const found = standing.flatMap(line => { const p = parse(line); return p.kind === 'fact' && p.value.fields.scope === scope ? [{ line, fields: p.value.fields }] : []; }); if (found.length !== 1 || found[0].fields.role !== 'writes' || found[0].fields.measure !== 'id' || found[0].fields.form !== 'alphabet' || authority.of(found[0].line).kind !== 'admitted')
44
+ throw new BoundRefusal(scope === 'engine/object' ? 'reference/backend' : 'authenticated-reference/context', 'REFUSE·reference missing, conflicting or unauthenticated ' + scope); return found[0].fields.value; };
31
45
  if (one('reference/authentication') !== 'authenticated-standing@1')
32
46
  throw Error('REFUSE·reference authentication profile');
33
47
  const snapshot = one('reference/snapshot'), epochText = one('reference/epoch'), proofScope = one('reference/proof-scope'), recordsText = one('reference/records');
34
48
  if (!isSha256(snapshot) || !/^\d+$/.test(epochText) || !Number.isSafeInteger(Number(epochText)) || !/^\d+$/.test(recordsText) || !Number.isSafeInteger(Number(recordsText)) || Number(recordsText) < 1 || !proofScope)
35
49
  throw Error('REFUSE·reference authority snapshot or resource contract');
50
+ const backend = one('engine/object');
51
+ if (!['esm@1', 'wasm@1'].includes(backend))
52
+ throw new BoundRefusal('reference/backend', 'REFUSE·reference unsupported declared backend');
53
+ if (options.signal?.aborted)
54
+ throw new BoundRefusal('reference/cancelled', 'REFUSE·reference cancelled');
36
55
  const reference = decodeReference(fragment);
37
56
  // Provider selection is made from HOST-declared cells/configuration, never carrier-picked code.
38
- const held = await admittedObjects(root, signedOwnerLines(store), () => { }, true);
57
+ const held = await prepareNativeReference(root);
39
58
  if (!held.records.some(r => r.record === 'cell'))
40
59
  throw Error('REFUSE·reference host has no declared object interpretation');
41
60
  const context = held.context, policy = held.configuration.map(fields => canonical(fields)), selection = transportSelection(held.configuration, new URL(reference.source).protocol.slice(0, -1));
42
61
  if (!selection)
43
62
  throw Error('REFUSE·reference no admitted transport policy');
44
- const implementations = [...new Set(held.records.filter(r => r.record === 'cell').map(r => r.fields.topos))].sort().map(pin => { const selected = toposAt(pin, store), selectors = selected.parts.filter(f => f.scope === 'wire/topos/context-view'); if (selectors.length !== 1)
45
- throw Error('REFUSE·reference host context selector'); const offers = selected.parts.filter(f => f.kind === 'check' && f.view === selectors[0].value); if (offers.length !== 1)
46
- throw Error('REFUSE·reference host context offer'); const offer = offers[0], closure = [...new Set((offer.restsOn ?? '').split('|').filter(Boolean))].sort(); for (const digest of closure)
47
- contentAt(digest, store); return [pin, offer.value, closure]; });
48
- const result = await inspectAuthenticatedReference(fragment, { snapshot, epoch: Number(epochText), proofScope, policy: hash(semanticStandingBytes(policy)), formImplementation: hash(JSON.stringify(implementations)), signers: authority.admitted, algorithms: era.admitted, objects: context, maxBytes: selection.responseBytes, maxRecords: Number(recordsText), digestBytes: hash, verifyBytes, fetchBytes: (source, blob) => transportBytes(source, blob, policy) });
49
- return { ...result, readings: objectFold(result.records, context) };
63
+ if (options.content && options.content.name !== selection.name)
64
+ throw new BoundRefusal('authenticated-reference/transport', 'REFUSE·reference injected content binding conflicts with declaration');
65
+ const contracts = { refusals: one('reference/contracts/refusals'), standing: one('reference/contracts/standing') }, expectedPolicy = one('reference/policy'), expectedImplementation = one('reference/form-implementation');
66
+ const installed = installedReferenceContracts();
67
+ if (contracts.refusals !== installed.refusals || contracts.standing !== installed.standing || !isSha256(expectedPolicy) || expectedImplementation !== held.formImplementation)
68
+ throw new BoundRefusal('authenticated-reference/context', 'REFUSE·reference expected interpretation differs');
69
+ const result = await inspectAuthenticatedReference(fragment, { snapshot, epoch: Number(epochText), proofScope, contracts, policy: expectedPolicy, formImplementation: expectedImplementation, signers: authority.admitted, algorithms: era.admitted, objects: context, maxBytes: selection.responseBytes, maxRecords: Number(recordsText), digestBytes: hash, verifyBytes, fetchBytes: (source, blob) => options.content ? readContent(source, blob, { read: options.content.read, digestBytes: hash }, { timeoutMs: selection.timeoutMs, maxBytes: selection.responseBytes }, options.signal) : transportBytes(source, blob, policy) });
70
+ if (options.signal?.aborted)
71
+ throw new BoundRefusal('reference/cancelled', 'REFUSE·reference cancelled');
72
+ const readings = backend === 'wasm@1' ? await foldObjectsWithWasm(root, result.records, context, held.configuration, options.signal) : objectFold(result.records, context);
73
+ return { ...result, backend, readings };
50
74
  }
@@ -1,5 +1,6 @@
1
+ import { BoundRefusal } from "./refusals.js";
1
2
  const hex = /^[0-9a-f]{64}$/;
2
- const refuse = (cause) => { throw Error(`REFUSE·reference ${cause}`); };
3
+ const refuse = (cause) => { throw new BoundRefusal('reference/format', `REFUSE·reference ${cause}`); };
3
4
  const keys = ['class', 'algorithm', 'digest', 'blob', 'source'];
4
5
  function check(reference) {
5
6
  if (Object.keys(reference).length !== keys.length || keys.some(key => !Object.prototype.hasOwnProperty.call(reference, key)))
@@ -0,0 +1,18 @@
1
+ import { parse } from '@lapxo/topos/wire';
2
+ import { cellInputs } from '@lapxo/topos/cell-inputs';
3
+ import { semanticStandingBytes } from '@lapxo/topos/standing';
4
+ /** Shared projection after admission; transport order never replaces effective order. */
5
+ export function referenceStanding(originals, records) {
6
+ const membership = new Set([...cellInputs(records, 'semantic-live@2').values()].flat().map(line => semanticStandingBytes([line])));
7
+ const fields = (line) => { const p = parse(line); if (p.kind !== 'fact')
8
+ throw Error('REFUSE·authenticated-reference original parse'); return p.value.fields; };
9
+ const effective = [...originals].sort((a, b) => Number(fields(a).epoch) - Number(fields(b).epoch));
10
+ return semanticStandingBytes(effective.filter(line => fields(line).type === undefined || membership.has(semanticStandingBytes([line]))));
11
+ }
12
+ export function referenceContracts(value) {
13
+ return value !== null && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 2 && /^sha256:[a-f0-9]{64}$/.test(value.refusals) && /^sha256:[a-f0-9]{64}$/.test(value.standing);
14
+ }
15
+ /** This versioned context frame binds interpretation contracts without changing wire fields. */
16
+ export function referenceContextBytes(context) {
17
+ return new TextEncoder().encode(JSON.stringify(['reference-export-context@1', context.semanticIdentity, context.epoch, context.proofScope, context.policy, context.formImplementation, context.signers, context.algorithms, context.contracts.refusals, context.contracts.standing, context.maxBytes, context.maxRecords]) + '\n');
18
+ }
@@ -0,0 +1,14 @@
1
+ export interface RefusalEntry {
2
+ readonly code: string;
3
+ readonly category: string;
4
+ readonly meaning: string;
5
+ readonly next: string;
6
+ }
7
+ /** Stable code assigned at the cause. Details are diagnostic, never parsed as policy. */
8
+ export declare class BoundRefusal extends Error {
9
+ readonly code: string;
10
+ constructor(code: string, diagnostic: string);
11
+ }
12
+ /** Pure machine contract reader. A catalog is data, never an authority grant. */
13
+ export declare function readRefusalCatalog(text: string): readonly RefusalEntry[];
14
+ export declare function describeRefusal(error: BoundRefusal, catalog: readonly RefusalEntry[]): RefusalEntry;
@@ -0,0 +1,43 @@
1
+ import { parse, canonical } from '@lapxo/topos/wire';
2
+ /** Stable code assigned at the cause. Details are diagnostic, never parsed as policy. */
3
+ export class BoundRefusal extends Error {
4
+ constructor(code, diagnostic) { super(diagnostic); this.name = 'BoundRefusal'; this.code = code; }
5
+ }
6
+ /** Pure machine contract reader. A catalog is data, never an authority grant. */
7
+ export function readRefusalCatalog(text) {
8
+ const groups = new Map();
9
+ let profile = false;
10
+ for (const line of text.trimEnd().split('\n')) {
11
+ const parsed = parse(line);
12
+ if (parsed.kind !== 'fact' || canonical(parsed.value.fields) !== line)
13
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·invalid refusal catalog line');
14
+ const f = parsed.value.fields;
15
+ if (f.scope === 'refusal/catalog' && f.measure === 'id' && f.value === 'bound-refusals@1' && f.role === 'writes' && f.form === 'alphabet' && !profile) {
16
+ profile = true;
17
+ continue;
18
+ }
19
+ if (f.role !== 'writes' || f.form !== 'alphabet' || !f.scope?.startsWith('refusal/') || !['code', 'class', 'next'].includes(f.measure ?? ''))
20
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·invalid refusal catalog record');
21
+ const group = groups.get(f.scope) ?? new Map();
22
+ if (group.has(f.measure))
23
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·duplicate refusal catalog field');
24
+ group.set(f.measure, f);
25
+ groups.set(f.scope, group);
26
+ }
27
+ const entries = [];
28
+ for (const [scope, group] of groups) {
29
+ const code = group.get('code')?.value, category = group.get('class')?.value, meaning = group.get('class')?.about, next = group.get('next')?.value;
30
+ if (group.size !== 3 || !code || scope !== 'refusal/' + code || !category || !meaning || !next)
31
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·incomplete refusal catalog entry');
32
+ entries.push(Object.freeze({ code, category, meaning, next }));
33
+ }
34
+ if (!profile || !entries.length)
35
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·missing refusal catalog profile');
36
+ return Object.freeze(entries);
37
+ }
38
+ export function describeRefusal(error, catalog) {
39
+ const entry = catalog.find(row => row.code === error.code);
40
+ if (!entry)
41
+ throw new BoundRefusal('catalog/invalid', 'REFUSE·undeclared refusal code');
42
+ return entry;
43
+ }
@@ -1,3 +1,4 @@
1
+ import { invokeSigningPort } from "../engine/ports.js";
1
2
  import { readFileSync } from 'node:fs';
2
3
  import { parse, signerSelection, signerRequestBytes, signerResponseBytes, signerResponseOf } from '@lapxo/topos/wire';
3
4
  import { invokeSigner, SigningFailure } from "./ports/signing.js";
@@ -31,11 +32,16 @@ function binding(name, keyFile) {
31
32
  }
32
33
  }
33
34
  /** Resolve data before acquisition; all mechanisms use one wire protocol and verification path. */
34
- export function signerFor(lines, keyId, selected, keyFile) {
35
+ export function signerFor(lines, keyId, selected, keyFile, injected) {
35
36
  const fields = lines.flatMap(line => { const got = parse(line); return got.kind === 'fact' ? [got.value.fields] : []; });
36
37
  const declaration = signerSelection(fields, keyId);
37
38
  if (selected !== undefined && selected !== declaration.name)
38
39
  throw new SigningFailure(`REFUSE·signer ${keyId} CLI selection conflicts with declaration`);
40
+ if (injected) {
41
+ if (injected.name !== declaration.name || keyFile)
42
+ throw new SigningFailure('REFUSE·signer injected host binding conflicts with declaration or key file');
43
+ return (lot) => invokeSigningPort(lot, injected.sign, { timeoutMs: declaration.timeoutMs, maxBytes: declaration.responseBytes });
44
+ }
39
45
  const mechanism = binding(declaration.name, keyFile);
40
46
  const protocol = { request: signerRequestBytes, response: signerResponseBytes, reply: signerResponseOf };
41
47
  return (lot) => invokeSigner(lot, mechanism, protocol, declaration);
@@ -24,7 +24,17 @@ function bytesAt(at) {
24
24
  const held = seen.get(at);
25
25
  if (held?.stamp === stamp)
26
26
  return held.bytes;
27
- const bytes = readFileSync(at);
27
+ let bytes;
28
+ try {
29
+ bytes = readFileSync(at);
30
+ }
31
+ catch (error) {
32
+ // A concurrent act can retire a coordinate after its stamp was read.
33
+ if (error.code !== 'ENOENT')
34
+ throw error;
35
+ seen.delete(at);
36
+ return undefined;
37
+ }
28
38
  seen.set(at, { stamp, bytes });
29
39
  return bytes;
30
40
  }
@@ -10,9 +10,9 @@ export function usesWasmObject(configuration) {
10
10
  const rows = configuration.filter(f => f.scope === 'engine/object' && f.value !== 'withdraw');
11
11
  if (!rows.length)
12
12
  return false;
13
- if (rows.length !== 1 || rows[0].role !== 'writes' || rows[0].measure !== 'id' || rows[0].form !== 'alphabet' || rows[0].value !== 'wasm@1')
13
+ if (rows.length !== 1 || rows[0].role !== 'writes' || rows[0].measure !== 'id' || rows[0].form !== 'alphabet' || !['esm@1', 'wasm@1'].includes(rows[0].value ?? ''))
14
14
  refuse('unique declared backend required');
15
- return true;
15
+ return rows[0].value === 'wasm@1';
16
16
  }
17
17
  function limit(configuration, name, measure) {
18
18
  const rows = configuration.filter(f => f.scope === 'engine/wasm/' + name && f.value !== 'withdraw');
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "role": "contract",
6
6
  "path": "docs/contracts/reference-port.md",
7
- "digest": "sha256:ae500aaec7e94dc9bc7c4d45e3294b212b0ad5ade7647e3ce24807ca7a0ea7cc"
7
+ "digest": "sha256:d6198b9ab177086cb334e9e9cd7adb5c67b0af7165a79d23812aa2496d17674c"
8
8
  },
9
9
  {
10
10
  "role": "yes",
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "role": "vector",
16
16
  "path": "vectors/native/authenticated-reference.vector.json",
17
- "digest": "sha256:297c4942a847c42eb9d2cf000ab38d8a5f38b64e2e4a469919f0dfc88c9d8bd1"
17
+ "digest": "sha256:9aba7121fbbb98e63259dea78e52377cd71c628e0d17be20c9a51da27f2b31de"
18
18
  },
19
19
  {
20
20
  "role": "no",
@@ -0,0 +1,23 @@
1
+ # Public host ports
2
+
3
+ `/engine` and `/reference` export `readContent`, `retainContent` and `invokeSigningPort`, with `ContentReadPort`, `PersistencePort`, `SigningPort`, `DigestPort` and `PortLimits`. There is no default network, store, account or signer. `objectFold` remains pure and never calls a port.
4
+
5
+ Every operation takes an explicit positive safe-integer `timeoutMs` and `maxBytes`, and an optional AbortSignal. Invalid limits and pre-cancellation refuse before the callback. Acquisition, digest calculation and retention share the operation lifetime. A timeout aborts the supplied signal and returns no successful result. This is a cooperative host boundary, not preemption of synchronous JavaScript or sandbox confinement.
6
+
7
+ `readContent(source, digest, ports, limits, signal)` invokes the supplied read callback and SHA-256 callback, checks byte bounds before copying/hashing, rejects shared buffers, and verifies an owned snapshot against the requested digest. Location carries no authority.
8
+
9
+ `retainContent(digest, bytes, ports, limits, signal)` checks identity before calling the host's atomic byte-retention callback. Its acknowledgement contains only digest and byte count. It is not a signed receipt, admission or permission to reuse a response. The host must implement atomic retention and cooperate with cancellation. A callback that ignores cancellation may finish retaining bytes after the caller has refused; no success, act or authority follows from those bytes. Opening and projection never retain automatically.
10
+
11
+ `invokeSigningPort(lot, sign, limits, signal)` uses the existing Topos signing request/response framing. It acquires protocol-shaped signatures; it does not decide authority or certify them cryptographically. Native export can receive `hostSigner: {name, sign}` through `/reference/native`. The name must match the admitted signer selection, and cannot be combined with a key-file override. The existing consent path verifies every returned signature against the admitted key, era, scope and epoch before returning a carrier. No key material enters the engine.
12
+
13
+ Native opening accepts `content: {name, read}`. Its name must match the admitted transport selection, with that selection's existing byte and lifetime bounds. The host callback cannot be chosen by a carrier, reference or repository. The existing inspector verifies the returned carrier again and the selected backend projects only verified records. There is no backend fallback.
14
+
15
+ Public port failures use the machine refusal catalog: `host-port/budget`, `cancelled`, `timeout`, `failed`, `integrity`, `response`. Native acquisition failures retain their enclosing reference refusal. Host diagnostics are not returned as authority or parsed as codes.
16
+
17
+ Native vector output, in two real admitted places:
18
+
19
+ ```text
20
+ PORTS owned content · verified retention · signer content persistence failures · budget integrity cancellation timeout refused
21
+ HOST host-sensor · declared signer and content injected · real signature verified · wrong binding refused · no commit
22
+ HOST host-editorial · declared signer and content injected · real signature verified · wrong binding refused · no commit
23
+ ```
@@ -0,0 +1,25 @@
1
+ {
2
+ "scope": "contract/published/host-ports",
3
+ "members": [
4
+ {
5
+ "role": "contract",
6
+ "path": "docs/contracts/host-ports.md",
7
+ "digest": "sha256:f3d646da2399c719469a37293b329d53b72b0478ca97f961b90c1f4e6adc2ae6"
8
+ },
9
+ {
10
+ "role": "yes",
11
+ "path": "samples/host-ports/yes.json",
12
+ "digest": "sha256:f8dc3c6939bd8f4bb91cb57e3e9b52a865645cc80ee0f5ce5a11570ef3f31ce3"
13
+ },
14
+ {
15
+ "role": "vector",
16
+ "path": "vectors/native/host-ports.vector.json",
17
+ "digest": "sha256:58425f81fb4f1ef6e392d8fa3a5ee3a287ef6fbcbfb11e512fd2e30f24f465e6"
18
+ },
19
+ {
20
+ "role": "no",
21
+ "path": "samples/host-ports/no.json",
22
+ "digest": "sha256:ffc9dbb29743d4faa16c5c29eb73a7e0f24cf4da3255a979f628f5c3d8f4b7c1"
23
+ }
24
+ ]
25
+ }
@@ -3,6 +3,12 @@
3
3
  "scope": "contract/published/native",
4
4
  "surface": [
5
5
  "contract/published/optional-wasm-adapter",
6
- "contract/published/authenticated-reference"
6
+ "contract/published/authenticated-reference",
7
+ "contract/published/reference-export",
8
+ "contract/published/reference-backend",
9
+ "contract/published/standing",
10
+ "contract/published/refusals",
11
+ "contract/published/host-ports",
12
+ "contract/published/readme"
7
13
  ]
8
14
  }
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "role": "vector",
16
16
  "path": "vectors/wasm-object.cli.json",
17
- "digest": "sha256:5f761fabd0592d7f8e9b95a9fc80583424a8e83fb7cb3c4b4905b62eca43db9e"
17
+ "digest": "sha256:f0b970ede31b99e00e94f249970aa8c714880dccb03ed7952a3cf01bf24a7c17"
18
18
  },
19
19
  {
20
20
  "role": "no",