@lapxo/bound 0.2.3 → 0.2.4

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 (67) hide show
  1. package/.github/workflows/release.yml +67 -0
  2. package/.github/workflows/test.yml +35 -0
  3. package/CITATION.cff +19 -0
  4. package/TARGET.bound +71 -20
  5. package/dist/cli/accept.js +103 -71
  6. package/dist/cli/place.js +3 -2
  7. package/dist/cli/standing-result.js +3 -5
  8. package/dist/cli/takes.js +4 -3
  9. package/dist/cli/verb.js +2 -1
  10. package/dist/engine/ports.d.ts +6 -1
  11. package/dist/engine/ports.js +23 -9
  12. package/dist/fold/claims.d.ts +29 -0
  13. package/dist/fold/claims.js +2 -1
  14. package/dist/fold/closed.js +2 -1
  15. package/dist/fold/digests.js +7 -7
  16. package/dist/host/admitted-place.js +3 -31
  17. package/dist/host/call-context.d.ts +20 -0
  18. package/dist/host/call-context.js +14 -0
  19. package/dist/host/capsule-process.js +2 -1
  20. package/dist/host/capsule.js +7 -4
  21. package/dist/host/commit-result.d.ts +6 -0
  22. package/dist/host/commit-result.js +5 -0
  23. package/dist/host/contextual-place.js +9 -14
  24. package/dist/host/host-controls.js +19 -0
  25. package/dist/host/in-process-act.d.ts +82 -0
  26. package/dist/host/in-process-act.js +84 -0
  27. package/dist/host/in-process-context.d.ts +46 -0
  28. package/dist/host/in-process-context.js +91 -0
  29. package/dist/host/in-process-root.d.ts +117 -0
  30. package/dist/host/in-process-root.js +84 -0
  31. package/dist/host/in-process.d.ts +41 -0
  32. package/dist/host/in-process.js +55 -0
  33. package/dist/host/index.d.ts +15 -0
  34. package/dist/host/index.js +7 -0
  35. package/dist/host/native-receiver.d.ts +19 -0
  36. package/dist/host/native-receiver.js +25 -0
  37. package/dist/host/place-act-transaction.d.ts +24 -0
  38. package/dist/host/place-act-transaction.js +78 -0
  39. package/dist/host/place-clock-selection.d.ts +55 -0
  40. package/dist/host/place-clock-selection.js +1 -1
  41. package/dist/host/ports/local-act.d.ts +44 -0
  42. package/dist/host/ports/local-act.js +13 -2
  43. package/dist/host/ports/place-act.d.ts +49 -0
  44. package/dist/host/ports/place-act.js +19 -54
  45. package/dist/host/ports/publication.d.ts +10 -0
  46. package/dist/host/root-clock-source.d.ts +86 -0
  47. package/dist/host/root-clock-source.js +7 -5
  48. package/dist/host/signature-era.js +12 -0
  49. package/dist/host/signing.js +1 -1
  50. package/dist/host/source-content.js +2 -1
  51. package/dist/host/standing-carrier.js +18 -0
  52. package/dist/host/standing-projection.js +50 -0
  53. package/dist/land/act.js +60 -26
  54. package/dist/observe/run.js +2 -1
  55. package/dist/observe/runner.js +16 -8
  56. package/docs/contracts/native-host.md +21 -0
  57. package/docs/contracts/native-host.publication.json +25 -0
  58. package/docs/contracts/native-publication.inventory.json +2 -1
  59. package/docs/contracts/publication.inventory.json +29 -0
  60. package/docs/guide.md +10 -0
  61. package/docs/reference.md +39 -0
  62. package/docs/why.md +109 -0
  63. package/package.json +32 -2
  64. package/receipts.bound +10369 -9723
  65. package/samples/native-host/no.json +10 -0
  66. package/samples/native-host/yes.json +20 -0
  67. package/vectors/native/native-host.vector.json +62 -0
@@ -1,3 +1,8 @@
1
+ import { BoundRefusal } from "../host/refusals.js";
2
+ import { assertHostNotCancelled as notCancelled } from "../host/host-controls.js";
3
+ import { authorityAtEpoch } from '@lapxo/topos/wire';
4
+ import { requireEffects } from "../host/read-only.js";
5
+ import { hostOutput } from "../host/call-context.js";
1
6
  import { assertProspectiveRootClockSelection } from "../host/root-clock-source.js";
2
7
  import { hasContextualPlace, landContextualPlace } from "../host/contextual-place.js";
3
8
  import { commitLocalAct, intactLocalAct, committedLocalActs } from "../host/ports/local-act.js";
