@astrosheep/square 0.3.94 → 0.3.95

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 (85) hide show
  1. package/dist/artifact.js +1 -1
  2. package/dist/attention.d.ts +19 -0
  3. package/dist/attention.js +30 -0
  4. package/dist/boundary-presentation.js +33 -40
  5. package/dist/catch-decisions.d.ts +1 -2
  6. package/dist/catch-decisions.js +41 -21
  7. package/dist/cli/context.js +0 -2
  8. package/dist/cli/express-presentation.js +3 -3
  9. package/dist/cli/json.js +1 -1
  10. package/dist/cli/program.js +1 -1
  11. package/dist/cli/registry.js +1 -3
  12. package/dist/cli/square-commands.d.ts +0 -2
  13. package/dist/cli/square-commands.js +21 -27
  14. package/dist/decisions.d.ts +0 -5
  15. package/dist/decisions.js +0 -13
  16. package/dist/delivery-attempts.d.ts +52 -0
  17. package/dist/delivery-attempts.js +73 -0
  18. package/dist/delivery-effects.d.ts +139 -0
  19. package/dist/delivery-effects.js +163 -0
  20. package/dist/delivery-operations.js +43 -68
  21. package/dist/delivery-primitives.d.ts +5 -0
  22. package/dist/delivery-primitives.js +3 -0
  23. package/dist/delivery.d.ts +1 -1
  24. package/dist/delivery.js +6 -5
  25. package/dist/help.js +4 -6
  26. package/dist/http-delivery.d.ts +16 -0
  27. package/dist/http-delivery.js +101 -0
  28. package/dist/model.d.ts +4 -6
  29. package/dist/model.js +1 -1
  30. package/dist/notifications.js +2 -0
  31. package/dist/operation-presentation.d.ts +1 -2
  32. package/dist/operation-presentation.js +12 -20
  33. package/dist/ports.d.ts +5 -14
  34. package/dist/presentation-operations.d.ts +14 -19
  35. package/dist/presentation-operations.js +47 -57
  36. package/dist/presentation.d.ts +2 -9
  37. package/dist/presentation.js +13 -48
  38. package/dist/receive.d.ts +66 -0
  39. package/dist/receive.js +54 -0
  40. package/dist/runtime.d.ts +0 -1
  41. package/dist/runtime.js +2 -3
  42. package/dist/sqlite-square.d.ts +0 -1
  43. package/dist/sqlite-square.js +29 -55
  44. package/dist/square-api.d.ts +1 -1
  45. package/dist/square-api.js +1 -1
  46. package/dist/square-core.d.ts +0 -9
  47. package/dist/square-domain.js +9 -31
  48. package/dist/square-facade.d.ts +5 -21
  49. package/dist/square-legacy-framing.d.ts +2 -0
  50. package/dist/square-legacy-framing.js +8 -0
  51. package/dist/square-migration.d.ts +3 -1
  52. package/dist/square-migration.js +46 -13
  53. package/dist/square-receive.d.ts +6 -0
  54. package/dist/square-receive.js +18 -0
  55. package/dist/square-sqlite.d.ts +5 -3
  56. package/dist/square-sqlite.js +89 -90
  57. package/dist/square-tools.js +3 -11
  58. package/dist/views.d.ts +2 -3
  59. package/dist/views.js +13 -19
  60. package/dist/wakes.d.ts +0 -3
  61. package/dist/wakes.js +3 -5
  62. package/examples/symmetric-domains.mjs +250 -0
  63. package/guides/application-facade-delivery-evidence.md +5 -5
  64. package/guides/attention-primitive.md +32 -0
  65. package/guides/change-waits.md +2 -2
  66. package/guides/codex-delivery.md +1 -1
  67. package/guides/delivery-primitives.md +180 -0
  68. package/guides/mcp.md +1 -1
  69. package/guides/migration.md +12 -6
  70. package/guides/participant-ownership.md +1 -1
  71. package/guides/pi-tools.md +2 -2
  72. package/guides/primitive-catch-consolidation.md +5 -5
  73. package/guides/primitive-history-consolidation.md +2 -2
  74. package/guides/primitive-status-consolidation.md +3 -3
  75. package/guides/primitive-storage-consolidation.md +1 -1
  76. package/guides/receive-primitive.md +90 -0
  77. package/guides/row-runtime-delivery-evidence.md +1 -1
  78. package/guides/row-storage-design.md +17 -23
  79. package/guides/square-api.md +77 -10
  80. package/guides/symmetric-domains.md +92 -0
  81. package/package.json +14 -1
  82. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  83. package/plugins/claude/skills/square/SKILL.md +2 -6
  84. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  85. package/skills/square/SKILL.md +2 -6
