@astrosheep/square 0.3.73 → 0.3.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boundary-presentation.d.ts +8 -1
- package/dist/boundary-presentation.js +8 -1
- package/dist/cli/context.d.ts +1 -1
- package/dist/inbox.d.ts +2 -0
- package/dist/inbox.js +64 -22
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +51 -26
- package/dist/model.js +5 -2
- package/dist/open-square.d.ts +12 -2
- package/dist/open-square.js +42 -4
- package/dist/participant-host.js +6 -2
- package/dist/ports.d.ts +10 -0
- package/dist/square-actions.d.ts +3 -1
- package/dist/square-actions.js +10 -5
- package/dist/square-application.d.ts +4 -0
- package/dist/square-application.js +41 -27
- package/dist/square-facade.d.ts +26 -22
- package/dist/views.d.ts +3 -1
- package/dist/views.js +37 -29
- package/guides/application-demand-optimization.md +50 -0
- package/guides/catch-cold-path-optimization.md +56 -0
- package/guides/catch-lock-investigation.md +81 -0
- package/guides/mcp-connection-lifetime.md +22 -0
- package/guides/pi-boundary-pending-reuse.md +13 -0
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/square-pi.js +4 -8
|
@@ -2,7 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
3
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
4
|
import { SquareError, isSquareError, nameKey, validateName } from './model.js';
|
|
5
|
-
import { validateDoneBody } from './decisions.js';
|
|
5
|
+
import { resolveKnownName, validateDoneBody } from './decisions.js';
|
|
6
6
|
import { HELD_WAIT_BUDGET_MS, WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
|
|
7
7
|
import { catchLimit, observeCatchSelection } from './catch-decisions.js';
|
|
8
8
|
import { directedSuffix } from './activity-feed.js';
|
|
@@ -19,6 +19,7 @@ import { createSquare, diagnoseSquareFile } from './artifact.js';
|
|
|
19
19
|
import { migrateSquareFile } from './square-migration.js';
|
|
20
20
|
import { discoverSquares, DEFAULT_LIST_DEPTH, DEFAULT_LIST_LIMIT } from './list.js';
|
|
21
21
|
import { streamSquare } from './stream.js';
|
|
22
|
+
export { createSquareResourceOwner } from './open-square.js';
|
|
22
23
|
export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
23
24
|
export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
|
|
24
25
|
export { HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
@@ -82,8 +83,8 @@ async function joinOwned(square, requested, options = {}, control) {
|
|
|
82
83
|
async function prepareExpression(square, requested, control) {
|
|
83
84
|
try {
|
|
84
85
|
checkControl(control);
|
|
85
|
-
const
|
|
86
|
-
return
|
|
86
|
+
const result = await join(square, requested, control, { requireKnown: true });
|
|
87
|
+
return { participant: result.name, activity: result.activity };
|
|
87
88
|
}
|
|
88
89
|
catch (error) {
|
|
89
90
|
throw failure(error, { operation: 'express', subject: 'caller', participant: requested, expressEffect: 'unsent' });
|
|
@@ -139,12 +140,12 @@ async function landExpression(square, name, body, options, transport, control) {
|
|
|
139
140
|
});
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
|
-
async function expressOwned(square, requested, body, options, transport, control) {
|
|
143
|
+
async function expressOwned(square, requested, body, options, transport, presentation, control) {
|
|
143
144
|
const entry = await prepareExpression(square, requested, control);
|
|
144
145
|
const name = entry.participant;
|
|
145
146
|
let result;
|
|
146
147
|
try {
|
|
147
|
-
const feed = (await square.artifact.read((facts) => {
|
|
148
|
+
const feed = !presentation ? undefined : (await square.artifact.read((facts) => {
|
|
148
149
|
const pendingPublic = directedSuffix(facts, name);
|
|
149
150
|
return {
|
|
150
151
|
pendingPublic,
|
|
@@ -161,7 +162,7 @@ async function expressOwned(square, requested, body, options, transport, control
|
|
|
161
162
|
activity: result.activity, waited: result.waited,
|
|
162
163
|
...(result.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: result.replyAuthorNotHere }),
|
|
163
164
|
},
|
|
164
|
-
projection: {
|
|
165
|
+
projection: feed === undefined ? undefined : {
|
|
165
166
|
...fresh, ...feed, participant: name,
|
|
166
167
|
location: square.location, now: square.clock(), automaticDeliveryAvailable: hasAutomaticDeliveryIdentity(square.env),
|
|
167
168
|
...(entry.activity === null ? {} : { reentry: entry.activity }),
|
|
@@ -217,9 +218,11 @@ async function catchOwned(square, requested, options = {}, control) {
|
|
|
217
218
|
if (Object.hasOwn(options, 'replace') && idle === 0)
|
|
218
219
|
throw new SquareError('invalid_args', 'Catch replacement requires a positive idle duration');
|
|
219
220
|
catchLimit(options.limit);
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
const from = (await square.artifact.read((facts) => {
|
|
222
|
+
name = resolveKnownName(facts, requested);
|
|
223
|
+
subject = 'filter';
|
|
224
|
+
return options.from?.map((target) => resolveKnownName(facts, target));
|
|
225
|
+
}, control?.signal)).value;
|
|
223
226
|
selection = { ...options, ...(from === undefined ? {} : { from }) };
|
|
224
227
|
subject = 'caller';
|
|
225
228
|
if (idle === 0) {
|
|
@@ -360,12 +363,14 @@ export class Square {
|
|
|
360
363
|
endOwnedSession(name, sessionId, expectedEpoch) { return endOwnedSession(this.square, name, sessionId, expectedEpoch); }
|
|
361
364
|
retireEndedSessionRoutes(sessionId) { return retireEndedSessionRoutes(this.square, sessionId); }
|
|
362
365
|
}
|
|
363
|
-
export function createSquareApplication(context) {
|
|
366
|
+
export function createSquareApplication(context, demand = { presentation: true }) {
|
|
367
|
+
const presentation = demand.presentation;
|
|
364
368
|
const env = { ...context.env };
|
|
365
369
|
const clock = clockFor(env, context.clock);
|
|
366
370
|
const hostLedger = context.hostLedger ?? hostLedgerForEnv(env);
|
|
367
371
|
const openOptions = { env, clock, hostLedger, wakeTransport: context.wakeTransport };
|
|
368
372
|
const getTransport = defaultTransport(openOptions);
|
|
373
|
+
const resources = context.resources;
|
|
369
374
|
function location() {
|
|
370
375
|
const selected = context.squarePath ?? env.SQUARE_LOCATION;
|
|
371
376
|
if (selected === undefined || selected.trim() === '')
|
|
@@ -385,7 +390,9 @@ export function createSquareApplication(context) {
|
|
|
385
390
|
}
|
|
386
391
|
async function scoped(operation, control) {
|
|
387
392
|
checkControl(control);
|
|
388
|
-
const
|
|
393
|
+
const selected = location();
|
|
394
|
+
const artifact = resources?.artifact(selected);
|
|
395
|
+
const square = await openSquare(selected, { ...openOptions, artifact }, control);
|
|
389
396
|
try {
|
|
390
397
|
return await operation(square);
|
|
391
398
|
}
|
|
@@ -416,7 +423,7 @@ export function createSquareApplication(context) {
|
|
|
416
423
|
const result = await (verb === 'listen' ? listen : ignore)(square, name, target, control);
|
|
417
424
|
return {
|
|
418
425
|
data: result,
|
|
419
|
-
projection: {
|
|
426
|
+
projection: !presentation ? undefined : {
|
|
420
427
|
...await contextProjection(square, name), participant: name,
|
|
421
428
|
requestedTarget: target, target: result.activity?.target ?? target,
|
|
422
429
|
},
|
|
@@ -442,6 +449,8 @@ export function createSquareApplication(context) {
|
|
|
442
449
|
else {
|
|
443
450
|
result = await resume(square, name, control);
|
|
444
451
|
}
|
|
452
|
+
if (!presentation)
|
|
453
|
+
return { data: { activity: result.activity }, projection: undefined };
|
|
445
454
|
const view = await eventPresentation(square, result.activity.id);
|
|
446
455
|
return {
|
|
447
456
|
data: { activity: result.activity },
|
|
@@ -461,7 +470,7 @@ export function createSquareApplication(context) {
|
|
|
461
470
|
}, input.markdown, control?.signal);
|
|
462
471
|
return {
|
|
463
472
|
data: { location: squarePath, ...configuration },
|
|
464
|
-
projection: { location: squarePath, standingCount: 0, held: false, now,
|
|
473
|
+
projection: !presentation ? undefined : { location: squarePath, standingCount: 0, held: false, now,
|
|
465
474
|
automaticDeliveryAvailable: hasAutomaticDeliveryIdentity(env) },
|
|
466
475
|
};
|
|
467
476
|
},
|
|
@@ -472,6 +481,8 @@ export function createSquareApplication(context) {
|
|
|
472
481
|
throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
|
|
473
482
|
}
|
|
474
483
|
const data = await joinOwned(square, name, options, control);
|
|
484
|
+
if (!presentation)
|
|
485
|
+
return { data, projection: undefined };
|
|
475
486
|
try {
|
|
476
487
|
const view = await entryPresentation(square, data.participant, last);
|
|
477
488
|
return {
|
|
@@ -485,19 +496,19 @@ export function createSquareApplication(context) {
|
|
|
485
496
|
}, control);
|
|
486
497
|
},
|
|
487
498
|
express(body, options = {}, control) {
|
|
488
|
-
return selected('express', (square, name) => expressOwned(square, name, body, options, getTransport, control), control);
|
|
499
|
+
return selected('express', (square, name) => expressOwned(square, name, body, options, getTransport, presentation, control), control);
|
|
489
500
|
},
|
|
490
501
|
catch(options, control) {
|
|
491
502
|
return selected('catch', async (square, name) => {
|
|
492
503
|
const result = await catchOwned(square, name, options, control);
|
|
493
|
-
const presence = await catchPresence(square, control).catch(() => undefined);
|
|
504
|
+
const presence = !presentation ? undefined : await catchPresence(square, control).catch(() => undefined);
|
|
494
505
|
const view = result.selection;
|
|
495
506
|
const stop = result.stop;
|
|
496
507
|
const status = stop === 'already-active' || stop === 'replaced' || stop === 'capped' || stop === 'quorum'
|
|
497
508
|
? stop : undefined;
|
|
498
509
|
return {
|
|
499
510
|
data: { ...caughtResult(result), ...(status === undefined ? {} : { status }) },
|
|
500
|
-
projection: {
|
|
511
|
+
projection: !presentation ? undefined : {
|
|
501
512
|
...observedContext(square, view.standingCount, view.held, view.participant, view.now),
|
|
502
513
|
participant: view.participant, sayNumbers: view.sayNumbers,
|
|
503
514
|
ownActivityCount: view.ownActivityCount, hardCap: view.hardCap, stop,
|
|
@@ -525,12 +536,15 @@ export function createSquareApplication(context) {
|
|
|
525
536
|
const limit = query.limit ?? (query.at?.length ? null : HISTORY_DEFAULT_LIMIT);
|
|
526
537
|
const kinds = query.at?.length || query.contextBefore !== undefined || query.contextAfter !== undefined || query.since !== undefined ? undefined : NON_READ;
|
|
527
538
|
return (await square.artifact.read((facts) => {
|
|
528
|
-
const selected = selectHistory(facts, query, limit, kinds);
|
|
539
|
+
const selected = selectHistory(facts, query, limit, kinds, presentation);
|
|
529
540
|
if (limit === null && selected.totalMatches > HISTORY_MAX_LIMIT) {
|
|
530
541
|
throw failure(new SquareError('invalid_args', `History context exceeds ${HISTORY_MAX_LIMIT} activities`), {
|
|
531
542
|
operation: 'history', historyBounds: { maxLimit: HISTORY_MAX_LIMIT, totalMatches: selected.totalMatches },
|
|
532
543
|
});
|
|
533
544
|
}
|
|
545
|
+
const data = { activities: publicActivities(selected.activities) };
|
|
546
|
+
if (!presentation)
|
|
547
|
+
return { data, projection: undefined };
|
|
534
548
|
const indexes = selected.activities.map((activity) => activity.index);
|
|
535
549
|
const presenceAnchors = {};
|
|
536
550
|
for (const participant of facts.roster()) {
|
|
@@ -539,7 +553,7 @@ export function createSquareApplication(context) {
|
|
|
539
553
|
(presenceAnchors[anchor] ??= []).push(participant.name);
|
|
540
554
|
}
|
|
541
555
|
return {
|
|
542
|
-
data
|
|
556
|
+
data,
|
|
543
557
|
projection: {
|
|
544
558
|
...observedContext(square, inSquareCount(facts), facts.hold().active), ...selected,
|
|
545
559
|
sayNumbers: facts.sayOrdinals(indexes), presenceAnchors,
|
|
@@ -554,7 +568,7 @@ export function createSquareApplication(context) {
|
|
|
554
568
|
return selected('other', async (square, requested) => {
|
|
555
569
|
const name = (await resolveParticipant(square, requested, control)).name;
|
|
556
570
|
const targets = await listening(square, name, control);
|
|
557
|
-
return { data: { listening: targets }, projection: await contextProjection(square, name, control) };
|
|
571
|
+
return { data: { listening: targets }, projection: !presentation ? undefined : await contextProjection(square, name, control) };
|
|
558
572
|
}, control);
|
|
559
573
|
},
|
|
560
574
|
hold: (reason, control) => lifecycle('hold', reason, control),
|
|
@@ -562,15 +576,15 @@ export function createSquareApplication(context) {
|
|
|
562
576
|
done: (body, control) => lifecycle('done', body, control),
|
|
563
577
|
async status(viewer, control) {
|
|
564
578
|
checkControl(control);
|
|
565
|
-
const selectedViewer = viewer ?? await actor().catch(() => '');
|
|
579
|
+
const selectedViewer = !presentation ? '' : viewer ?? await actor().catch(() => '');
|
|
566
580
|
return scoped(async (square) => (await square.artifact.read((facts) => queryStatus(facts, {
|
|
567
581
|
location: square.location, now: square.clock(), automaticDeliveryAvailable: hasAutomaticDeliveryIdentity(square.env),
|
|
568
|
-
}, selectedViewer), control?.signal)).value, control);
|
|
582
|
+
}, selectedViewer, presentation), control?.signal)).value, control);
|
|
569
583
|
},
|
|
570
584
|
participants(control) {
|
|
571
585
|
return scoped(async (square) => (await square.artifact.read((facts) => queryParticipants(facts, {
|
|
572
586
|
location: square.location, now: square.clock(), automaticDeliveryAvailable: hasAutomaticDeliveryIdentity(square.env),
|
|
573
|
-
}), control?.signal)).value, control);
|
|
587
|
+
}, presentation), control?.signal)).value, control);
|
|
574
588
|
},
|
|
575
589
|
async diagnose(control) {
|
|
576
590
|
checkControl(control);
|
|
@@ -578,7 +592,7 @@ export function createSquareApplication(context) {
|
|
|
578
592
|
const data = await diagnoseSquareFile(squarePath);
|
|
579
593
|
return {
|
|
580
594
|
data,
|
|
581
|
-
projection: { location: squarePath, now: clock(),
|
|
595
|
+
projection: !presentation ? undefined : { location: squarePath, now: clock(),
|
|
582
596
|
...(data.standingCount === undefined ? {} : { standingCount: data.standingCount }) },
|
|
583
597
|
};
|
|
584
598
|
},
|
|
@@ -586,14 +600,14 @@ export function createSquareApplication(context) {
|
|
|
586
600
|
checkControl(control);
|
|
587
601
|
const squarePath = location();
|
|
588
602
|
const data = await migrateSquareFile(squarePath, control?.signal);
|
|
589
|
-
return { data, projection: { location: squarePath, now: clock(), standingCount: data.standing } };
|
|
603
|
+
return { data, projection: !presentation ? undefined : { location: squarePath, now: clock(), standingCount: data.standing } };
|
|
590
604
|
},
|
|
591
605
|
async discover(options, control) {
|
|
592
606
|
checkControl(control);
|
|
593
607
|
const data = await discoverSquares(context.cwd, options, control, openOptions);
|
|
594
608
|
return {
|
|
595
609
|
data,
|
|
596
|
-
projection: { cwd: context.cwd, now: clock(),
|
|
610
|
+
projection: !presentation ? undefined : { cwd: context.cwd, now: clock(),
|
|
597
611
|
depth: options.depth ?? DEFAULT_LIST_DEPTH, limit: options.limit ?? DEFAULT_LIST_LIMIT },
|
|
598
612
|
};
|
|
599
613
|
},
|
|
@@ -633,7 +647,7 @@ export function createSquareApplication(context) {
|
|
|
633
647
|
const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
634
648
|
return compare(a.squarePath, b.squarePath) || compare(nameKey(a.name), nameKey(b.name)) || compare(a.name, b.name);
|
|
635
649
|
});
|
|
636
|
-
return { data: { rows: rows.slice(0, limit), total: rows.length }, projection: { sessionId, now: clock(), limit } };
|
|
650
|
+
return { data: { rows: rows.slice(0, limit), total: rows.length }, projection: !presentation ? undefined : { sessionId, now: clock(), limit } };
|
|
637
651
|
},
|
|
638
652
|
async recovery(control) {
|
|
639
653
|
let projection = { now: clock() };
|
|
@@ -659,7 +673,7 @@ export function createSquareApplication(context) {
|
|
|
659
673
|
}, control);
|
|
660
674
|
}
|
|
661
675
|
catch { /* unavailable recovery observations never replace the original error */ }
|
|
662
|
-
return { data: projection, projection };
|
|
676
|
+
return { data: projection, projection: presentation ? projection : undefined };
|
|
663
677
|
},
|
|
664
678
|
stream(options, control) {
|
|
665
679
|
checkControl(control);
|
package/dist/square-facade.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ActivityId, Perception, Reach } from './square-core.js';
|
|
2
2
|
import type { StoredAct } from './model.js';
|
|
3
|
-
import type { HostLedgerPort, WakeTransportPort, DeliveryResult } from './ports.js';
|
|
3
|
+
import type { HostLedgerPort, WakeTransportPort, DeliveryResult, SquareResourceOwner } from './ports.js';
|
|
4
4
|
import type { DiagnoseResult } from './artifact.js';
|
|
5
5
|
import type { MigrationOutcome } from './square-migration.js';
|
|
6
6
|
export type { ActivityId, Perception, Reach } from './square-core.js';
|
|
@@ -136,10 +136,14 @@ export interface SquareApplicationContext extends Readonly<OpenOptions> {
|
|
|
136
136
|
readonly env: NodeJS.ProcessEnv;
|
|
137
137
|
readonly squarePath?: string;
|
|
138
138
|
readonly participant?: string;
|
|
139
|
+
/** Borrow the owner's connection for the configured square. The caller owns its lifetime;
|
|
140
|
+
* without an owner, each operation opens and closes its own connection. */
|
|
141
|
+
readonly resources?: SquareResourceOwner;
|
|
139
142
|
}
|
|
140
|
-
|
|
143
|
+
/** Data-only callers do not request or pay for presentation work. */
|
|
144
|
+
export interface ApplicationOutcome<Data, Projection, Presentation extends boolean = true> {
|
|
141
145
|
readonly data: Data;
|
|
142
|
-
readonly projection: Projection;
|
|
146
|
+
readonly projection: Presentation extends true ? Projection : undefined;
|
|
143
147
|
}
|
|
144
148
|
export interface ApplicationFailureDetail {
|
|
145
149
|
readonly operation: 'express' | 'join' | 'catch' | 'history' | 'other';
|
|
@@ -444,26 +448,26 @@ export interface StatusProjection extends SquareContextProjection, PresenceClass
|
|
|
444
448
|
}
|
|
445
449
|
export interface ParticipantsProjection extends SquareContextProjection, PresenceClassification {
|
|
446
450
|
}
|
|
447
|
-
export interface SquareApplication {
|
|
451
|
+
export interface SquareApplication<Presentation extends boolean = true> {
|
|
448
452
|
now(): number;
|
|
449
|
-
build(input: BuildApplicationInput, control?: OperationControl): Promise<ApplicationOutcome<BuildApplicationData, SquareContextProjection>>;
|
|
450
|
-
join(options?: JoinApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<JoinApplicationData, EntryProjection>>;
|
|
451
|
-
express(body: string, options?: ExpressApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<ExpressApplicationData, ExpressProjection>>;
|
|
452
|
-
catch(options?: CatchApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<CatchApplicationData, CatchProjection>>;
|
|
453
|
-
history(query?: HistoryApplicationQuery, control?: OperationControl): Promise<ApplicationOutcome<HistoryApplicationData, HistoryProjection>>;
|
|
454
|
-
listen(target: string, control?: OperationControl): Promise<ApplicationOutcome<ListenerChangeData, ListenerProjection>>;
|
|
455
|
-
ignore(target: string, control?: OperationControl): Promise<ApplicationOutcome<ListenerChangeData, ListenerProjection>>;
|
|
456
|
-
listening(control?: OperationControl): Promise<ApplicationOutcome<ListeningData, SquareContextProjection>>;
|
|
457
|
-
hold(reason?: string, control?: OperationControl): Promise<ApplicationOutcome<LifecycleData, LifecycleProjection>>;
|
|
458
|
-
resume(control?: OperationControl): Promise<ApplicationOutcome<LifecycleData, LifecycleProjection>>;
|
|
459
|
-
done(body?: string, control?: OperationControl): Promise<ApplicationOutcome<LifecycleData, LifecycleProjection>>;
|
|
460
|
-
status(viewer?: string, control?: OperationControl): Promise<ApplicationOutcome<StatusApplicationData, StatusProjection>>;
|
|
461
|
-
participants(control?: OperationControl): Promise<ApplicationOutcome<ParticipantsApplicationData, ParticipantsProjection>>;
|
|
462
|
-
diagnose(control?: OperationControl): Promise<ApplicationOutcome<DiagnosisApplicationData, Partial<SquareContextProjection
|
|
463
|
-
migrate(control?: OperationControl): Promise<ApplicationOutcome<MigrationApplicationData, Partial<SquareContextProjection
|
|
464
|
-
discover(options: DiscoveryApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<DiscoveryApplicationData, DiscoveryProjection>>;
|
|
465
|
-
inbox(sessionId: string, options?: InboxApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<InboxApplicationData, InboxProjection>>;
|
|
466
|
-
recovery(control?: OperationControl): Promise<ApplicationOutcome<RecoveryApplicationData, RecoveryProjection>>;
|
|
453
|
+
build(input: BuildApplicationInput, control?: OperationControl): Promise<ApplicationOutcome<BuildApplicationData, SquareContextProjection, Presentation>>;
|
|
454
|
+
join(options?: JoinApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<JoinApplicationData, EntryProjection, Presentation>>;
|
|
455
|
+
express(body: string, options?: ExpressApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<ExpressApplicationData, ExpressProjection, Presentation>>;
|
|
456
|
+
catch(options?: CatchApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<CatchApplicationData, CatchProjection, Presentation>>;
|
|
457
|
+
history(query?: HistoryApplicationQuery, control?: OperationControl): Promise<ApplicationOutcome<HistoryApplicationData, HistoryProjection, Presentation>>;
|
|
458
|
+
listen(target: string, control?: OperationControl): Promise<ApplicationOutcome<ListenerChangeData, ListenerProjection, Presentation>>;
|
|
459
|
+
ignore(target: string, control?: OperationControl): Promise<ApplicationOutcome<ListenerChangeData, ListenerProjection, Presentation>>;
|
|
460
|
+
listening(control?: OperationControl): Promise<ApplicationOutcome<ListeningData, SquareContextProjection, Presentation>>;
|
|
461
|
+
hold(reason?: string, control?: OperationControl): Promise<ApplicationOutcome<LifecycleData, LifecycleProjection, Presentation>>;
|
|
462
|
+
resume(control?: OperationControl): Promise<ApplicationOutcome<LifecycleData, LifecycleProjection, Presentation>>;
|
|
463
|
+
done(body?: string, control?: OperationControl): Promise<ApplicationOutcome<LifecycleData, LifecycleProjection, Presentation>>;
|
|
464
|
+
status(viewer?: string, control?: OperationControl): Promise<ApplicationOutcome<StatusApplicationData, StatusProjection, Presentation>>;
|
|
465
|
+
participants(control?: OperationControl): Promise<ApplicationOutcome<ParticipantsApplicationData, ParticipantsProjection, Presentation>>;
|
|
466
|
+
diagnose(control?: OperationControl): Promise<ApplicationOutcome<DiagnosisApplicationData, Partial<SquareContextProjection>, Presentation>>;
|
|
467
|
+
migrate(control?: OperationControl): Promise<ApplicationOutcome<MigrationApplicationData, Partial<SquareContextProjection>, Presentation>>;
|
|
468
|
+
discover(options: DiscoveryApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<DiscoveryApplicationData, DiscoveryProjection, Presentation>>;
|
|
469
|
+
inbox(sessionId: string, options?: InboxApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<InboxApplicationData, InboxProjection, Presentation>>;
|
|
470
|
+
recovery(control?: OperationControl): Promise<ApplicationOutcome<RecoveryApplicationData, RecoveryProjection, Presentation>>;
|
|
467
471
|
stream(options: StreamApplicationOptions, control?: OperationControl): AsyncIterable<StreamApplicationRecord>;
|
|
468
472
|
}
|
|
469
473
|
export type JoinApplicationResult = JoinApplicationData;
|
package/dist/views.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare const NON_READ: readonly ["join", "done", "say", "hold", "resume"
|
|
|
11
11
|
export declare function toPublicActivity(stored: StoredAct): Activity;
|
|
12
12
|
export declare function parseRequiredActivityId(id: ActivityId): number;
|
|
13
13
|
/** Compile the public query directly to typed selectors; only search hydrates unselected bodies. */
|
|
14
|
-
export declare function selectHistory(facts: SquareRead, query: HistoryApplicationQuery, limit: number | null, kinds: ActivityFilter['kinds']): {
|
|
14
|
+
export declare function selectHistory(facts: SquareRead, query: HistoryApplicationQuery, limit: number | null, kinds: ActivityFilter['kinds'], presentation?: boolean): {
|
|
15
15
|
continuation?: HistoryContinuation | undefined;
|
|
16
16
|
activities: StoredAct[];
|
|
17
17
|
visibleActivities: StoredAct[];
|
|
@@ -29,8 +29,10 @@ export declare function recoveryObservation(square: OpenSquare, name: string, co
|
|
|
29
29
|
export declare function entryPresentation(square: OpenSquare, name: string, lastN?: number | null, control?: OperationControl): Promise<EntryPresentation>;
|
|
30
30
|
type ParticipantQueryContext = Pick<SquareContextProjection, 'location' | 'now' | 'automaticDeliveryAvailable'>;
|
|
31
31
|
export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext): ApplicationOutcome<ParticipantsApplicationData, ParticipantsProjection>;
|
|
32
|
+
export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext, presentation: boolean): ApplicationOutcome<ParticipantsApplicationData, ParticipantsProjection, boolean>;
|
|
32
33
|
export declare function listPresentation(square: OpenSquare, control?: OperationControl): Promise<ListPresentation>;
|
|
33
34
|
export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer?: string): ApplicationOutcome<StatusApplicationData, StatusProjection>;
|
|
35
|
+
export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer: string, presentation: boolean): ApplicationOutcome<StatusApplicationData, StatusProjection, boolean>;
|
|
34
36
|
export declare function eventPresentation(square: OpenSquare, id: ActivityId, control?: OperationControl): Promise<EventPresentation>;
|
|
35
37
|
export declare function catchPresence(square: OpenSquare, control?: OperationControl): Promise<CatchPresence>;
|
|
36
38
|
export declare function streamProjection(square: OpenSquare, cursor: number, recipient?: string, control?: OperationControl): Promise<StreamProjection>;
|
package/dist/views.js
CHANGED
|
@@ -28,7 +28,7 @@ export function parseRequiredActivityId(id) {
|
|
|
28
28
|
return index;
|
|
29
29
|
}
|
|
30
30
|
/** Compile the public query directly to typed selectors; only search hydrates unselected bodies. */
|
|
31
|
-
export function selectHistory(facts, query, limit, kinds) {
|
|
31
|
+
export function selectHistory(facts, query, limit, kinds, presentation = true) {
|
|
32
32
|
if (query.before !== undefined && query.after !== undefined)
|
|
33
33
|
throw new SquareError('invalid_args', 'History cannot combine before and after cursors');
|
|
34
34
|
const afterIndex = query.after === undefined ? undefined : parseRequiredActivityId(query.after);
|
|
@@ -44,14 +44,16 @@ export function selectHistory(facts, query, limit, kinds) {
|
|
|
44
44
|
...(query.at?.length ? { indexes: facts.contextIndexes(query.at.map(parseRequiredActivityId), query.contextBefore ?? 0, query.contextAfter ?? 0) } : {}),
|
|
45
45
|
kinds: search === undefined ? kinds : ['say', 'done'],
|
|
46
46
|
};
|
|
47
|
-
let selected, totalMatches, publicBefore, publicAfter;
|
|
47
|
+
let selected, totalMatches, publicBefore = false, publicAfter = false;
|
|
48
48
|
if (recipient === undefined && search === undefined && limit !== null && limit <= PAGE_SIZE) {
|
|
49
49
|
selected = facts.activityHeads(filter, { edge: afterIndex === undefined ? 'newest' : 'oldest', limit }).items;
|
|
50
|
-
const publicHeads = selected.filter((head) => ARCHIVE.has(head.kind));
|
|
51
|
-
const publicFilter = { ...filter, kinds: [...ARCHIVE] };
|
|
52
50
|
totalMatches = facts.activityCount(filter);
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
if (presentation) {
|
|
52
|
+
const publicHeads = selected.filter((head) => ARCHIVE.has(head.kind));
|
|
53
|
+
const publicFilter = { ...filter, kinds: [...ARCHIVE] };
|
|
54
|
+
publicBefore = publicHeads.length > 0 && facts.activityCount({ ...publicFilter, beforeIndex: publicHeads[0].index }) > 0;
|
|
55
|
+
publicAfter = publicHeads.length > 0 && facts.activityCount({ ...publicFilter, afterIndex: publicHeads.at(-1).index }) > 0;
|
|
56
|
+
}
|
|
55
57
|
}
|
|
56
58
|
else {
|
|
57
59
|
const page = [];
|
|
@@ -68,7 +70,7 @@ export function selectHistory(facts, query, limit, kinds) {
|
|
|
68
70
|
if (matches !== undefined && !matches.has(head.index))
|
|
69
71
|
continue;
|
|
70
72
|
totalMatches++;
|
|
71
|
-
if (ARCHIVE.has(head.kind)) {
|
|
73
|
+
if (presentation && ARCHIVE.has(head.kind)) {
|
|
72
74
|
firstPublic = Math.min(firstPublic, head.index);
|
|
73
75
|
lastPublic = head.index;
|
|
74
76
|
}
|
|
@@ -82,14 +84,16 @@ export function selectHistory(facts, query, limit, kinds) {
|
|
|
82
84
|
cursor = source.items.at(-1).index;
|
|
83
85
|
}
|
|
84
86
|
selected = page;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
if (presentation) {
|
|
88
|
+
const publicHeads = selected.filter((head) => ARCHIVE.has(head.kind));
|
|
89
|
+
publicBefore = publicHeads.length > 0 && firstPublic < publicHeads[0].index;
|
|
90
|
+
publicAfter = publicHeads.length > 0 && lastPublic > publicHeads.at(-1).index;
|
|
91
|
+
}
|
|
88
92
|
}
|
|
89
93
|
const activities = [...facts.activities(selected.map((head) => head.index))];
|
|
90
94
|
if (query.order === 'desc')
|
|
91
95
|
activities.reverse();
|
|
92
|
-
const visibleActivities = activities.filter((activity) => ARCHIVE.has(activity.kind));
|
|
96
|
+
const visibleActivities = presentation ? activities.filter((activity) => ARCHIVE.has(activity.kind)) : [];
|
|
93
97
|
const direction = afterIndex === undefined ? 'before' : 'after';
|
|
94
98
|
const cursor = visibleActivities.length === 0 ? undefined : direction === 'before'
|
|
95
99
|
? Math.min(...visibleActivities.map((activity) => activity.index)) : Math.max(...visibleActivities.map((activity) => activity.index));
|
|
@@ -156,9 +160,9 @@ function classifyPresence(participants, now) {
|
|
|
156
160
|
classificationByName: Object.fromEntries(participants.map((participant) => [participant.name, participant.state === 'done' ? 'done'
|
|
157
161
|
: participant.state === 'not joined' ? 'not-joined' : hereNames.has(participant.name) ? 'here' : 'lingering'])) };
|
|
158
162
|
}
|
|
159
|
-
export function queryParticipants(facts, context) {
|
|
163
|
+
export function queryParticipants(facts, context, presentation = true) {
|
|
160
164
|
const participants = coreParticipants(facts, context.now);
|
|
161
|
-
return { data: { participants }, projection: { ...context, ...classifyPresence(participants, context.now),
|
|
165
|
+
return { data: { participants }, projection: !presentation ? undefined : { ...context, ...classifyPresence(participants, context.now),
|
|
162
166
|
standingCount: inSquareCount(facts), held: facts.hold().active } };
|
|
163
167
|
}
|
|
164
168
|
export async function listPresentation(square, control) {
|
|
@@ -170,30 +174,34 @@ export async function listPresentation(square, control) {
|
|
|
170
174
|
return { context: facts.header().preamble, participants, activities: facts.activityCount({ kinds: ['say'] }) };
|
|
171
175
|
}, control?.signal)).value;
|
|
172
176
|
}
|
|
173
|
-
export function queryStatus(facts, context, viewer = '') {
|
|
174
|
-
const
|
|
175
|
-
const header = facts.header(), hold = facts.hold();
|
|
177
|
+
export function queryStatus(facts, context, viewer = '', presentation = true) {
|
|
178
|
+
const participants = coreParticipants(facts, context.now);
|
|
179
|
+
const header = facts.header(), hold = facts.hold(), standingCount = inSquareCount(facts);
|
|
176
180
|
const latest = facts.activityHeads({ kinds: ['say', 'done'] }, { edge: 'newest', limit: 1 }).items[0];
|
|
177
181
|
const latestAct = latest === undefined ? undefined : facts.activity(latest.index);
|
|
182
|
+
const data = {
|
|
183
|
+
hardCap: header.hardCap,
|
|
184
|
+
...(header.throttlePerMinute === undefined ? {} : { throttlePerMinute: header.throttlePerMinute }),
|
|
185
|
+
activeCount: standingCount, doneCount: participants.filter((p) => p.state === 'done').length,
|
|
186
|
+
hold: hold.active ? {
|
|
187
|
+
...(hold.actor === undefined ? {} : { by: hold.actor }),
|
|
188
|
+
...(hold.reason === undefined ? {} : { reason: hold.reason }),
|
|
189
|
+
...(hold.at === undefined ? {} : { at: hold.at }),
|
|
190
|
+
} : null,
|
|
191
|
+
participants: participants.filter((p) => p.state !== 'not joined'), now: context.now,
|
|
192
|
+
...(latestAct === undefined || latestAct.actor === undefined || latestAct.kind === 'read' ? {} : { latestActivity: toPublicActivity(latestAct) }),
|
|
193
|
+
};
|
|
194
|
+
if (!presentation)
|
|
195
|
+
return { data, projection: undefined };
|
|
178
196
|
const afterIndex = latestAct?.index ?? -1;
|
|
179
197
|
const joins = facts.activities(facts.activityHeads({ kinds: ['join'], afterIndex }, { edge: 'newest', limit: 3 }).items.map((h) => h.index)).map((activity) => ({ activity,
|
|
180
198
|
rejoined: facts.activityCount({ kinds: ['done'], actors: [activity.actor], actorMatch: 'exact', beforeIndex: activity.index }) > 0 }));
|
|
181
199
|
const canonicalViewer = facts.participant(viewer)?.name ?? viewer;
|
|
182
200
|
return {
|
|
183
|
-
data
|
|
184
|
-
hardCap: header.hardCap,
|
|
185
|
-
...(header.throttlePerMinute === undefined ? {} : { throttlePerMinute: header.throttlePerMinute }),
|
|
186
|
-
activeCount: projection.standingCount, doneCount: participants.filter((p) => p.state === 'done').length,
|
|
187
|
-
hold: hold.active ? {
|
|
188
|
-
...(hold.actor === undefined ? {} : { by: hold.actor }),
|
|
189
|
-
...(hold.reason === undefined ? {} : { reason: hold.reason }),
|
|
190
|
-
...(hold.at === undefined ? {} : { at: hold.at }),
|
|
191
|
-
} : null,
|
|
192
|
-
participants: participants.filter((p) => p.state !== 'not joined'), now: context.now,
|
|
193
|
-
...(latestAct === undefined || latestAct.actor === undefined || latestAct.kind === 'read' ? {} : { latestActivity: toPublicActivity(latestAct) }),
|
|
194
|
-
},
|
|
201
|
+
data,
|
|
195
202
|
projection: {
|
|
196
|
-
...
|
|
203
|
+
...context, ...classifyPresence(participants, context.now), standingCount, held: hold.active,
|
|
204
|
+
knownParticipantCount: participants.length, viewer: canonicalViewer,
|
|
197
205
|
...(canonicalViewer === '' ? {} : { participant: canonicalViewer }),
|
|
198
206
|
joins, joinCount: facts.activityCount({ kinds: ['join'], afterIndex }),
|
|
199
207
|
attentionNames: participants.filter((p) => canonicalViewer === '' || nameKey(p.name) === nameKey(canonicalViewer)).map((p) => p.name),
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Application presentation demand
|
|
2
|
+
|
|
3
|
+
## Design
|
|
4
|
+
|
|
5
|
+
`createSquareApplication(context)` retains complete terminal-facing outcomes. MCP uses `createSquareApplication(context, { presentation: false })`; its outcome has the same `data` contract and `projection: undefined`.
|
|
6
|
+
|
|
7
|
+
This is one explicit result demand, not a second business implementation. The application still owns admission, retries, receipts, cancellation and delivery. Ordinary early returns and conditional expressions skip presentation work where it was previously performed; there is no deferred projector, registry, cache or resource-lifetime change. CLI projection types remain required, while the data-only factory overload exposes their absence explicitly.
|
|
8
|
+
|
|
9
|
+
MCP no longer requests join history/perceptions, the pre-expression directed feed, catch presence enrichment, listener context reads, lifecycle event rereads, or history/status/participants presentation-only annotations. Selection/counts required for results and history limits remain. For terminal expressions the directed feed is still captured once **before** landing, not after sending or on each retry. Delivery and postcommit publication remain on the original path.
|
|
10
|
+
|
|
11
|
+
Two same-operation observations are consolidated:
|
|
12
|
+
|
|
13
|
+
- Catch resolves caller and all sender filters in one typed-fact read, preserving caller-first validation, canonical error subjects, filter order and known-but-departed senders. The actual selection/receipt transaction, ownership admission and lease fences remain separate and current.
|
|
14
|
+
- Expression's known-name check runs inside the existing join preview via an internal `requireKnown` admission option. Unknown names still fail before claiming or mutating; departed participants still reenter once. Ordinary join may still create a name. Preview facts never replace the subsequent ownership/CAS or commit-time checks.
|
|
15
|
+
|
|
16
|
+
Long-lived MCP connections and concurrent delivery are deliberately outside this change.
|
|
17
|
+
|
|
18
|
+
## Local MCP measurement
|
|
19
|
+
|
|
20
|
+
Baseline: `31cc1bf`. Candidate: the presentation-demand and observation changes above. Node v26.10.0. A real MCP client used the SDK's in-memory transport, not a direct call to application helpers. Each call still creates its application and opens/closes the artifact.
|
|
21
|
+
|
|
22
|
+
Three runs per variant, alternating B1/A1/B2/A2/B3/A3. Each process seeds four participants and 120 2,048-byte historical says outside measurement, joins the probe participant, then runs three warmup cycles and twenty measured cycles of repeated join, express, two empty filtered catches, history and status. Harness session keys are cleared: the probe's `reconnect` label means a repeated no-op join (`kind: joined`), not a real harness ownership reconnect. New measured expressions are small (`probe body`) and use force without pending directed feed; catches are empty. This is not a bulk-payload, ownership-contention or concurrent benchmark. No test suite, build or second benchmark ran alongside the six probes.
|
|
23
|
+
|
|
24
|
+
Wall times below are the median of three run-level p50s, each from twenty measured calls. Small local timings are illustrative, not a production SLA or the earlier 300-process catch-p95 workload. The stable transaction/statement counts establish removed work more directly.
|
|
25
|
+
|
|
26
|
+
| MCP operation | Wall p50 before → after (ms) | Artifact read transactions/call | SQL prepares/call |
|
|
27
|
+
| --- | ---: | ---: | ---: |
|
|
28
|
+
| Repeated no-op join | 1.478 → 1.036 | 3 → 2 | 30 → 15 |
|
|
29
|
+
| Express | 3.963 → 3.372 | 6 → 4 | 79 → 61 |
|
|
30
|
+
| Empty catch, one sender filter | 3.157 → 1.613 | 5 → 3 | 124 → 40 |
|
|
31
|
+
| Empty catch, three sender filters | 3.335 → 1.655 | 7 → 3 | 134 → 42 |
|
|
32
|
+
| History | 1.582 → 0.808 | 2 → 2 | 52 → 16 |
|
|
33
|
+
| Status | 2.344 → 2.093 | 2 → 2 | 103 → 94 |
|
|
34
|
+
|
|
35
|
+
Sender lookups still scale with filter count; **transactions do not**. No participant validation has been omitted to obtain that result. Repeated join's body-hydrating row reads fall from ten to zero per call. History still hydrates its ten result rows, and both empty catches hydrate none. Express and status each retain one body-hydrating row in this probe; not all body reads are presentation work.
|
|
36
|
+
|
|
37
|
+
SQLite counters are attributed by canonical database location. Host-ledger/lock database work is counted separately, never attributed to the artifact. A positive calibration observes one port read as one artifact read transaction and `activities([0,1])` as two hydrated rows. History moves the same hydration counters used for the zero checks. Hydrated bytes in the probe mean returned string/blob bytes, not filesystem I/O.
|
|
38
|
+
|
|
39
|
+
All six probes passed their calibration, first-join, hydration and no-error controls: 720 measured calls plus six first joins have no recorded errors. Warmup results are not retained or included in that gate. Raw per-call records retain timing/counters/errors but not complete returned payloads; each operation also retains its first measured result summary. Public-data/error parity is established by regression tests, not by those no-error benchmark controls. Recorded compiled-module hashes were checked before and after each probe; the probe script itself was not included in those historical hash manifests. Independent review recomputed the raw timing/counter statistics and confirmed the stated measurement limits.
|
|
40
|
+
|
|
41
|
+
## Regression evidence
|
|
42
|
+
|
|
43
|
+
- New tests prove MCP avoids the extra presentation reads, while terminal-facing join/express still hydrate real presentation content and express retains its pre-send feed.
|
|
44
|
+
- A body-bearing catch hydrates exactly its selected page; empty catch and archive/status/participants data parity remain intact.
|
|
45
|
+
- One versus multiple catch filters has the same read-transaction count; canonical caller/filter errors remain distinct.
|
|
46
|
+
- Both new tests fail against the unchanged baseline at the intended hydration/transaction assertions.
|
|
47
|
+
- The existing CLI/application/MCP activity/receipt parity, never-joined refusal, departed reentry, metadata ownership, cancellation and postcommit behavior tests pass.
|
|
48
|
+
- Full suite: 680 passed, one opt-in Claude runtime test skipped. CLI process suite: 48 passed. Independent static review accepted correctness and control-flow clarity.
|
|
49
|
+
|
|
50
|
+
Local evidence: `/tmp/square-upper-{before,after}-{1,2,3}.{json,log,exit,sha,integrity}`. Probe: `/tmp/square-mcp-demand-bench.mjs`. Test logs: `/tmp/square-upper-{tests,cli,negative}.log`.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Catch cold-path optimization
|
|
2
|
+
|
|
3
|
+
## Result and scope
|
|
4
|
+
|
|
5
|
+
Under the existing 300-process Pi-session workload, first-catch API p95 falls by **60.4% / 61.4%** for histories of 1,800 / 18,000 activities. These are reductions between the medians of three run-level p95s, not a guarantee for every run or operation. Whole catch-phase p95 falls by about **56%** for both histories.
|
|
6
|
+
|
|
7
|
+
The comparison is against `90752ba`, the current production implementation plus the same instrumentation, not an older, slower implementation. The benchmark represents Pi sessions without a Paseo route; it does not establish equal gains for Codex/Paseo sessions, emoji-only names, or native notification delivery.
|
|
8
|
+
|
|
9
|
+
## Two changes
|
|
10
|
+
|
|
11
|
+
- `model.ts`: try the existing Unicode text-name grammar before the full RGI emoji grammar. It accepts letters, numbers, marks excluding variation selectors, hyphens, underscores, and nonempty slash-separated segments. It is an exact subset of the original grammar; the original full matcher remains the fallback. `NAME_GRAPHEME_SOURCE`, also used by mention scanning, retains its original value.
|
|
12
|
+
- `participant-host.ts`: before discovering installed adapters, use the existing route resolver to check whether any candidate exists. No host ledger or no possible candidate means no route, without importing adapters that cannot be used. Real candidates still pass through the real capability check. Paseo precedence, Codex fallback, harness identity, and publication failure isolation remain unchanged.
|
|
13
|
+
|
|
14
|
+
CPU sampling of the baseline pointed to cold name validation inside reader transactions and optional adapter module loading during catch. The changes remove that work rather than prewarm it or move it outside measurement. The sampling and this combined experiment do not establish each change's individual contribution.
|
|
15
|
+
|
|
16
|
+
SQLite storage, DELETE journaling, FULL synchronous durability, transaction validation, ownership/epoch fences, receipts, and retry policy are unchanged. Earlier lock/backoff/cache experiments did not show the required improvement and are not included. The earlier [COMMIT-gap investigation](catch-lock-investigation.md) measured preparation inside writers; it did not bound costs inside concurrent readers or optional postcommit module loading.
|
|
17
|
+
|
|
18
|
+
## Controlled comparison
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 300 --mode session --active --no-hints
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Node v26.10.0. Three runs of each variant, ordered B1, A1, B2, A2, B3, A3. Each command covers both histories. Baseline worktree: `/tmp/square-half-baseline-final`, detached at `90752ba`; candidate: the main worktree with the two changes above. Benchmark scripts were byte-identical. The changed compiled modules and benchmark script were hashed before and checked after every command. No other benchmark, test suite, build, or profile ran alongside the sequence; read-only review and test-source editing did occur. This is a local, alternating experiment, not a randomized or controlled-host latency guarantee.
|
|
25
|
+
|
|
26
|
+
The benchmark workload is unchanged: 300 processes, 2,048-byte historical bodies, a five-second idle window, bell discovery without filesystem hints, then open, recognize, first catch, empty catch, and close. First-catch timing covers the complete participant catch API. Whole-phase timing includes all those catch-phase steps; it is not one catch call.
|
|
27
|
+
|
|
28
|
+
### Median of three run-level measurements
|
|
29
|
+
|
|
30
|
+
| Metric | Before, 1,800 | After, 1,800 | Reduction | Before, 18,000 | After, 18,000 | Reduction |
|
|
31
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
32
|
+
| First catch API p95 (ms) | 3,923.86 | 1,554.02 | 60.4% | 3,568.86 | 1,378.74 | 61.4% |
|
|
33
|
+
| Whole catch phase p95 (ms) | 4,354.45 | 1,924.30 | 55.8% | 3,907.48 | 1,723.48 | 55.9% |
|
|
34
|
+
| Empty catch API p95 (ms) | 58.49 | 56.60 | 3.2% | 82.22 | 54.84 | 33.3% |
|
|
35
|
+
| Aggregate catch-phase CPU (ms) | 15,643.35 | 7,184.01 | 54.1% | 15,274.12 | 6,515.09 | 57.3% |
|
|
36
|
+
|
|
37
|
+
### Individual first-catch p95s
|
|
38
|
+
|
|
39
|
+
| History | Before B1 / B2 / B3 (ms) | After A1 / A2 / A3 (ms) |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| 1,800 | 4,044.22 / 3,404.54 / 3,923.86 | 1,544.62 / 1,554.02 / 1,720.00 |
|
|
42
|
+
| 18,000 | 3,024.11 / 3,568.86 / 3,759.82 | 1,343.00 / 1,824.53 / 1,378.74 |
|
|
43
|
+
|
|
44
|
+
The second large-history pair improves by 48.9%, so the evidence does **not** say every paired run exceeded 50%. Both median reductions exceed 50%, and every candidate first-catch p95 is below every baseline p95 in its history group.
|
|
45
|
+
|
|
46
|
+
## Correctness evidence
|
|
47
|
+
|
|
48
|
+
All six commands exited successfully with empty stderr. Each of the twelve scenarios had 300 ready/completed workers and discovered all 300 participants. The raw per-worker reports were separately checked, including `remaining` (which the benchmark's built-in success gate does not assert): every worker consumed exactly one activity, had zero remaining, and consumed nothing again. That is 3,600 successful worker outcomes.
|
|
49
|
+
|
|
50
|
+
Every scenario retained 300 hydrated bodies, 600 SQL writes, 300 committed artifact writes and 1,800 committed artifact reads. The recorded COMMIT execution-plus-gap invariant held. No workload or instrumentation was removed to obtain the reduction.
|
|
51
|
+
|
|
52
|
+
Focused regression coverage preserves explicit expected acceptance/rejection for Unicode text, complete emoji, slash boundaries, selectors, joiners and malformed strings. A fresh-process module-loading test verifies that no-candidate drafting imports neither adapter, and that real Codex and Pi-plus-Paseo candidates still resolve through installed adapters.
|
|
53
|
+
|
|
54
|
+
`npm test` passed (678 passed, one opt-in runtime test skipped); the CLI process suite passed all 48 tests. Running the new module-loading test against the unchanged baseline fails at its zero-import assertion (two adapters loaded), providing a negative control. Rebuilding for regression tests preserved the measured production module hashes. An independent review recomputed p95s from raw worker timings and verified the reported medians and outcomes.
|
|
55
|
+
|
|
56
|
+
Local raw evidence: `/tmp/square-half-final-{before,after}-{1,2,3}.{json,err,exit,sha,integrity}`. Summary and per-worker assertion audit: `/tmp/square-half-final-summary.{py,json}`. The earlier pilot is excluded from the table.
|