@@ -78,8 +83,8 @@ function refusesWidening(signed, standing) {
78
83
  const widened = widenedWithoutWitness(signed, standing, isCeiling, wordOf(standing, 'at-classes', 'witness'));
79
84
  if (widened.length) {
80
85
  for (const line of widened)
81
- process.stderr.write(`WIDER ${fieldOf(line, 'scope')} · ${fieldOf(line, 'value')}\n`);
82
- process.stderr.write(`${selfName()}: REFUSE·widening nothing landed · a ceiling that admits more than it did gives back freedom the lock had spent: sign it under a witness that names what made the old ceiling wrong\n`);
86
+ hostOutput('error', `WIDER ${fieldOf(line, 'scope')} · ${fieldOf(line, 'value')}\n`);
87
+ hostOutput('error', `${selfName()}: REFUSE·widening nothing landed · a ceiling that admits more than it did gives back freedom the lock had spent: sign it under a witness that names what made the old ceiling wrong\n`);
83
88
  return true;
84
89
  }
85
90
  return false;
@@ -91,26 +96,26 @@ function refusesAdmission(root, signed, standing) {
91
96
  const store = storeOf(root);
92
97
  const outside = outsideTheirCapsule(root, standing, signed);
93
98
  for (const line of outside)
94
- process.stderr.write(`OUTSIDE ${fieldOf(line, 'scope')} · ${fieldOf(line, 'value')}\n`);
99
+ hostOutput('error', `OUTSIDE ${fieldOf(line, 'scope')} · ${fieldOf(line, 'value')}\n`);
95
100
  if (outside.length) {
96
- process.stderr.write(`${selfName()}: REFUSE·outside nothing landed · a reader or a capsule lands in the capsule of its world, never in the wire or the instrument\n`);
101
+ hostOutput('error', `${selfName()}: REFUSE·outside nothing landed · a reader or a capsule lands in the capsule of its world, never in the wire or the instrument\n`);
97
102
  return true;
98
103
  }
99
104
  const forks = forksJoinedBy(store, signed).filter(fork => forksItself(fork.lines) || fork.lines.filter(line => signed.includes(line)).length > 1);
100
105
  if (forks.length) {
101
106
  for (const fork of forks) {
102
- process.stderr.write(`FORK ${fork.key}\n`);
107
+ hostOutput('error', `FORK ${fork.key}\n`);
103
108
  for (const line of fork.lines)
104
- process.stderr.write(` ${signed.includes(line) ? 'new ' : 'held'} by=${fieldOf(line, 'by')} ${sideOf(line, lockLines(store))}\n`);
109
+ hostOutput('error', ` ${signed.includes(line) ? 'new ' : 'held'} by=${fieldOf(line, 'by')} ${sideOf(line, lockLines(store))}\n`);
105
110
  }
106
- process.stderr.write(`${selfName()}: REFUSE·fork nothing landed · one signer never forks itself, and one act never forks itself: withdraw the held side in this batch, or sign the other reading under its own scope\n`);
111
+ hostOutput('error', `${selfName()}: REFUSE·fork nothing landed · one signer never forks itself, and one act never forks itself: withdraw the held side in this batch, or sign the other reading under its own scope\n`);
107
112
  return true;
108
113
  }
109
114
  const held = aliasPinned(root, signed);
110
115
  if (held.length) {
111
116
  for (const one of held)
112
- process.stderr.write(`ALIAS ${one.scope} · ${one.digest} · ${one.by.join(' ')}\n`);
113
- process.stderr.write(`${selfName()}: REFUSE·alias nothing landed · an alias is withdrawn only when no pin names its digest\n`);
117
+ hostOutput('error', `ALIAS ${one.scope} · ${one.digest} · ${one.by.join(' ')}\n`);
118
+ hostOutput('error', `${selfName()}: REFUSE·alias nothing landed · an alias is withdrawn only when no pin names its digest\n`);
114
119
  return true;
115
120
  }
116
121
  return false;
@@ -121,24 +126,24 @@ function refusesStandingPins(root, lines) {
121
126
  return false;
122
127
  }
123
128
  catch (error) {
124
- process.stderr.write(`${selfName()}: ${error instanceof Error ? error.message : String(error)}\n`);
129
+ hostOutput('error', `${selfName()}: ${error instanceof Error ? error.message : String(error)}\n`);
125
130
  return true;
126
131
  }
127
132
  }
128
133
  /** A batch signed as accept signs it: at the next epoch, by the key it names, each line read by the wire it would land under. */
129
- export async function signedBatch(root, args, files) {
130
- const byFile = files.map((file) => [placesOfFile(file), claimLinesIn(file)]);
134
+ export async function signedBatch(root, args, files, host) {
135
+ const byFile = host ? [[[], host.records]] : files.map((file) => [placesOfFile(file), claimLinesIn(file)]);
131
136
  const proposed = byFile.flatMap(([, lines]) => lines);
132
137
  if (!proposed.length) {
133
- process.stderr.write(`${selfName()}: a batch is a file with claim lines\n`);
138
+ hostOutput('error', `${selfName()}: a batch is a file with claim lines\n`);
134
139
  return { kind: 'exit', code: 2 };
135
140
  }
136
141
  const drafted = proposed.filter((line) => !fieldOf(line, 'sig'));
137
142
  const bare = drafted.filter((line) => fieldOf(line, 'epoch') || !['', 'target'].includes(fieldOf(line, 'by')));
138
143
  if (bare.length) {
139
144
  for (const line of bare)
140
- process.stderr.write(`BARE ${fieldOf(line, 'scope')} · by=${fieldOf(line, 'by') || '∅'}${fieldOf(line, 'epoch') ? ` epoch=${fieldOf(line, 'epoch')}` : ''}\n`);
141
- process.stderr.write(`${selfName()}: REFUSE·bare nothing landed · ${bare.length} lines name a signer or an epoch they were not signed with: a drafted line proposes by=target and sign signs it, a line a lane signed carries its sig\n`);
145
+ hostOutput('error', `BARE ${fieldOf(line, 'scope')} · by=${fieldOf(line, 'by') || '∅'}${fieldOf(line, 'epoch') ? ` epoch=${fieldOf(line, 'epoch')}` : ''}\n`);
146
+ hostOutput('error', `${selfName()}: REFUSE·bare nothing landed · ${bare.length} lines name a signer or an epoch they were not signed with: a drafted line proposes by=target and sign signs it, a line a lane signed carries its sig\n`);
142
147
  return { kind: 'exit', code: 1 };
143
148
  }
144
149
  const store = storeOf(root);
@@ -146,7 +151,7 @@ export async function signedBatch(root, args, files) {
146
151
  const rooted = lockStanding(store);
147
152
  const stray = destinations.filter(one => !placesIn(root).includes(one) && !foundableIn(root, rooted, wordOf(rooted, 'families', 'region'), one));
148
153
  if (stray.length) {
149
- process.stderr.write(`${selfName()}: REFUSE·place nothing landed · ${stray.join(', ')} is no place of the tree, and no line of the root founds it: --place names the own lock a line lands in\n`);
154
+ hostOutput('error', `${selfName()}: REFUSE·place nothing landed · ${stray.join(', ')} is no place of the tree, and no line of the root founds it: --place names the own lock a line lands in\n`);
150
155
  return { kind: 'exit', code: 2 };
151
156
  }
152
157
  const local = localActLines(root, destinations);
@@ -157,8 +162,8 @@ export async function signedBatch(root, args, files) {
157
162
  : []))(fieldOf(line, 'by'), publicKeyOf(root, fieldOf(line, 'by'))));
158
163
  if (stale.length) {
159
164
  for (const one of stale)
160
- process.stderr.write(`SIGNED ${one}\n`);
161
- process.stderr.write(`${selfName()}: REFUSE·signed nothing landed · ${stale.length} lines a lane signed stand at no epoch this act\n`);
165
+ hostOutput('error', `SIGNED ${one}\n`);
166
+ hostOutput('error', `${selfName()}: REFUSE·signed nothing landed · ${stale.length} lines a lane signed stand at no epoch this act\n`);
162
167
  return { kind: 'exit', code: 1 };
163
168
  }
164
169
  const [named, places] = [namedPlaces(args), placesIn(root)];
@@ -177,7 +182,7 @@ export async function signedBatch(root, args, files) {
177
182
  await admittedObjects(root, [...signedOwnerLines(store), ...proposed]);
178
183
  }
179
184
  catch (e) {
180
- process.stderr.write(`${selfName()}: ${String(e)} · nothing landed\n`);
185
+ hostOutput('error', `${selfName()}: ${String(e)} · nothing landed\n`);
181
186
  return { kind: 'exit', code: 1 };
182
187
  }
183
188
  }
@@ -186,30 +191,39 @@ export async function signedBatch(root, args, files) {
186
191
  const outdated = proposed.filter(line => fieldOf(line, 'sig') && isConfig(line) && Number(fieldOf(line, 'epoch')) !== epoch);
187
192
  if (outdated.length) {
188
193
  for (const line of outdated)
189
- process.stderr.write(`SIGNED ${fieldOf(line, 'scope')} · signed by ${fieldOf(line, 'by')} at epoch ${fieldOf(line, 'epoch')}, and this act lands at ${epoch}: sign it again\n`);
190
- process.stderr.write(`${selfName()}: REFUSE·signed nothing landed · ${outdated.length} lines a lane signed stand at no epoch this act\n`);
194
+ hostOutput('error', `SIGNED ${fieldOf(line, 'scope')} · signed by ${fieldOf(line, 'by')} at epoch ${fieldOf(line, 'epoch')}, and this act lands at ${epoch}: sign it again\n`);
195
+ hostOutput('error', `${selfName()}: REFUSE·signed nothing landed · ${outdated.length} lines a lane signed stand at no epoch this act\n`);
191
196
  return { kind: 'exit', code: 1 };
192
197
  }
193
- const keyFile = valueOf(args, FLAGS.keyFile);
194
- const keyId = valueOf(args, FLAGS.key) ?? ownerOf(store);
198
+ const keyFile = host ? undefined : valueOf(args, FLAGS.keyFile);
199
+ const keyId = host?.keyId ?? valueOf(args, FLAGS.key) ?? ownerOf(store);
195
200
  const publicKey = drafted.length ? publicKeyOf(root, keyId) : undefined;
196
201
  if (drafted.length && !publicKey) {
197
- process.stderr.write(`${selfName()}: unknown key ${keyId} — name it in ${selfName()}.keys\n`);
202
+ hostOutput('error', `${selfName()}: unknown key ${keyId} — name it in ${selfName()}.keys\n`);
198
203
  return { kind: 'exit', code: 2 };
199
204
  }
200
205
  let signed = proposed.filter((line) => fieldOf(line, 'sig'));
206
+ if (host && drafted.length) {
207
+ notCancelled(host.signal);
208
+ const selected = authorityFor(lockLines(store), rootSigner(root), signaturesOf(store).admitted).admitted.find(s => s.id === keyId);
209
+ for (const line of drafted) {
210
+ const parsed = parse(line);
211
+ if (parsed.kind !== 'fact' || !selected || authorityAtEpoch({ ...parsed.value.fields, by: keyId, epoch: String(epoch) }, selected, epoch, fieldOf(line, 'scope'), 'authority').kind !== 'admitted')
212
+ throw new BoundRefusal('host/receiver-authority', 'REFUSE·host/receiver-authority');
213
+ }
214
+ }
201
215
  if (drafted.length) {
202
216
  let invoke;
203
217
  try {
204
- invoke = signerFor([...lockStanding(store), ...destinations.flatMap(place => ownLockOf(root, place))], keyId, valueOf(args, FLAGS.signer), keyFile);
218
+ invoke = signerFor([...lockStanding(store), ...destinations.flatMap(place => ownLockOf(root, place))], keyId, host?.signer.name ?? valueOf(args, FLAGS.signer), keyFile, host?.signer);
205
219
  }
206
220
  catch (error) {
207
- process.stderr.write(`${selfName()}: ${error instanceof Error ? error.message : 'REFUSE·signer unavailable'}\n`);
221
+ hostOutput('error', `${selfName()}: ${error instanceof Error ? error.message : 'REFUSE·signer unavailable'}\n`);
208
222
  return { kind: 'exit', code: 1 };
209
223
  }
210
224
  const got = await signConsentLot(drafted, { epoch, by: keyId, algorithm: signaturesOf(store).era }, publicKey, signaturesOf(store).admitted, invoke);
211
225
  if (got.kind === 'refuse') {
212
- process.stderr.write(`${selfName()}: ${got.why}\n`);
226
+ hostOutput('error', `${selfName()}: ${got.why}\n`);
213
227
  return { kind: 'exit', code: 1 };
214
228
  }
215
229
  let index = 0;
@@ -222,7 +236,7 @@ export async function signedBatch(root, args, files) {
222
236
  for (const line of signed) {
223
237
  const got = admittedLine(line, wireLines, Number(fieldOf(line, 'epoch')), foldClaims(signedOwnerLines(store)).standing.filter(one => fieldOf(one, 'scope').startsWith('uses/') && fieldOf(one, 'value') !== 'withdraw'));
224
238
  if (got.kind !== 'fact') {
225
- process.stderr.write(`${selfName()}: REFUSE·wire ${fieldOf(line, 'scope')} · ${got.why} · nothing landed\n`);
239
+ hostOutput('error', `${selfName()}: REFUSE·wire ${fieldOf(line, 'scope')} · ${got.why} · nothing landed\n`);
226
240
  return { kind: 'exit', code: 1 };
227
241
  }
228
242
  }
@@ -231,33 +245,33 @@ export async function signedBatch(root, args, files) {
231
245
  const taken = retractedTogether(signed);
232
246
  if (taken.length) {
233
247
  for (const pair of taken) {
234
- process.stderr.write(`WITHDRAW ${sayingOf(pair.withdraw)}\n`);
235
- process.stderr.write(`NEW ${sayingOf(pair.line)}\n`);
248
+ hostOutput('error', `WITHDRAW ${sayingOf(pair.withdraw)}\n`);
249
+ hostOutput('error', `NEW ${sayingOf(pair.line)}\n`);
236
250
  }
237
- process.stderr.write(`${selfName()}: REFUSE·withdraw nothing landed · a withdraw matches a new line in this batch, so it takes that line too\n`);
251
+ hostOutput('error', `${selfName()}: REFUSE·withdraw nothing landed · a withdraw matches a new line in this batch, so it takes that line too\n`);
238
252
  return { kind: 'exit', code: 1 };
239
253
  }
240
254
  const authority = authorityFor([...lockLines(store), ...signed], rootSigner(root), signaturesOf(store).admitted);
241
255
  const ownership = ownedOf(root, signed, line => authority.of(line).kind === 'admitted', by => authority.admitted.find(one => one.id === by)?.coverage ?? [], named, line => { const heads = byFile.flatMap(([heads, lines]) => lines.some(draft => sayingOf(draft) === sayingOf(line)) ? heads : []); return heads.length ? heads : undefined; });
242
256
  if (ownership.nowhere.length) {
243
257
  for (const line of ownership.nowhere)
244
- process.stderr.write(`${selfName()}: REFUSE·signer ${keyId} uncovered ${fieldOf(line, 'scope')} · nothing signed for delivery\n`);
258
+ hostOutput('error', `${selfName()}: REFUSE·signer ${keyId} uncovered ${fieldOf(line, 'scope')} · nothing signed for delivery\n`);
245
259
  return { kind: 'exit', code: 1 };
246
260
  }
247
261
  if (ownership.forked.length) {
248
262
  for (const one of ownership.forked)
249
- process.stderr.write(`FORKED ${one.place}/${LOCK} · ${one.key} · the lot would leave two standing claims where the lock had one\n`);
250
- process.stderr.write(`${selfName()}: REFUSE·forked nothing landed · ${ownership.forked.length} claims an own lot would fork: a lot for many places lands the same lines at each, and lines meant for one place go in a lot of their own\n`);
263
+ hostOutput('error', `FORKED ${one.place}/${LOCK} · ${one.key} · the lot would leave two standing claims where the lock had one\n`);
264
+ hostOutput('error', `${selfName()}: REFUSE·forked nothing landed · ${ownership.forked.length} claims an own lot would fork: a lot for many places lands the same lines at each, and lines meant for one place go in a lot of their own\n`);
251
265
  return { kind: 'exit', code: 1 };
252
266
  }
253
267
  if (refusesAdmission(root, ownership.ledger, lockStanding(store)))
254
268
  return { kind: 'exit', code: 1 };
255
269
  if (signed.some(line => !isConfig(line))) {
256
270
  try {
257
- await admittedObjects(root, [...signedOwnerLines(store), ...signed], text => process.stdout.write(text + '\n'));
271
+ await admittedObjects(root, [...signedOwnerLines(store), ...signed], text => hostOutput('out', text + '\n'));
258
272
  }
259
273
  catch (e) {
260
- process.stderr.write(`${selfName()}: ${e instanceof Error ? e.message : String(e)} · nothing landed\n`);
274
+ hostOutput('error', `${selfName()}: ${e instanceof Error ? e.message : String(e)} · nothing landed\n`);
261
275
  return { kind: 'exit', code: 1 };
262
276
  }
263
277
  }
@@ -265,7 +279,7 @@ export async function signedBatch(root, args, files) {
265
279
  admissionEvidence(root, ownership.ledger, lockStanding(store));
266
280
  }
267
281
  catch (error) {
268
- process.stderr.write(`${selfName()}: ${error instanceof Error ? error.message : String(error)} · nothing landed\n`);
282
+ hostOutput('error', `${selfName()}: ${error instanceof Error ? error.message : String(error)} · nothing landed\n`);
269
283
  return { kind: 'exit', code: 1 };
270
284
  }
271
285
  return { kind: 'signed', signed, epoch, base, ...(drafted.length ? { keyId } : {}) };
@@ -285,12 +299,26 @@ export async function accept(root, args) {
285
299
  if (namedPlaces(args).length || valueOf(args, FLAGS.key) || valueOf(args, FLAGS.keyFile) || valueOf(args, FLAGS.signer))
286
300
  throw Error('REFUSE·contextual place accepts one already signed context envelope');
287
301
  const result = await landContextualPlace(root, contextFiles[0]);
288
- process.stdout.write(JSON.stringify({ profile: 'place-act-context@1', identity: result.identity, status: result.kind, context: result.context, receipts: result.receipts }) + '\n');
302
+ hostOutput('out', JSON.stringify({ profile: 'place-act-context@1', identity: result.identity, status: result.kind, context: result.context, receipts: result.receipts }) + '\n');
289
303
  return 0;
290
304
  }
291
305
  const named = namedPlaces(args);
292
306
  const placed = positionalsOf(args).reduce((all, file) => ((places) => (places.length ? claimLinesIn(file).reduce((got, line) => got.set(sayingOf(line), [...new Set([...(got.get(sayingOf(line)) ?? []), ...places])]), all) : all))(placesOfFile(file)), new Map());
293
307
  const batch = await signedBatch(root, args, positionalsOf(args));
308
+ return finishAcceptance(root, args, batch, placed, started);
309
+ }
310
+ /** Root host boundary, sharing signing, wire, coverage, takes and publication
311
+ * with accept(). No file is manufactured for a batch of already-owned bytes. */
312
+ export async function acceptRootRecords(root, host) {
313
+ requireEffects('Root lot admission');
314
+ notCancelled(host.signal);
315
+ if (!actResultProfile(lockStanding(storeOf(root))))
316
+ throw new BoundRefusal('host/act', 'REFUSE·Root host requires the admitted atomic result profile');
317
+ const started = Date.now(), batch = await signedBatch(root, [], [], host);
318
+ return finishAcceptance(root, [], batch, new Map(), started, host);
319
+ }
320
+ async function finishAcceptance(root, args, batch, placed, started, host) {
321
+ const named = namedPlaces(args);
294
322
  if (batch.kind === 'exit')
295
323
  return batch.code;
296
324
  if (batch.kind === 'once')
@@ -300,8 +328,8 @@ export async function accept(root, args) {
300
328
  const owned = ownedOf(root, batch.signed, (line) => authority.of(line).kind === 'admitted', (by) => authority.admitted.find((one) => one.id === by)?.coverage ?? [], named, (line) => placed.get(sayingOf(line)));
301
329
  if (owned.nowhere.length) {
302
330
  for (const line of owned.nowhere)
303
- process.stderr.write(`NOWHERE ${fieldOf(line, 'scope')} · ${((got) => (got.kind === 'admitted' ? 'admitted' : got.why))(authority.of(line))}, and ${named.length ? `no place of ${named.join(', ')} takes it` : 'no one own lock it covers takes it: name the places with --place'}\n`);
304
- process.stderr.write(`${selfName()}: REFUSE·nowhere nothing landed · ${owned.nowhere.length} lines neither the ledger nor an own lock admits: a line is landed signed where it stands, never written in bare\n`);
331
+ hostOutput('error', `NOWHERE ${fieldOf(line, 'scope')} · ${((got) => (got.kind === 'admitted' ? 'admitted' : got.why))(authority.of(line))}, and ${named.length ? `no place of ${named.join(', ')} takes it` : 'no one own lock it covers takes it: name the places with --place'}\n`);
332
+ hostOutput('error', `${selfName()}: REFUSE·nowhere nothing landed · ${owned.nowhere.length} lines neither the ledger nor an own lock admits: a line is landed signed where it stands, never written in bare\n`);
305
333
  return 1;
306
334
  }
307
335
  const places = placesIn(root);
@@ -310,18 +338,18 @@ export async function accept(root, args) {
310
338
  const every = pinned.length === 0 && touched.length === places.length && places.length > 0;
311
339
  const regions = (pinned.length ? pinned : every ? places : touched).filter((one) => places.includes(one));
312
340
  const where = every ? 'every place · then the root, where the places join' : regions.length ? regions.map((one) => `${one}/`).join(' · ') : 'the root alone, where the places join';
313
- process.stdout.write(`ACTS ${where} · ${owned.ledger.length} lines to the ledger${[...owned.own].map(([place, lines]) => ` · ${lines.length} to ${place}'s own lock`).join('')} · before the lock\n`);
341
+ hostOutput('out', `ACTS ${where} · ${owned.ledger.length} lines to the ledger${[...owned.own].map(([place, lines]) => ` · ${lines.length} to ${place}'s own lock`).join('')} · before the lock\n`);
314
342
  const asked = Date.now();
315
343
  // Root-ledger writes may change grants or inherited policies. Child-only
316
344
  // lots read that authority through publication, without excluding siblings.
317
345
  const rootWrite = owned.ledger.length > 0 || every || regions.length === 0;
318
346
  const enter = rootWrite ? underTheRegions : underAuthorityRegions;
319
- return enter(store, rootWrite ? [...regions, ''] : regions, 'land', () => acceptNow(root, { ...batch, signed: owned.ledger, records: batch.signed }, started, owned, asked, regions));
347
+ return enter(store, rootWrite ? [...regions, ''] : regions, 'land', () => acceptNow(root, { ...batch, signed: owned.ledger, records: batch.signed }, started, owned, asked, regions, host));
320
348
  }
321
- async function acceptNow(root, batch, started, owned, asked = started, descent = []) {
349
+ async function acceptNow(root, batch, started, owned, asked = started, descent = [], host) {
322
350
  const waited = Date.now() - asked;
323
351
  if (waited > 60000)
324
- process.stdout.write(`RED wait ${Math.round(waited / 1000)} s for the lock before the act · a wait over a minute is a red\n`);
352
+ hostOutput('out', `RED wait ${Math.round(waited / 1000)} s for the lock before the act · a wait over a minute is a red\n`);
325
353
  const spent = [`sign ${asked - started}`, `wait ${waited}`];
326
354
  const took = timing(spent);
327
355
  const { signed, epoch } = batch;
@@ -330,14 +358,14 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
330
358
  const store = storeOf(root);
331
359
  const currentBase = fullDigest(store, localActLines(root, batch.base.places));
332
360
  if (currentBase !== batch.base.digest) {
333
- process.stderr.write(`${selfName()}: REFUSE·base moved ${batch.base.digest} → ${currentBase} · nothing landed\n`);
361
+ hostOutput('error', `${selfName()}: REFUSE·base moved ${batch.base.digest} → ${currentBase} · nothing landed\n`);
334
362
  return 1;
335
363
  }
336
364
  try {
337
365
  assertProspectiveRootClockSelection(root, signed);
338
366
  }
339
367
  catch (error) {
340
- process.stderr.write(`${selfName()}: ${error instanceof Error ? error.message : String(error)} · nothing landed\n`);
368
+ hostOutput('error', `${selfName()}: ${error instanceof Error ? error.message : String(error)} · nothing landed\n`);
341
369
  return 1;
342
370
  }
343
371
  if (signed.some(line => !isConfig(line))) {
@@ -345,7 +373,7 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
345
373
  await admittedObjects(root, [...signedOwnerLines(store), ...signed]);
346
374
  }
347
375
  catch (e) {
348
- process.stderr.write(`${selfName()}: ${e instanceof Error ? e.message : String(e)} · nothing landed\n`);
376
+ hostOutput('error', `${selfName()}: ${e instanceof Error ? e.message : String(e)} · nothing landed\n`);
349
377
  return 1;
350
378
  }
351
379
  }
@@ -357,7 +385,7 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
357
385
  evidence = took('evidence', () => admissionEvidence(root, signed, snapshot.standing));
358
386
  }
359
387
  catch (error) {
360
- process.stderr.write(`${selfName()}: ${error instanceof Error ? error.message : String(error)} · nothing landed\n`);
388
+ hostOutput('error', `${selfName()}: ${error instanceof Error ? error.message : String(error)} · nothing landed\n`);
361
389
  return 1;
362
390
  }
363
391
  const fold = evidence.fold;
@@ -365,7 +393,7 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
365
393
  for (const line of signed) {
366
394
  const verdict = authority.of(line);
367
395
  if (verdict.kind !== 'admitted') {
368
- process.stderr.write(`${selfName()}: ${verdict.kind === 'refuse' ? verdict.why : `REFUSE·authority grey: ${verdict.why}`}\n`);
396
+ hostOutput('error', `${selfName()}: ${verdict.kind === 'refuse' ? verdict.why : `REFUSE·authority grey: ${verdict.why}`}\n`);
369
397
  return 1;
370
398
  }
371
399
  }
@@ -384,13 +412,13 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
384
412
  const refused = judged.filter((one) => one.kind === 'refused' || (one.kind === 'judged' && one.verdict.kind === 'refuse'));
385
413
  if (refused.length) {
386
414
  for (const one of refused)
387
- process.stdout.write(`REFUSE take ${one.scope} · ${one.kind === 'judged' ? one.verdict.why : one.kind === 'refused' ? one.why : ''}\n`);
388
- process.stderr.write(`${selfName()}: REFUSE·takes nothing landed · ${refused.length} takes refused: a batch lands whole or not at all\n`);
415
+ hostOutput('out', `REFUSE take ${one.scope} · ${one.kind === 'judged' ? one.verdict.why : one.kind === 'refused' ? one.why : ''}\n`);
416
+ hostOutput('error', `${selfName()}: REFUSE·takes nothing landed · ${refused.length} takes refused: a batch lands whole or not at all\n`);
389
417
  return 1;
390
418
  }
391
419
  const told = took('told', () => standingOf(root, undefined, signed));
392
420
  for (const name of openedBy(descent, told.standing, signed))
393
- process.stderr.write(`OPEN ${name}\n`);
421
+ hostOutput('error', `OPEN ${name}\n`);
394
422
  if (owned.own.size)
395
423
  layReleases(store, root, told.standing, store);
396
424
  const resultProfile = actResultProfile(snapshot.standing);
@@ -405,13 +433,17 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
405
433
  throw Error('REFUSE·act native result requires a declared fold identity');
406
434
  const receipt = canonical({ scope: resultProfile.scope, at: resultProfile.context, role: 'writes', form: 'alphabet', measure: 'digest', value: identity, by: folder, epoch: String(epoch) });
407
435
  const bundle = { records: records.join('\n') + '\n', receipt: receipt + '\n', placements: placements.map(p => ({ place: p.place, records: p.records.join('\n') + '\n' })) };
408
- await commitLocalAct(store, identity, descent, async () => bundle, intactLocalAct);
436
+ notCancelled(host?.signal);
437
+ host?.prepared?.(identity);
438
+ notCancelled(host?.signal);
439
+ const committed = await commitLocalAct(store, identity, descent, async () => bundle, intactLocalAct, host?.storage, host?.signal);
440
+ host?.committed?.(identity, committed, committed.kind);
409
441
  // Commitment precedes projections and conformance work. If either fails,
410
442
  // this public identity still names the admitted act; it is never rollback.
411
- process.stdout.write(`ACT ${identity} · epoch ${epoch} · admitted · conformance not asserted\n`);
443
+ hostOutput('out', `ACT ${identity} · epoch ${epoch} · admitted · conformance not asserted\n`);
412
444
  for (const line of records)
413
- process.stdout.write(line + '\n');
414
- process.stdout.write(receipt + '\n');
445
+ hostOutput('out', line + '\n');
446
+ hostOutput('out', receipt + '\n');
415
447
  landed = [{ at: 'committed act ' + identity, appended: records.length }];
416
448
  }
417
449
  else
@@ -442,7 +474,7 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
442
474
  }
443
475
  landCoordinates(store, new Map(), coordinates.taken, `withdraw:epoch-${epoch}`);
444
476
  for (const path of coordinates.taken)
445
- process.stdout.write(`GONE ${path} · withdrawn by its line\n`);
477
+ hostOutput('out', `GONE ${path} · withdrawn by its line\n`);
446
478
  }
447
479
  for (const one of judged)
448
480
  landTake(root, one, []);
@@ -453,31 +485,31 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
453
485
  land(store, 'judge', late);
454
486
  for (const line of late)
455
487
  if (fieldOf(line, 'measure') !== 'cone')
456
- process.stdout.write(`LATE ${fieldOf(line, 'scope')} · answered from the landed cone\n`);
488
+ hostOutput('out', `LATE ${fieldOf(line, 'scope')} · answered from the landed cone\n`);
457
489
  }
458
490
  if (signed.some((line) => fieldOf(line, 'scope').startsWith(`${basename(ownRoot())}/`) && !fieldOf(line, 'scope').split('/').slice(1, -1).includes('uses'))) {
459
491
  const code = instrumentOf(store, entryOf(root), told.standing, root, ownLock());
460
492
  if (instrumentKept(store) !== code) {
461
493
  const open = await closeReceipts(root, store);
462
494
  instrumentKeep(store, code);
463
- process.stderr.write(`open by instrument · ${open.length}\n`);
495
+ hostOutput('error', `open by instrument · ${open.length}\n`);
464
496
  }
465
497
  }
466
498
  const rendered = took('render', () => renderInto(root, store));
467
- process.stdout.write(`RENDER 0\n`);
499
+ hostOutput('out', `RENDER 0\n`);
468
500
  const own = new Set([...owned.own.values()].flat()).size;
469
501
  const perLine = Math.round((Date.now() - started) / Math.max(1, signed.length + own));
470
502
  const folder = writerFor(fold.standing, 'fold');
471
503
  const beat = (ms, at) => canonical({ scope: 'beat/accept', role: 'writes', form: 'interval', measure: 'ms', value: `${ms}..${ms}`, by: folder ?? '', at: `place:${at}` });
472
504
  if (folder !== undefined)
473
505
  land(store, folder, [beat(perLine, 'line'), beat(1, fold.kept ? 'kept' : 'folded'), beat(Date.now() - started, 'total')]);
474
- process.stdout.write(`FACT signed ${signed.length + own} · landed ${landed.length ? landed.map((one) => `${one.appended} in ${one.at}`).join(' · ') : '0'}${[...owned.own].map(([place, lines]) => ` · ${lines.length} in ${place}/${LOCK}`).join('')} · epoch ${epoch}\n`
506
+ hostOutput('out', `FACT signed ${signed.length + own} · landed ${landed.length ? landed.map((one) => `${one.appended} in ${one.at}`).join(' · ') : '0'}${[...owned.own].map(([place, lines]) => ` · ${lines.length} in ${place}/${LOCK}`).join('')} · epoch ${epoch}\n`
475
507
  + `TARGET ${rendered.standing} claims · ${rendered.forks.length} forks · ${rendered.vacuous.length} vacuous · ${rendered.grey.length} grey · ${rendered.refused.length} refused · readings · no encounters computed · ${Date.now() - started} ms\n`
476
508
  + `SPENT ${spent.join(' ms · ')} ms · ${signed.length} lines\n`);
477
509
  if (withdrawn) {
478
- process.stdout.write(`WITHDRAW ${withdrawn.taken.length} taken · ${withdrawn.exact.length} exact on distributive scales · ${withdrawn.open.length} on open scales, not computed\n`);
510
+ hostOutput('out', `WITHDRAW ${withdrawn.taken.length} taken · ${withdrawn.exact.length} exact on distributive scales · ${withdrawn.open.length} on open scales, not computed\n`);
479
511
  for (const line of withdrawn.inexact)
480
- process.stdout.write(`INEXACT ${fieldOf(line, 'scope')} · its scale is not distributive: what the others hold is not what they held without it\n`);
512
+ hostOutput('out', `INEXACT ${fieldOf(line, 'scope')} · its scale is not distributive: what the others hold is not what they held without it\n`);
481
513
  }
482
514
  return 0;
483
515
  });
@@ -490,18 +522,18 @@ function repaid(root, proposed, started) {
490
522
  const store = storeOf(root);
491
523
  const prior = committedLocalActs(store).find(act => { const lines = act.bundle.records.trimEnd().split('\n'); return lines.length === proposed.length && lines.every((line, i) => line === proposed[i]); });
492
524
  if (prior) {
493
- process.stdout.write(`ACT ${prior.identity} · epoch ${fieldOf(prior.bundle.receipt.trim(), 'epoch')} · admitted · replay\n` + prior.bundle.records + prior.bundle.receipt);
525
+ hostOutput('out', `ACT ${prior.identity} · epoch ${fieldOf(prior.bundle.receipt.trim(), 'epoch')} · admitted · replay\n` + prior.bundle.records + prior.bundle.receipt);
494
526
  }
495
527
  const asked = new Set(proposed.filter((line) => fieldOf(line, 'role') === 'demands').map((line) => fieldOf(line, 'scope')));
496
528
  const view = `${wordOf(foldClaims(signedOwnerLines(store)).standing, 'families', 'view')}/`;
497
529
  const standingAsked = signedDemands(store).filter((line) => asked.has(fieldOf(line, 'scope')) && !fieldOf(line, 'scope').startsWith(view));
498
530
  const owed = paidOf(store, standingAsked, [], view).missing;
499
531
  if (!owed.length) {
500
- process.stdout.write(`ONCE ${proposed.length} lines · every one already landed and every demand of it paid: a batch landed twice is one, nothing landed\n`);
532
+ hostOutput('out', `ONCE ${proposed.length} lines · every one already landed and every demand of it paid: a batch landed twice is one, nothing landed\n`);
501
533
  return 0;
502
534
  }
503
535
  const judged = owed.map((line) => judgeDemand(root, line));
504
- process.stdout.write(`ONCE ${proposed.length} lines · every one already landed · ${owed.length} of its demands unpaid, judged again\n`);
536
+ hostOutput('out', `ONCE ${proposed.length} lines · every one already landed · ${owed.length} of its demands unpaid, judged again\n`);
505
537
  if (judged.every((one) => one.kind === 'owed')) {
506
538
  for (const one of judged)
507
539
  landTake(root, one, []);
@@ -510,8 +542,8 @@ function repaid(root, proposed, started) {
510
542
  const refused = judged.filter((one) => one.kind === 'refused' || (one.kind === 'judged' && one.verdict.kind === 'refuse'));
511
543
  if (refused.length) {
512
544
  for (const one of refused)
513
- process.stdout.write(`REFUSE take ${one.scope} · ${one.kind === 'judged' ? one.verdict.why : one.kind === 'refused' ? one.why : ''}\n`);
514
- process.stderr.write(`${selfName()}: REFUSE·takes nothing landed · ${refused.length} takes refused: what a batch left unpaid is paid whole or not at all\n`);
545
+ hostOutput('out', `REFUSE take ${one.scope} · ${one.kind === 'judged' ? one.verdict.why : one.kind === 'refused' ? one.why : ''}\n`);
546
+ hostOutput('error', `${selfName()}: REFUSE·takes nothing landed · ${refused.length} takes refused: what a batch left unpaid is paid whole or not at all\n`);
515
547
  return 1;
516
548
  }
517
549
  const standing = standingOf(root);
@@ -525,11 +557,11 @@ function repaid(root, proposed, started) {
525
557
  land(store, 'judge', late);
526
558
  for (const line of late)
527
559
  if (fieldOf(line, 'measure') !== 'cone')
528
- process.stdout.write(`LATE ${fieldOf(line, 'scope')} · answered from the landed cone\n`);
560
+ hostOutput('out', `LATE ${fieldOf(line, 'scope')} · answered from the landed cone\n`);
529
561
  }
530
562
  const rendered = renderInto(root, store);
531
- process.stdout.write(`RENDER 0\n`);
532
- process.stdout.write(`TARGET ${rendered.standing} claims · ${rendered.forks.length} forks · ${rendered.vacuous.length} vacuous · ${rendered.grey.length} grey · ${rendered.refused.length} refused · readings · no encounters computed · ${Date.now() - started} ms\n`);
563
+ hostOutput('out', `RENDER 0\n`);
564
+ hostOutput('out', `TARGET ${rendered.standing} claims · ${rendered.forks.length} forks · ${rendered.vacuous.length} vacuous · ${rendered.grey.length} grey · ${rendered.refused.length} refused · readings · no encounters computed · ${Date.now() - started} ms\n`);
533
565
  return 0;
534
566
  }
535
567
  function packTake(one) {
package/dist/cli/place.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { hostOutput } from "../host/call-context.js";
1
2
  import { refreshRetainedJudgment } from "./retained-judgment.js";
2
3
  import { timing } from "../host/timing.js";
3
4
  import { attestationObservations } from "../fold/evidence.js";
@@ -193,7 +194,7 @@ export function foldPlace(root, entry, under, look = true, given) {
193
194
  if (closed && given?.free !== true) {
194
195
  const prior = keptPlace(store, under, instrumentOf(store, entry, standingOf(root).standing, root, ownLock()));
195
196
  if (prior !== undefined) {
196
- process.stderr.write(`CLOSED ${under || '.'} · digest ${digestMs} ms\n`);
197
+ hostOutput('error', `CLOSED ${under || '.'} · digest ${digestMs} ms\n`);
197
198
  return refreshRetainedJudgment(prior, [...prior.standing.filter((line) => isCeiling(line) && !(under && fieldOf(line, 'measure') === 'unread')), ...ownCeilings(under)]);
198
199
  }
199
200
  }
@@ -276,6 +277,6 @@ export function foldPlace(root, entry, under, look = true, given) {
276
277
  };
277
278
  if (folder !== undefined && meets(root, store, under ?? '', true, whole.observed))
278
279
  took('keep', () => keepFold(store, taken, whole, points, self));
279
- process.stderr.write(`SPENT fold ${spent.join(' ms · ')} ms\n`);
280
+ hostOutput('error', `SPENT fold ${spent.join(' ms · ')} ms\n`);
280
281
  return whole;
281
282
  }
@@ -1,7 +1,6 @@
1
- import { BoundRefusal } from "../host/refusals.js";
2
1
  import { hasContextualPlace, contextualStanding } from "../host/contextual-place.js";
3
2
  import { publicStandingLines } from "../host/admitted-place.js";
4
- import { viewsOf } from "../fold/views.js";
3
+ import { assertStandingView } from "../host/standing-projection.js";
5
4
  import { lockStanding } from "../fold/keys.js";
6
5
  import { storeOf } from "../land/ledger.js";
7
6
  /** An evidence projection, never a new authority lock. Authentication uses
@@ -9,9 +8,8 @@ import { storeOf } from "../land/ledger.js";
9
8
  * supersessions and withdrawals across all declared classes. Storage layout
10
9
  * and delivery mode do not enter this public boundary. */
11
10
  export function printStandingResult(root) {
12
- const store = storeOf(root), view = viewsOf(lockStanding(store)).get('standing');
13
- if (!view || view.shape || !view.regions.some(region => region.name === 'standing'))
14
- throw new BoundRefusal('standing/view', 'REFUSE·view standing requires a declared standing view');
11
+ const store = storeOf(root);
12
+ assertStandingView(lockStanding(store));
15
13
  if (hasContextualPlace(root)) {
16
14
  process.stdout.write(JSON.stringify(contextualStanding(root)) + '\n');
17
15
  return;
package/dist/cli/takes.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { hostOutput } from "../host/call-context.js";
1
2
  import { existsSync } from 'node:fs';
2
3
  import { join } from 'node:path';
3
4
  import { fullDigest } from "../fold/digests.js";
@@ -62,7 +63,7 @@ export function landTake(root, one, gates) {
62
63
  if (one.kind === 'refused')
63
64
  return;
64
65
  if (one.kind === 'owed') {
65
- process.stdout.write(`OWED take ${one.scope} · ${one.why}\n`);
66
+ hostOutput('out', `OWED take ${one.scope} · ${one.why}\n`);
66
67
  return;
67
68
  }
68
69
  if (one.kind === 'conditioned') {
@@ -71,7 +72,7 @@ export function landTake(root, one, gates) {
71
72
  scope, role: 'writes', form: 'alphabet', measure: fieldOf(demand, 'measure') || 'status', value: fieldOf(demand, 'value') || 'present', by: 'judge', at: receipt, condition,
72
73
  })]);
73
74
  land(store, 'take', [canonical({ scope: `take/${scope}`, role: 'writes', form: 'alphabet', measure: 'status', value: 'fact', by: 'judge', at: receipt })]);
74
- process.stdout.write(`FACT take ${scope} · paid by ${receipt} · condition ${condition} · no moved set\n`);
75
+ hostOutput('out', `FACT take ${scope} · paid by ${receipt} · condition ${condition} · no moved set\n`);
75
76
  return;
76
77
  }
77
78
  const { scope, demand, before, after, ignored, started } = one;
@@ -102,7 +103,7 @@ export function landTake(root, one, gates) {
102
103
  canonical({ scope: `take/${scope}`, role: 'writes', form: 'interval', measure: 'ms', value: `${ms}..${ms}`, by: 'judge', at: receipt }),
103
104
  ]);
104
105
  const effects = verdict.envelope.effects.map((e) => `${e.kind}@${e.at || '.'}:${e.admitted ? e.status : `${e.status}·grey`}(${e.ms} ms)`).join(' ') || 'none';
105
- process.stdout.write(`${verdict.kind === 'fact' ? 'FACT ' : 'REFUSE'} take ${scope} · ${verdict.why}\n`
106
+ hostOutput('out', `${verdict.kind === 'fact' ? 'FACT ' : 'REFUSE'} take ${scope} · ${verdict.why}\n`
106
107
  + `MOVED ${verdict.changed.length} sources: ${verdict.changed.join(' ')}\n`
107
108
  + `IGNORED ${[...ignored].map(([role, n]) => `${n} ${role}`).join(' · ') || 'none'} (not walked, never landed)\n`
108
109
  + `${verdict.notOwned.length ? `NOT OWN ${verdict.notOwned.length} witnesses of other laws, left for them: ${verdict.notOwned.join(' ')}\n` : ''}`
package/dist/cli/verb.js CHANGED
@@ -49,7 +49,7 @@ import { placesIn } from "../fold/places.js";
49
49
  import { isCeiling } from "../fold/configures.js";
50
50
  import { readingsIn } from "../land/inert.js";
51
51
  import { pass } from "./pass.js";
52
- import { underTheLock, underTheRegions, underRootAuthority } from "../land/act.js";
52
+ import { installActSignalHandler, underTheLock, underTheRegions, underRootAuthority } from "../land/act.js";
53
53
  import { ownerOf, renderInto, signedDemands, targetFile } from "./owner.js";
54
54
  import { claimLinesIn, entryOf, namedPlaces, positionalIndexes, positionalsOf, valueOf } from "./args.js";
55
55
  import { EXIT, FLAGS, NAMED_VIEWS, helpLines } from "./names.js";
@@ -672,6 +672,7 @@ const refused = (error) => {
672
672
  printRefusal(error instanceof BoundRefusal ? error : new BoundRefusal('operation/failed', message));
673
673
  process.exitCode = EXIT.refuse;
674
674
  };
675
+ installActSignalHandler();
675
676
  try {
676
677
  const ran = main(process.argv.slice(2));
677
678
  if (ran instanceof Promise)
@@ -28,5 +28,10 @@ export declare function retainContent(digest: string, bytes: Uint8Array, ports:
28
28
  readonly digest: string;
29
29
  readonly bytes: number;
30
30
  }>;
31
- /** Acquire protocol-shaped signatures only; the existing consent path verifies them and decides authority. */
31
+ /** Acquire canonical line signatures only. The line protocol is not widened
32
+ * for contextual receipt frames. Both operations share bounds and response checks. */
32
33
  export declare function invokeSigningPort(lot: SigningLot, sign: SigningPort, limits: PortLimits, signal?: AbortSignal): Promise<readonly SignatureRecord[]>;
34
+ /** Host-prepared signature frames. This acquires no authority: the contextual
35
+ * transaction validates the frame's domain/receiver and verifies every signature.
36
+ * A carrier cannot select this operation or supply a signature verifier. */
37
+ export declare function invokeFrameSigningPort(lot: SigningLot, sign: SigningPort, limits: PortLimits, signal?: AbortSignal): Promise<readonly SignatureRecord[]>;
@@ -57,10 +57,29 @@ export async function retainContent(digest, bytes, ports, limits, signal) {
57
57
  fail('cancelled'); const result = await ports.retain(digest, copy, s); if (result !== undefined)
58
58
  fail('response'); return Object.freeze({ digest, bytes: copy.byteLength }); });
59
59
  }
60
- /** Acquire protocol-shaped signatures only; the existing consent path verifies them and decides authority. */
60
+ /** Acquire canonical line signatures only. The line protocol is not widened
61
+ * for contextual receipt frames. Both operations share bounds and response checks. */
61
62
  export async function invokeSigningPort(lot, sign, limits, signal) {
63
+ validateSigningLot(lot, limits);
64
+ let request;
65
+ try {
66
+ request = signerRequestBytes(lot);
67
+ }
68
+ catch {
69
+ return fail('response');
70
+ }
71
+ return invokeDeclaredSignatures(lot, request, sign, limits, signal);
72
+ }
73
+ /** Host-prepared signature frames. This acquires no authority: the contextual
74
+ * transaction validates the frame's domain/receiver and verifies every signature.
75
+ * A carrier cannot select this operation or supply a signature verifier. */
76
+ export async function invokeFrameSigningPort(lot, sign, limits, signal) {
77
+ validateSigningLot(lot, limits);
78
+ return invokeDeclaredSignatures(lot, JSON.stringify(lot) + '\n', sign, limits, signal);
79
+ }
80
+ function validateSigningLot(lot, limits) {
62
81
  limitsOf(limits);
63
- if (!lot || !Array.isArray(lot.bytes))
82
+ if (!lot || !Array.isArray(lot.bytes) || !lot.bytes.length || typeof lot.keyId !== 'string' || !lot.keyId || typeof lot.algorithm !== 'string' || !lot.algorithm)
64
83
  fail('budget');
65
84
  let size = 0;
66
85
  for (const bytes of lot.bytes) {
@@ -70,13 +89,8 @@ export async function invokeSigningPort(lot, sign, limits, signal) {
70
89
  if (size > limits.maxBytes)
71
90
  fail('budget');
72
91
  }
73
- let request;
74
- try {
75
- request = signerRequestBytes(lot);
76
- }
77
- catch {
78
- return fail('response');
79
- }
92
+ }
93
+ function invokeDeclaredSignatures(lot, request, sign, limits, signal) {
80
94
  if (encoded(request).length > limits.maxBytes)
81
95
  fail('budget');
82
96
  const input = Object.freeze({ ...lot, bytes: Object.freeze([...lot.bytes]) });