@@ -0,0 +1,139 @@
1
+ export interface EffectClaim {
2
+ readonly token: string;
3
+ readonly ordinal: number;
4
+ }
5
+ /** Bound to one captured item/recipient/authority/purpose, not an encoded series key. */
6
+ export interface EffectPort<C> {
7
+ /** The caller's existing atomic claim operation. Undefined means no custody acquired. */
8
+ claim(signal?: AbortSignal): Promise<EffectClaim | undefined>;
9
+ /** Pre-effect policy only. Rejection means unverifiable/not current. */
10
+ current(signal?: AbortSignal): Promise<boolean>;
11
+ /**
12
+ * No request signal: persistence has its own bounded lifetime. For presentation,
13
+ * `presented` MUST complete the attempt and record ALL captured full-consumption
14
+ * receipts in ONE atomic transaction (rollback both on error). `clipped`, `failed`,
15
+ * `unknown` and `released` complete the attempt without full-consumption receipts.
16
+ * No post-effect currentness/deferral/cancellation veto; captured authority still fences.
17
+ */
18
+ settle(claim: EffectClaim, completion: C): Promise<boolean>;
19
+ }
20
+ export type Persistence = {
21
+ readonly status: 'recorded';
22
+ } | {
23
+ readonly status: 'not-recorded';
24
+ } | {
25
+ readonly status: 'error';
26
+ readonly error: unknown;
27
+ };
28
+ export interface Settlement<C> {
29
+ readonly completion: C;
30
+ readonly persistence: Persistence;
31
+ }
32
+ export interface DispatchOutcome {
33
+ readonly outcome: 'accepted' | 'failed' | 'unknown' | 'not-capable';
34
+ readonly data?: unknown;
35
+ }
36
+ export type DispatchCompletion = {
37
+ readonly outcome: 'accepted' | 'failed' | 'unknown';
38
+ readonly reason: 'returned';
39
+ readonly data?: unknown;
40
+ } | {
41
+ readonly outcome: 'unknown';
42
+ readonly reason: 'threw';
43
+ readonly error: unknown;
44
+ } | {
45
+ readonly outcome: 'unknown';
46
+ readonly reason: 'timeout';
47
+ } | {
48
+ readonly outcome: 'released';
49
+ readonly reason: 'not-current';
50
+ readonly phase: 'initial' | 'final';
51
+ } | {
52
+ readonly outcome: 'released';
53
+ readonly reason: 'not-capable';
54
+ readonly data?: unknown;
55
+ } | {
56
+ readonly outcome: 'released';
57
+ readonly reason: 'cancelled';
58
+ };
59
+ export interface DispatchContext {
60
+ readonly claim: EffectClaim;
61
+ readonly timeoutMs: number;
62
+ readonly deadline: number;
63
+ readonly signal?: AbortSignal;
64
+ /** Policy only. Sender MUST recheck deadline/cancellation after this await before I/O. */
65
+ beforeSend(): Promise<boolean>;
66
+ }
67
+ export interface DispatchReport {
68
+ readonly observation: {
69
+ readonly kind: 'not-invoked';
70
+ } | {
71
+ readonly kind: 'returned';
72
+ readonly result: DispatchOutcome;
73
+ } | {
74
+ readonly kind: 'threw';
75
+ readonly error: unknown;
76
+ } | {
77
+ readonly kind: 'timeout';
78
+ };
79
+ readonly settlement?: Settlement<DispatchCompletion>;
80
+ }
81
+ /** Timer bounds the external attempt only, not claim/currentness/persistence. No automatic retry. */
82
+ export declare function dispatchEffect(port: EffectPort<DispatchCompletion>, send: (context: DispatchContext) => Promise<DispatchOutcome>, options: {
83
+ readonly timeoutMs: number;
84
+ readonly signal?: AbortSignal;
85
+ }): Promise<DispatchReport>;
86
+ export type PresentationCompletion = {
87
+ readonly outcome: 'presented' | 'clipped';
88
+ readonly reason: 'returned';
89
+ } | {
90
+ readonly outcome: 'failed' | 'unknown';
91
+ readonly reason: 'threw';
92
+ readonly error: unknown;
93
+ } | {
94
+ readonly outcome: 'released';
95
+ readonly reason: 'not-current' | 'cancelled' | 'unused';
96
+ };
97
+ export interface PresentationWork<P> {
98
+ readonly payload: P;
99
+ readonly coverage: 'full' | 'clipped';
100
+ readonly port: EffectPort<PresentationCompletion>;
101
+ }
102
+ declare const preparedBrand: unique symbol;
103
+ export interface PreparedPresentation<P> {
104
+ readonly [preparedBrand]: true;
105
+ readonly payload: P;
106
+ eligible(signal?: AbortSignal): Promise<boolean>;
107
+ release(): Promise<Settlement<PresentationCompletion>>;
108
+ }
109
+ export interface PresentationControl {
110
+ readonly signal?: AbortSignal;
111
+ /** Default unknown; throwing or invalid classifiers also stay unknown. */
112
+ readonly classifyError?: (error: unknown) => 'failed' | 'unknown';
113
+ }
114
+ export interface PresentationReport<P, R> {
115
+ readonly observation: {
116
+ readonly kind: 'not-invoked';
117
+ } | {
118
+ readonly kind: 'returned';
119
+ readonly value: R;
120
+ } | {
121
+ readonly kind: 'threw';
122
+ readonly error: unknown;
123
+ readonly outcome: 'failed' | 'unknown';
124
+ };
125
+ readonly settlements: readonly {
126
+ readonly prepared: PreparedPresentation<P>;
127
+ readonly result: Settlement<PresentationCompletion>;
128
+ }[];
129
+ }
130
+ export declare function preparePresentation<P>(work: PresentationWork<P>, control?: {
131
+ readonly signal?: AbortSignal;
132
+ }): Promise<PreparedPresentation<P> | undefined>;
133
+ export declare function presentPrepared<P, R>(prepared: PreparedPresentation<P>, sink: (payload: P) => R | Promise<R>, control?: PresentationControl): Promise<PresentationReport<P, R>>;
134
+ /** Takes all local custody synchronously, then owns eligibility/effect/settlement/cleanup. */
135
+ export declare function presentPreparedBatch<P, B, R>(prepared: readonly PreparedPresentation<P>[], render: (eligible: readonly PreparedPresentation<P>[]) => {
136
+ readonly payload: B;
137
+ readonly included: readonly PreparedPresentation<P>[];
138
+ }, sink: (payload: B) => R | Promise<R>, control?: PresentationControl): Promise<PresentationReport<P, R>>;
139
+ export {};
@@ -0,0 +1,163 @@
1
+ async function settle(port, claim, completion) {
2
+ try {
3
+ return { completion, persistence: { status: await port.settle(claim, completion) ? 'recorded' : 'not-recorded' } };
4
+ }
5
+ catch (error) {
6
+ return { completion, persistence: { status: 'error', error } };
7
+ }
8
+ }
9
+ async function current(port, signal) {
10
+ try {
11
+ return await port.current(signal);
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ /** Timer bounds the external attempt only, not claim/currentness/persistence. No automatic retry. */
18
+ export async function dispatchEffect(port, send, options) {
19
+ const notInvoked = { kind: 'not-invoked' };
20
+ if (options.signal?.aborted)
21
+ return { observation: notInvoked };
22
+ const claim = await port.claim(options.signal);
23
+ if (claim === undefined)
24
+ return { observation: notInvoked };
25
+ if (!await current(port, options.signal))
26
+ return { observation: notInvoked,
27
+ settlement: await settle(port, claim, { outcome: 'released', reason: 'not-current', phase: 'initial' }) };
28
+ if (options.signal?.aborted)
29
+ return { observation: notInvoked,
30
+ settlement: await settle(port, claim, { outcome: 'released', reason: 'cancelled' }) };
31
+ let denied = false;
32
+ let timer;
33
+ const deadline = Date.now() + options.timeoutMs;
34
+ const timeout = new Promise(resolve => {
35
+ timer = setTimeout(() => resolve({ kind: 'timeout' }), options.timeoutMs);
36
+ });
37
+ let observation;
38
+ let completion;
39
+ try {
40
+ observation = await Promise.race([timeout, (async () => {
41
+ try {
42
+ return { kind: 'returned', result: await send({ claim, deadline, ...options,
43
+ beforeSend: async () => {
44
+ const allowed = await current(port, options.signal);
45
+ if (!allowed)
46
+ denied = true;
47
+ return allowed;
48
+ },
49
+ }) };
50
+ }
51
+ catch (error) {
52
+ return { kind: 'threw', error };
53
+ }
54
+ })()]);
55
+ // Freeze before persistence. Late policy results/sender completions cannot rewrite it.
56
+ completion = observation.kind === 'timeout' ? { outcome: 'unknown', reason: 'timeout' }
57
+ : denied ? { outcome: 'released', reason: 'not-current', phase: 'final' }
58
+ : observation.kind === 'threw' ? { outcome: 'unknown', reason: 'threw', error: observation.error }
59
+ : observation.result.outcome === 'not-capable'
60
+ ? { outcome: 'released', reason: 'not-capable', data: observation.result.data }
61
+ : { outcome: observation.result.outcome, reason: 'returned', data: observation.result.data };
62
+ }
63
+ finally {
64
+ if (timer !== undefined)
65
+ clearTimeout(timer);
66
+ }
67
+ return { observation, settlement: await settle(port, claim, completion) };
68
+ }
69
+ const preparedBrand = Symbol('prepared presentation');
70
+ const custody = new WeakMap();
71
+ export async function preparePresentation(work, control) {
72
+ if (control?.signal?.aborted)
73
+ return undefined;
74
+ const claim = await work.port.claim(control?.signal);
75
+ if (claim === undefined)
76
+ return undefined;
77
+ let resolve;
78
+ let decided = false;
79
+ const state = {
80
+ owned: false, coverage: work.coverage,
81
+ eligible: signal => signal?.aborted ? Promise.resolve(false) : current(work.port, signal),
82
+ done: new Promise(res => { resolve = res; }),
83
+ finish(completion) {
84
+ if (decided)
85
+ return;
86
+ decided = true;
87
+ // Defer callbacks: every included handle is marked before any settlement starts.
88
+ void Promise.resolve().then(() => settle(work.port, claim, completion)).then(resolve);
89
+ },
90
+ };
91
+ const prepared = Object.freeze({
92
+ [preparedBrand]: true,
93
+ payload: work.payload,
94
+ eligible: (signal) => state.owned ? Promise.resolve(false) : state.eligible(signal),
95
+ release() {
96
+ if (!state.owned) {
97
+ state.owned = true;
98
+ state.finish({ outcome: 'released', reason: 'unused' });
99
+ }
100
+ return state.done;
101
+ },
102
+ });
103
+ custody.set(prepared, state);
104
+ return prepared;
105
+ }
106
+ export function presentPrepared(prepared, sink, control) {
107
+ return presentPreparedBatch([prepared], eligible => ({ payload: prepared.payload, included: eligible }), sink, control);
108
+ }
109
+ /** Takes all local custody synchronously, then owns eligibility/effect/settlement/cleanup. */
110
+ export async function presentPreparedBatch(prepared, render, sink, control = {}) {
111
+ const handles = [...prepared];
112
+ const states = handles.map(handle => custody.get(handle));
113
+ if (new Set(handles).size !== handles.length || states.some(state => state === undefined || state.owned)) {
114
+ await Promise.all([...new Set(handles)].filter(handle => custody.has(handle) && !custody.get(handle).owned).map(handle => handle.release()));
115
+ throw new TypeError('Presentation requires unique, unused prepared custody.');
116
+ }
117
+ const owned = states;
118
+ for (const state of owned)
119
+ state.owned = true;
120
+ let observation = { kind: 'not-invoked' };
121
+ try {
122
+ const eligible = [];
123
+ for (const [index, state] of owned.entries()) {
124
+ if (await state.eligible(control.signal))
125
+ eligible.push(handles[index]);
126
+ else
127
+ state.finish({ outcome: 'released', reason: control.signal?.aborted ? 'cancelled' : 'not-current' });
128
+ }
129
+ const rendered = render(Object.freeze(eligible));
130
+ const included = [...rendered.included];
131
+ if (new Set(included).size !== included.length || included.some(handle => !eligible.includes(handle)))
132
+ throw new TypeError('Included presentations must be a unique subset of eligible custody.');
133
+ if (included.length > 0 && !control.signal?.aborted) {
134
+ let completion;
135
+ try {
136
+ const value = await sink(rendered.payload);
137
+ observation = { kind: 'returned', value };
138
+ completion = { outcome: 'presented', reason: 'returned' };
139
+ }
140
+ catch (error) {
141
+ let outcome = 'unknown';
142
+ try {
143
+ if (control.classifyError?.(error) === 'failed')
144
+ outcome = 'failed';
145
+ }
146
+ catch { /* conservative */ }
147
+ observation = { kind: 'threw', error, outcome };
148
+ completion = { outcome, reason: 'threw', error };
149
+ }
150
+ for (const handle of included) {
151
+ const state = custody.get(handle);
152
+ state.finish(completion.outcome === 'presented'
153
+ ? { outcome: state.coverage === 'full' ? 'presented' : 'clipped', reason: 'returned' } : completion);
154
+ }
155
+ }
156
+ }
157
+ finally {
158
+ for (const state of owned)
159
+ state.finish({ outcome: 'released', reason: control.signal?.aborted ? 'cancelled' : 'unused' });
160
+ await Promise.all(owned.map(state => state.done));
161
+ }
162
+ return { observation, settlements: await Promise.all(handles.map(async (handle, index) => ({ prepared: handle, result: await owned[index].done }))) };
163
+ }
@@ -5,6 +5,7 @@ import { asSeatRef } from './seats.js';
5
5
  import { freshWatchLease } from './runtime.js';
6
6
  import { deliveryDeferralExpiry } from './delivery-deferral.js';
7
7
  import { leaseOwnsNotification } from './delivery.js';
8
+ import { dispatchEffect } from './delivery-effects.js';
8
9
  function routeFor(location, seat, now) {
9
10
  if (seat.attachment !== 'attached' || seat.wakeAddress === undefined)
10
11
  return undefined;
@@ -37,19 +38,6 @@ async function currentness(artifact, request, now) {
37
38
  });
38
39
  return value;
39
40
  }
40
- async function attemptWakeWithin(transport, request, timeoutMs, beforeSend) {
41
- let timer;
42
- const timeout = new Promise((resolve) => {
43
- timer = setTimeout(() => resolve({ outcome: 'unknown', diagnostic: 'transport timeout' }), timeoutMs);
44
- });
45
- try {
46
- return await Promise.race([transport.attempt(request, timeoutMs, beforeSend), timeout]);
47
- }
48
- finally {
49
- if (timer !== undefined)
50
- clearTimeout(timer);
51
- }
52
- }
53
41
  async function candidates(artifact, requested, now = Date.now()) {
54
42
  const { value } = await artifact.read((facts) => {
55
43
  const heads = requested === undefined
@@ -62,12 +50,6 @@ async function candidates(artifact, requested, now = Date.now()) {
62
50
  });
63
51
  return value;
64
52
  }
65
- function releaseClaim(artifact, seat, activity, claimToken, signature, message, diagnostic) {
66
- return artifact.completeDelivery({ seat, activity, claimToken, outcome: 'released', at: Date.now(), signature,
67
- ...(message === undefined ? {} : { message }),
68
- ...(diagnostic === undefined ? {} : { diagnostic: safeJson(diagnostic) }),
69
- }).catch(() => false);
70
- }
71
53
  function safeJson(value) {
72
54
  if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number')
73
55
  return value;
@@ -115,55 +97,48 @@ export async function deliverPending(input) {
115
97
  }
116
98
  const claimToken = randomUUID();
117
99
  const leaseMs = input.timeoutMs ?? 5000;
118
- const claim = await input.artifact.claimDelivery({ seat: request.seat, activity: request.activity, kind: 'wake',
119
- route: seat.wakeAddress, at: input.now ?? Date.now(), leaseMs, claimToken }).catch(() => ({ status: 'stale' }));
120
- if (claim.status !== 'acquired')
121
- return;
122
- const withClaim = { ...request, claimToken, attemptN: claim.attemptN };
123
- let final;
124
- let suppressed = false;
125
- const beforeSend = async () => {
126
- try {
127
- final = await currentness(input.artifact, withClaim, input.now ?? Date.now());
128
- suppressed = !final.current;
129
- }
130
- catch {
131
- suppressed = true;
132
- }
133
- return !suppressed;
134
- };
135
- let before;
136
- try {
137
- before = await currentness(input.artifact, withClaim, input.now ?? Date.now());
138
- }
139
- catch { /* unverifiable */ }
140
- if (!before?.current) {
141
- await releaseClaim(input.artifact, request.seat, request.activity, claimToken, 'pre_send_revalidation_failed', before === undefined ? 'Square delivery state could not be verified.' : 'The notification is no longer current.', before);
142
- return;
143
- }
144
- let outcome;
145
- try {
146
- outcome = await attemptWakeWithin(input.transport, withClaim, leaseMs, beforeSend);
147
- }
148
- catch (error) {
149
- outcome = { outcome: 'unknown', diagnostic: error instanceof Error ? error.message : String(error) };
150
- }
151
- if (suppressed) {
152
- await releaseClaim(input.artifact, request.seat, request.activity, claimToken, 'pre_send_revalidation_failed', 'The notification changed before the final send check.', final ?? { observationAvailable: false });
153
- return;
154
- }
155
- if (outcome.outcome === 'not-capable') {
156
- await releaseClaim(input.artifact, request.seat, request.activity, claimToken, 'transport_not_capable', undefined, outcome.diagnostic);
157
- return;
158
- }
159
- const mapped = outcome.outcome === 'accepted' ? 'accepted'
160
- : outcome.outcome === 'unknown' ? 'unknown' : 'failed';
161
- await input.artifact.completeDelivery({ seat: request.seat, activity: request.activity, claimToken,
162
- outcome: mapped, at: Date.now(),
163
- ...('signature' in outcome && outcome.signature !== undefined ? { signature: outcome.signature } : {}),
164
- ...('message' in outcome && outcome.message !== undefined ? { message: outcome.message } : {}),
165
- ...('diagnostic' in outcome && outcome.diagnostic !== undefined ? { diagnostic: safeJson(outcome.diagnostic) } : {}),
166
- }).catch(() => false);
100
+ let snapshot;
101
+ await dispatchEffect({
102
+ async claim() {
103
+ const claim = await input.artifact.claimDelivery({ seat: request.seat, activity: request.activity, kind: 'wake',
104
+ route: seat.wakeAddress, at: input.now ?? Date.now(), leaseMs, claimToken }).catch(() => ({ status: 'stale' }));
105
+ return claim.status === 'acquired' ? { token: claimToken, ordinal: claim.attemptN } : undefined;
106
+ },
107
+ async current() {
108
+ snapshot = undefined;
109
+ snapshot = await currentness(input.artifact, request, input.now ?? Date.now());
110
+ return snapshot.current;
111
+ },
112
+ async settle(claim, completion) {
113
+ let fields = {};
114
+ if (completion.reason === 'not-current')
115
+ fields = {
116
+ signature: 'pre_send_revalidation_failed',
117
+ message: completion.phase === 'final' ? 'The notification changed before the final send check.'
118
+ : snapshot === undefined ? 'Square delivery state could not be verified.' : 'The notification is no longer current.',
119
+ diagnostic: snapshot ?? (completion.phase === 'final' ? { observationAvailable: false } : undefined),
120
+ };
121
+ else if (completion.reason === 'timeout')
122
+ fields = { diagnostic: 'transport timeout' };
123
+ else if (completion.reason === 'threw')
124
+ fields = { diagnostic: completion.error instanceof Error ? completion.error.message : String(completion.error) };
125
+ else if (completion.reason === 'not-capable')
126
+ fields = {
127
+ signature: 'transport_not_capable', diagnostic: completion.data?.diagnostic,
128
+ };
129
+ else if (completion.reason === 'returned')
130
+ fields = completion.data;
131
+ return input.artifact.completeDelivery({ seat: request.seat, activity: request.activity, claimToken: claim.token,
132
+ outcome: completion.outcome, at: Date.now(),
133
+ ...(fields.signature === undefined ? {} : { signature: fields.signature }),
134
+ ...(fields.message === undefined ? {} : { message: fields.message }),
135
+ ...(fields.diagnostic === undefined ? {} : { diagnostic: safeJson(fields.diagnostic) }),
136
+ }).catch(() => false);
137
+ },
138
+ }, async ({ claim, timeoutMs, beforeSend }) => {
139
+ const outcome = await input.transport.attempt({ ...request, claimToken: claim.token, attemptN: claim.ordinal }, timeoutMs, beforeSend);
140
+ return { outcome: outcome.outcome, data: outcome };
141
+ }, { timeoutMs: leaseMs });
167
142
  };
168
143
  const groups = receivers.values();
169
144
  const settled = await Promise.allSettled(Array.from({ length: Math.min(4, receivers.size) }, async () => {
@@ -0,0 +1,5 @@
1
+ /** Storage-independent attempt transitions and bound effect lifecycles. */
2
+ export { claimAttempt, prepareAttempt, settleAttempt, presentationBlocksDispatch, automaticDispatchEligible } from './delivery-attempts.js';
3
+ export type { DeliveryPurpose, AttemptOutcome, Attempt, AttemptRows, AttemptClaim } from './delivery-attempts.js';
4
+ export { dispatchEffect, preparePresentation, presentPrepared, presentPreparedBatch } from './delivery-effects.js';
5
+ export type { EffectClaim, EffectPort, Persistence, Settlement, DispatchOutcome, DispatchCompletion, DispatchContext, DispatchReport, PresentationCompletion, PresentationWork, PreparedPresentation, PresentationControl, PresentationReport } from './delivery-effects.js';
@@ -0,0 +1,3 @@
1
+ /** Storage-independent attempt transitions and bound effect lifecycles. */
2
+ export { claimAttempt, prepareAttempt, settleAttempt, presentationBlocksDispatch, automaticDispatchEligible } from './delivery-attempts.js';
3
+ export { dispatchEffect, preparePresentation, presentPrepared, presentPreparedBatch } from './delivery-effects.js';
@@ -40,7 +40,7 @@ export declare function pendingPages(artifact: SquareArtifactPort, scan: Pending
40
40
  at: number;
41
41
  actor: string;
42
42
  recipient: string;
43
- route: "bell" | "mention" | "reply" | "attention";
43
+ route: import("./attention.js").AudienceRoute;
44
44
  }[], void, unknown>;
45
45
  /** A pass retains only its captured joins; routes and individual receipts are fresh. */
46
46
  export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly DirectedHead[]): {
package/dist/delivery.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { renderAttentionPreview, renderSquareReminder } from './attention-presentation.js';
2
2
  import { parseActivityId } from './square-core.js';
3
3
  import { deliveryDeferralExpiry } from './delivery-deferral.js';
4
+ import { automaticDispatchEligible, presentationBlocksDispatch } from './delivery-attempts.js';
4
5
  import { matchesCatchSelection } from './catch-selection.js';
5
6
  /** Exact stored activity projection for transports that send an awareness reminder. */
6
7
  export function wakePreview(facts, request) {
@@ -16,16 +17,16 @@ export function wakePreview(facts, request) {
16
17
  actor: item.actor, route: head.route, body: item.body ?? '(structured activity)',
17
18
  })]);
18
19
  }
20
+ function custody(evidence) {
21
+ return evidence.map(row => ({ purpose: row.kind === 'wake' ? 'dispatch' : 'presentation', outcome: row.outcome, expiresAt: row.expiresAt }));
22
+ }
19
23
  /** Presentation custody blocks a wake, independently of native wake acceptance. */
20
24
  export function presentationBlocksWake(evidence, now) {
21
- return evidence.some((row) => row.kind === 'presentation'
22
- && (row.outcome === 'presented' || row.outcome === 'clipped' || row.outcome === 'unknown'
23
- || row.outcome === 'dispatching' && (row.expiresAt === undefined || row.expiresAt > now)));
25
+ return presentationBlocksDispatch(custody(evidence), now);
24
26
  }
25
27
  /** Preselection only: an expired wake dispatch is uncertain, never safe automatic replay. */
26
28
  export function automaticWakeEligible(evidence, now) {
27
- return !presentationBlocksWake(evidence, now) && !evidence.some((row) => row.kind === 'wake'
28
- && (row.outcome === 'accepted' || row.outcome === 'unknown' || row.outcome === 'dispatching'));
29
+ return automaticDispatchEligible(custody(evidence), now);
29
30
  }
30
31
  /** Notification pending is not cursor suffix or historical catch membership. */
31
32
  export function pendingHead(facts, name, index) {
package/dist/help.js CHANGED
@@ -32,7 +32,7 @@ const COMMANDS = [
32
32
  '',
33
33
  'Options:',
34
34
  ' -f, --force Express without first catching unread activity or satisfying attention etiquette.',
35
- ' --no-wait If held or throttled, save a draft and return instead of waiting. A held wait gives up on its own after 10 minutes.',
35
+ ' --no-wait If throttled, save a draft and return instead of waiting.',
36
36
  ' --metadata <json> Attach a bounded JSON object (at most 16 KiB UTF-8 and 256 nesting levels); it may be the only content.',
37
37
  ' Metadata is returned in structured activity results but is not shown in human text or searchable.',
38
38
  ],
@@ -70,8 +70,6 @@ const COMMANDS = [
70
70
  summary: 'Show the participant roster and current states.',
71
71
  details: ['Options:', ' --limit <count> Show the first count names in human text (default 20, maximum 100).'],
72
72
  },
73
- { names: ['hold'], usage: '--as <name> hold [reason | -]', usesSquare: true, group: 'participant', summary: 'Raise a hand and pause participant activity.' },
74
- { names: ['resume'], usage: '--as <name> resume', usesSquare: true, group: 'participant', summary: 'Lower the raised hand and resume activity.' },
75
73
  {
76
74
  names: ['install'], usage: 'install (--all | <target>...) [-f]', group: 'maintenance',
77
75
  summary: 'Install Square support for one or more agent hosts.',
@@ -94,7 +92,7 @@ const COMMANDS = [
94
92
  {
95
93
  names: ['migrate'], usage: 'migrate', usesSquare: true, group: 'maintenance',
96
94
  summary: 'Convert a supported older square to the current format in place.',
97
- details: ['For the selected .square artifact only. Stop sessions using this artifact before conversion. Back up the stopped artifact, migrate it, then run square --location <path> doctor before starting updated clients.', 'Versions 2–6 migrate explicitly to format 7; format 6 attachment is preserved and Body coordinates become opaque owners. Versions 4/5 first gain attached seats; 4 also gains empty deferrals. Version 1, unrelated or unsupported artifacts refuse without a write. Names whose legacy owner and routes agree may share an owner; ambiguous names stay standing without custody. Rejoin with the same owner after migration to republish discovery. There is no --check, --backup or --force.'],
95
+ details: ['For the selected .square artifact only. Stop sessions using this artifact before conversion. Back up the stopped artifact, migrate it, then run square --location <path> doctor before starting updated clients.', 'Versions 2–7 migrate explicitly to format 8. Format 7 migration deletes hold/resume activities and their keyed rows, clears replies to those activities, and preserves surviving activity ids with gaps. Format 6 attachment and opaque-owner conversion are preserved; versions 4/5 first gain attached seats, and 4 also gains empty deferrals. Version 1, unrelated or unsupported artifacts refuse without a write. Names whose legacy owner and routes agree may share an owner; ambiguous names stay standing without custody. Rejoin with the same owner after migration to republish discovery. There is no --check, --backup or --force.'],
98
96
  },
99
97
  ];
100
98
  function definitionFor(command) {
@@ -105,7 +103,7 @@ function isHelpFlag(value) {
105
103
  }
106
104
  export function renderGlobalHelp() {
107
105
  const groups = [
108
- { key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'unlisten', 'ignore', 'listening', 'catch', 'history', 'status', 'hold', 'resume', 'done'] },
106
+ { key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'unlisten', 'ignore', 'listening', 'catch', 'history', 'status', 'done'] },
109
107
  { key: 'host', title: 'Prepare and manage:', order: ['build', 'list', 'participants'] },
110
108
  { key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor', 'migrate'] },
111
109
  ];
@@ -121,7 +119,7 @@ export function renderGlobalHelp() {
121
119
  'Usage: square [--location <square>] [--as <name>] [--owner <id>] <command> [args...]',
122
120
  'Environment: SQUARE_LOCATION sets the CLI local artifact path; square-accessing commands require it or --location. Use --as to select a participant.',
123
121
  'Identity: --owner overrides SQUARE_OWNER, then a complete harness identity, then user:<OS user>@<host>. Incomplete or ambiguous harness identities refuse. Owners without wake use catch --idle.',
124
- 'Output: --json on join, express, catch, history, listen, ignore, unlisten, listening, hold, resume, done, status, participants and inbox prints the SDK result without observation; a participant handle becomes {name}.',
122
+ 'Output: --json on join, express, catch, history, listen, ignore, unlisten, listening, done, status, participants and inbox prints the SDK result without observation; a participant handle becomes {name}.',
125
123
  'History emits one JSON document; stream --ndjson emits one SDK record per line.',
126
124
  '',
127
125
  ...commandLines,
@@ -0,0 +1,16 @@
1
+ import type { WakeRoute } from './model.js';
2
+ import type { WakeOutcome, WakeRequest, WakeTransportPort } from './ports.js';
3
+ export interface HttpWakeTransportOptions {
4
+ fetch?: typeof fetch;
5
+ env?: NodeJS.ProcessEnv;
6
+ }
7
+ /** HTTP accepts wake custody only; it never consumes or presents Square activity. */
8
+ export declare class HttpWakeTransport implements WakeTransportPort {
9
+ private readonly opts;
10
+ constructor(opts?: HttpWakeTransportOptions);
11
+ probe(route: WakeRoute): Promise<boolean | {
12
+ outcome: 'not-capable';
13
+ diagnostic: string;
14
+ }>;
15
+ attempt(request: WakeRequest, timeoutMs: number, beforeSend: () => Promise<boolean>): Promise<WakeOutcome>;
16
+ }