cabloy 5.1.221 → 5.1.222

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 (52) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/package.json +1 -1
  4. package/repo-agent-governance/managed-assets.json +15 -15
  5. package/repo-agent-governance/policies/repository.md +1 -0
  6. package/repo-agent-governance/skills/cabloy-backend-scaffold/SKILL.md +5 -1
  7. package/repo-agent-governance/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +7 -5
  8. package/repo-agent-governance/skills/cabloy-frontend-scaffold/SKILL.md +5 -1
  9. package/repo-agent-governance/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +6 -4
  10. package/repo-docs/backend/unit-testing.md +5 -1
  11. package/repo-docs/frontend/scripts.md +8 -0
  12. package/vona/packages-cli/cabloy-cli/package.json +1 -1
  13. package/vona/packages-cli/cli/package.json +1 -1
  14. package/vona/packages-cli/cli-set-api/package.json +1 -1
  15. package/vona/packages-utils/process-helper/package.json +1 -1
  16. package/vona/packages-utils/process-helper/src/index.test.ts +62 -0
  17. package/vona/packages-utils/process-helper/src/index.ts +9 -4
  18. package/vona/packages-vona/vona/package.json +1 -1
  19. package/vona/packages-vona/vona-core/package.json +1 -1
  20. package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.test.ts +558 -0
  21. package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.ts +389 -0
  22. package/vona/packages-vona/vona-core/src/lib/core/context.ts +3 -0
  23. package/vona/packages-vona/vona-core/src/lib/core/index.ts +11 -0
  24. package/vona/packages-vona/vona-core/src/lib/core/meta.ts +5 -0
  25. package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +10 -0
  26. package/vona/packages-vona/vona-core/src/lib/framework/start.ts +8 -0
  27. package/vona/packages-vona/vona-mock/package.json +1 -1
  28. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  29. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  30. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +124 -4
  31. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +7 -0
  32. package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExisting.test.ts +423 -0
  33. package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExistingCommit.test.ts +174 -0
  34. package/vona/src/suite-vendor/a-vona/modules/a-instance/package.json +1 -1
  35. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/bean/bean.instance.ts +3 -1
  36. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/lib/existingReadyAdmission.ts +125 -0
  37. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/service/instance.ts +72 -25
  38. package/vona/src/suite-vendor/a-vona/modules/a-instance/test/existingReadyAdmission.test.ts +949 -0
  39. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  40. package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +61 -59
  41. package/vona/src/suite-vendor/a-vona/modules/a-mail/package.json +1 -1
  42. package/vona/src/suite-vendor/a-vona/modules/a-mail/src/service/mail.ts +5 -0
  43. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  44. package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +61 -2
  45. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  46. package/zova/package.original.json +0 -1
  47. package/zova/packages-cli/cli/package.json +4 -4
  48. package/zova/packages-cli/cli-set-front/package.json +2 -2
  49. package/zova/packages-zova/zova/package.json +2 -2
  50. package/zova/pnpm-lock.yaml +7 -78
  51. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  52. package/zova/src/suite-vendor/a-zova/package.json +2 -2
@@ -0,0 +1,949 @@
1
+ import type { ExistingReadyCapability, IInstanceRecord } from 'vona';
2
+
3
+ import assert from 'node:assert/strict';
4
+ import { AsyncLocalStorage } from 'node:async_hooks';
5
+ import { describe, it } from 'node:test';
6
+ import { setImmediate as nextTurn } from 'node:timers/promises';
7
+ import {
8
+ beginCompletedInstance,
9
+ captureCompletedInstance,
10
+ captureRequestBoundary,
11
+ createCompletedReadiness,
12
+ createRequestBoundary,
13
+ finishCompletedInstance,
14
+ getRequestBoundary,
15
+ invalidateCompletedInstance,
16
+ isExistingReadyContext,
17
+ pinExistingReadyContext,
18
+ resolveExistingReadyCapability,
19
+ runWithRequestBoundary,
20
+ } from 'vona';
21
+
22
+ import type { ExistingReadyResult } from '../src/lib/existingReadyAdmission.ts';
23
+
24
+ import { BeanInstance } from '../src/bean/bean.instance.ts';
25
+ import { createExistingReadyAdmissionForTest } from '../src/lib/existingReadyAdmission.ts';
26
+ import { ServiceInstance } from '../src/service/instance.ts';
27
+
28
+ // Fixture-only internal access; the canonical URL keeps the same owner WeakMaps as 'vona'.
29
+ // Do not publish provisioning or capability issuance through the framework barrel for tests.
30
+ const completedReadinessUrl = new URL(
31
+ '../../../../../../packages-vona/vona-core/src/lib/core/completedReadiness.ts',
32
+ import.meta.url,
33
+ );
34
+ const {
35
+ beginCompletedApplication,
36
+ closeCompletedReadiness,
37
+ finishCompletedApplication,
38
+ getCompletedPublication,
39
+ getExistingReadyContextConfig,
40
+ issueExistingReadyCapability,
41
+ matchesCompletedRow,
42
+ provisionCompletedInstanceForTest,
43
+ } = await import(completedReadinessUrl.href);
44
+ const admissionInternals = {
45
+ getCompletedPublication,
46
+ issueExistingReadyCapability,
47
+ matchesCompletedRow,
48
+ };
49
+
50
+ const name = '';
51
+ type InstanceName = keyof IInstanceRecord;
52
+ type Row = {
53
+ id: number;
54
+ name: string;
55
+ disabled: boolean;
56
+ isolate: boolean;
57
+ config: string | null;
58
+ };
59
+
60
+ function row(overrides: Partial<Row> = {}): Row {
61
+ return { id: 41, name, disabled: false, isolate: false, config: null, ...overrides };
62
+ }
63
+
64
+ function barrier() {
65
+ let release!: () => void;
66
+ const promise = new Promise<void>(resolve => {
67
+ release = resolve;
68
+ });
69
+ return { promise, release };
70
+ }
71
+
72
+ // Observation expiry never settles the original work. Every held task is released and joined.
73
+ function observe<T>(promise: Promise<T>): Promise<T> {
74
+ return new Promise((resolve, reject) => {
75
+ const timer = setTimeout(() => reject(new Error('admission_observation_expired')), 1_000);
76
+ promise.then(
77
+ value => {
78
+ clearTimeout(timer);
79
+ resolve(value);
80
+ },
81
+ error => {
82
+ clearTimeout(timer);
83
+ reject(error);
84
+ },
85
+ );
86
+ });
87
+ }
88
+
89
+ function withinBoundary<T>(fn: () => T): T {
90
+ return runWithRequestBoundary(createRequestBoundary(), fn);
91
+ }
92
+
93
+ function capability(result: ExistingReadyResult): ExistingReadyCapability {
94
+ assert.equal(result.outcome, 'ready');
95
+ if (result.outcome !== 'ready') {
96
+ throw new Error('ready_control_required');
97
+ }
98
+ assert.equal(Object.isFrozen(result), true);
99
+ assert.equal(Object.isFrozen(result.capability), true);
100
+ return result.capability;
101
+ }
102
+
103
+ function controlled() {
104
+ const appIdentity = {};
105
+ const owner = createCompletedReadiness(appIdentity);
106
+ const registry = Object.freeze({});
107
+ const route = Object.freeze({});
108
+ const handle = Object.freeze({});
109
+ const calls: string[] = [];
110
+ let time = 100;
111
+ let read: (selected: string) => unknown | Promise<unknown> = () => ({
112
+ outcome: 'found',
113
+ row: row(),
114
+ });
115
+ provisionCompletedInstanceForTest(owner, name, { registry, route, handle, isolate: false });
116
+ const applicationAttempt = beginCompletedApplication(owner);
117
+ finishCompletedApplication(owner, applicationAttempt, true);
118
+ function publish(value: Row = row(), config: object = { proof: { generation: 1 } }) {
119
+ const attempt = beginCompletedInstance(owner, name);
120
+ const captured = captureCompletedInstance(owner, name, attempt, value, config);
121
+ assert.ok(captured);
122
+ finishCompletedInstance(owner, name, attempt, captured);
123
+ const publication = getCompletedPublication(owner, name, registry, route);
124
+ assert.ok(publication);
125
+ return publication;
126
+ }
127
+ const dependencies = {
128
+ ...admissionInternals,
129
+ owner,
130
+ registry,
131
+ route,
132
+ read(selected: string) {
133
+ calls.push(selected);
134
+ return read(selected);
135
+ },
136
+ now: () => time,
137
+ };
138
+ const admission = createExistingReadyAdmissionForTest(dependencies);
139
+ publish();
140
+ return {
141
+ appIdentity,
142
+ owner,
143
+ registry,
144
+ route,
145
+ handle,
146
+ calls,
147
+ admission,
148
+ dependencies,
149
+ publish,
150
+ setTime(value: number) {
151
+ time = value;
152
+ },
153
+ setRead(fn: typeof read) {
154
+ read = fn;
155
+ },
156
+ };
157
+ }
158
+
159
+ // These are local objects, not another Vona application or container-managed singletons.
160
+ // The real lifecycle methods and async.queue run against inert, test-owned collaborators.
161
+ function localLifecycle() {
162
+ const identity = {};
163
+ const owner = createCompletedReadiness(identity);
164
+ const registry = {};
165
+ const route = {};
166
+ const handle = {};
167
+ const rows = new Map<string, Row>([
168
+ [
169
+ name,
170
+ row({
171
+ config: JSON.stringify({
172
+ proof: { db: 1, shared: 'db', nested: { db: 1 } },
173
+ }),
174
+ }),
175
+ ],
176
+ ]);
177
+ const counts = { reads: 0, startups: 0, contexts: 0, broadcasts: 0 };
178
+ let onRead: () => Promise<void> = async () => {};
179
+ let onStartup: () => Promise<void> = async () => {};
180
+ let onContext: () => Promise<void> = async () => {};
181
+ const localApp = {
182
+ meta: {
183
+ completedReadiness: owner,
184
+ appReady: true,
185
+ appReadyInstances: {} as Record<string, boolean>,
186
+ scopeContainer: {
187
+ startup: {
188
+ service: {
189
+ startup: {
190
+ async runStartupInstance() {
191
+ counts.startups++;
192
+ await onStartup();
193
+ },
194
+ },
195
+ },
196
+ },
197
+ },
198
+ },
199
+ config: {
200
+ instance: {
201
+ instances: {
202
+ [name]: { config: { proof: { base: 1, shared: 'base', nested: { base: 1 } } } },
203
+ },
204
+ },
205
+ proof: { global: 1, shared: 'global', nested: { global: 1 } },
206
+ },
207
+ currentContext: {} as any,
208
+ bean: {} as any,
209
+ throw(status: number): never {
210
+ throw new Error(`local_status_${status}`);
211
+ },
212
+ };
213
+ const service = new ServiceInstance();
214
+ const bean = new BeanInstance();
215
+ const scope = {
216
+ model: {
217
+ instance: {
218
+ async get(where: { name: string }) {
219
+ counts.reads++;
220
+ await onRead();
221
+ return rows.get(where.name);
222
+ },
223
+ },
224
+ },
225
+ service: { instance: service },
226
+ broadcast: {
227
+ reload: {
228
+ emit() {
229
+ counts.broadcasts++;
230
+ },
231
+ },
232
+ },
233
+ };
234
+ function attach<T extends ServiceInstance | BeanInstance>(value: T): T {
235
+ Object.defineProperties(value, {
236
+ app: { value: localApp },
237
+ scope: { value: scope },
238
+ });
239
+ return value;
240
+ }
241
+ attach(service);
242
+ attach(bean);
243
+ localApp.bean = {
244
+ instance: bean,
245
+ executor: {
246
+ async newCtx(fn: () => Promise<unknown>) {
247
+ counts.contexts++;
248
+ await onContext();
249
+ return fn();
250
+ },
251
+ },
252
+ };
253
+ localApp.currentContext = { app: localApp, instanceName: name };
254
+ const applicationAttempt = beginCompletedApplication(owner);
255
+ finishCompletedApplication(owner, applicationAttempt, true);
256
+ function provision(selected = name) {
257
+ provisionCompletedInstanceForTest(owner, selected, { registry, route, handle, isolate: false });
258
+ }
259
+ function publication(selected = name) {
260
+ return getCompletedPublication(owner, selected, registry, route);
261
+ }
262
+ const admission = createExistingReadyAdmissionForTest({
263
+ ...admissionInternals,
264
+ owner,
265
+ registry,
266
+ route,
267
+ now: () => 100,
268
+ read: selected => ({ outcome: 'found', row: rows.get(selected) }),
269
+ });
270
+ return {
271
+ identity,
272
+ owner,
273
+ registry,
274
+ route,
275
+ handle,
276
+ localApp,
277
+ rows,
278
+ counts,
279
+ service,
280
+ bean,
281
+ admission,
282
+ provision,
283
+ publication,
284
+ anotherService: () => attach(new ServiceInstance()),
285
+ setRead(fn: typeof onRead) {
286
+ onRead = fn;
287
+ },
288
+ setStartup(fn: typeof onStartup) {
289
+ onStartup = fn;
290
+ },
291
+ setContext(fn: typeof onContext) {
292
+ onContext = fn;
293
+ },
294
+ };
295
+ }
296
+
297
+ // Controlled-provider qualification only: no installed registry driver or consumer is asserted.
298
+ // The managed runner, not this suite, owns application startup and shutdown.
299
+ describe('existing-ready admission qualification', { concurrency: false, timeout: 120_000 }, () => {
300
+ it(
301
+ 'keeps each queued preparation and startup in its caller async context',
302
+ { timeout: 15_000 },
303
+ async () => {
304
+ const f = localLifecycle();
305
+ f.provision();
306
+ const contexts = new AsyncLocalStorage<object>();
307
+ const databases = new AsyncLocalStorage<object>();
308
+ const transactions = new AsyncLocalStorage<object>();
309
+ Object.defineProperty(f.localApp, 'currentContext', { get: () => contexts.getStore() });
310
+ const firstScope = { context: {}, database: {}, transaction: {} };
311
+ const secondScope = { context: {}, database: {}, transaction: {} };
312
+ const reads: ReturnType<typeof current>[] = [];
313
+ const startups: ReturnType<typeof current>[] = [];
314
+ function current() {
315
+ return {
316
+ context: f.localApp.currentContext,
317
+ database: databases.getStore(),
318
+ transaction: transactions.getStore(),
319
+ };
320
+ }
321
+ f.setRead(async () => {
322
+ const scope = current();
323
+ reads.push(scope);
324
+ const source = scope.database === firstScope.database ? 'first' : 'second';
325
+ f.rows.set(name, row({ config: JSON.stringify({ proof: { source } }) }));
326
+ });
327
+ const entered = barrier();
328
+ const held = barrier();
329
+ f.setStartup(async () => {
330
+ startups.push(current());
331
+ if (startups.length === 1) {
332
+ entered.release();
333
+ await held.promise;
334
+ }
335
+ });
336
+ function start(scope: typeof firstScope) {
337
+ return contexts.run(scope.context, () =>
338
+ databases.run(scope.database, () =>
339
+ transactions.run(scope.transaction, () =>
340
+ f.anotherService().instanceStartup(name, { force: true }),
341
+ ),
342
+ ),
343
+ );
344
+ }
345
+ const first = start(firstScope);
346
+ let second: Promise<unknown> | undefined;
347
+ try {
348
+ await observe(entered.promise);
349
+ second = start(secondScope);
350
+ await nextTurn();
351
+ assert.equal(reads.length, 1, 'the second preparation must remain queued');
352
+ } finally {
353
+ held.release();
354
+ await Promise.all([first, ...(second ? [second] : [])]);
355
+ }
356
+ for (const observed of [reads, startups]) {
357
+ assert.equal(observed.length, 2);
358
+ for (const [index, expected] of [firstScope, secondScope].entries()) {
359
+ assert.equal(observed[index].context, expected.context);
360
+ assert.equal(observed[index].database, expected.database);
361
+ assert.equal(observed[index].transaction, expected.transaction);
362
+ }
363
+ }
364
+ assert.equal((f.service.getConfig(name) as any).proof.source, 'second');
365
+ assert.ok(f.publication());
366
+ assert.equal(contexts.getStore(), undefined);
367
+ assert.equal(databases.getStore(), undefined);
368
+ assert.equal(transactions.getStore(), undefined);
369
+ },
370
+ );
371
+
372
+ it(
373
+ '1 requires a healthy request boundary and freshly matches every registry identity field',
374
+ { timeout: 15_000 },
375
+ async () => {
376
+ const f = controlled();
377
+ assert.equal(getRequestBoundary(), undefined);
378
+ assert.deepEqual(await f.admission.admit(name), { outcome: 'boundary_required' });
379
+ assert.equal(f.calls.length, 0);
380
+ withinBoundary(() => {
381
+ assert.throws(
382
+ () => createExistingReadyAdmissionForTest(f.dependencies),
383
+ /lifecycle boundary/,
384
+ );
385
+ assert.throws(
386
+ () =>
387
+ provisionCompletedInstanceForTest(f.owner, name, {
388
+ registry: f.registry,
389
+ route: f.route,
390
+ handle: f.handle,
391
+ isolate: false,
392
+ }),
393
+ /lifecycle boundary/,
394
+ );
395
+ });
396
+ await withinBoundary(async () => {
397
+ captureRequestBoundary()!.reportFailure('diagnostic_failed');
398
+ assert.deepEqual(await f.admission.admit(name), { outcome: 'boundary_required' });
399
+ });
400
+ assert.equal(f.calls.length, 0);
401
+ for (const invalid of [null, 3, {}, '\0', '\uD800', 'x'.repeat(257)]) {
402
+ assert.deepEqual(await withinBoundary(() => f.admission.admit(invalid as string)), {
403
+ outcome: 'invalid_input',
404
+ });
405
+ }
406
+ assert.equal(f.calls.length, 0);
407
+ for (const deadline of [Number.NaN, Infinity, -Infinity]) {
408
+ assert.deepEqual(await withinBoundary(() => f.admission.admit(name, deadline)), {
409
+ outcome: 'invalid_input',
410
+ });
411
+ }
412
+ assert.deepEqual(await withinBoundary(() => f.admission.admit(name, 100)), {
413
+ outcome: 'expired',
414
+ });
415
+ assert.equal(f.calls.length, 0);
416
+
417
+ const first = capability(await withinBoundary(() => f.admission.admit(name)));
418
+ assert.equal(resolveExistingReadyCapability(f.owner, first, f.appIdentity)?.handle, f.handle);
419
+ assert.equal(resolveExistingReadyCapability(f.owner, first, {}), undefined);
420
+ assert.deepEqual(f.calls, [name]);
421
+ const unavailable: unknown[] = [
422
+ undefined,
423
+ { outcome: 'absent' },
424
+ { outcome: 'duplicate', rows: [row(), row()] },
425
+ { outcome: 'found', row: row({ disabled: true }) },
426
+ { outcome: 'found', row: row({ id: 42 }) },
427
+ { outcome: 'found', row: row({ name: 'other' }) },
428
+ { outcome: 'found', row: row({ config: '{}' }) },
429
+ { outcome: 'found', row: row({ isolate: true }) },
430
+ ];
431
+ for (const result of unavailable) {
432
+ const before = f.calls.length;
433
+ f.setRead(() => result);
434
+ assert.deepEqual(await withinBoundary(() => f.admission.admit(name)), {
435
+ outcome: 'unavailable',
436
+ });
437
+ assert.deepEqual(await withinBoundary(() => f.admission.recheck(first)), {
438
+ outcome: 'unavailable',
439
+ });
440
+ assert.equal(
441
+ f.calls.length,
442
+ before + 2,
443
+ 'admission and recheck must each read, not reuse a cached success',
444
+ );
445
+ }
446
+ f.setRead(() => ({ outcome: 'found', row: row() }));
447
+ const again = await withinBoundary(() => f.admission.recheck(first));
448
+ assert.equal(capability(again), first);
449
+ const before = f.calls.length;
450
+ for (const identities of [{ registry: {} }, { route: {} }]) {
451
+ const other = createExistingReadyAdmissionForTest({ ...f.dependencies, ...identities });
452
+ assert.deepEqual(await withinBoundary(() => other.admit(name)), { outcome: 'unavailable' });
453
+ }
454
+ assert.deepEqual(await withinBoundary(() => f.admission.admit('unknown')), {
455
+ outcome: 'unavailable',
456
+ });
457
+ assert.equal(f.calls.length, before);
458
+ f.setTime(1_100);
459
+ assert.equal(resolveExistingReadyCapability(f.owner, first), undefined);
460
+ assert.deepEqual(await withinBoundary(() => f.admission.recheck(first)), {
461
+ outcome: 'unavailable',
462
+ });
463
+ assert.equal(f.calls.length, before);
464
+ },
465
+ );
466
+
467
+ it(
468
+ '2 keeps the zero-queue reservation until held work joins after expiry, generation change, close or port fault',
469
+ { timeout: 15_000 },
470
+ async () => {
471
+ for (const change of ['expiry', 'generation', 'close', 'portFault'] as const) {
472
+ const f = controlled();
473
+ const entered = barrier();
474
+ const held = barrier();
475
+ let settled = false;
476
+ const boundary = createRequestBoundary();
477
+ let fault!: () => void;
478
+ f.setRead(async () => {
479
+ fault = () => captureRequestBoundary()!.reportFailure('late_callback_failed');
480
+ entered.release();
481
+ await held.promise;
482
+ return { outcome: 'found', row: row() };
483
+ });
484
+ const task = runWithRequestBoundary(boundary, () => f.admission.admit(name));
485
+ const joined = task.then(value => {
486
+ settled = true;
487
+ return value;
488
+ });
489
+ try {
490
+ await observe(entered.promise);
491
+ if (change === 'expiry') {
492
+ f.setTime(1_100);
493
+ }
494
+ if (change === 'generation') {
495
+ f.publish();
496
+ }
497
+ if (change === 'close') {
498
+ closeCompletedReadiness(f.owner);
499
+ }
500
+ if (change === 'portFault') {
501
+ runWithRequestBoundary(boundary, fault);
502
+ }
503
+ await nextTurn();
504
+ assert.equal(
505
+ settled,
506
+ false,
507
+ 'expiry and invalidation do not cancel the provider promise',
508
+ );
509
+ for (let i = 0; i < 3; i++) {
510
+ assert.deepEqual(await withinBoundary(() => f.admission.admit(name)), {
511
+ outcome: 'busy',
512
+ });
513
+ }
514
+ assert.equal(f.calls.length, 1, 'busy requests are rejected, not queued');
515
+ } finally {
516
+ held.release();
517
+ await joined;
518
+ }
519
+ assert.deepEqual(await task, { outcome: change === 'expiry' ? 'expired' : 'unavailable' });
520
+ await nextTurn();
521
+ assert.equal(f.calls.length, 1, 'no rejected request runs after release');
522
+ f.setRead(() => ({ outcome: 'found', row: row() }));
523
+ const recovered = await withinBoundary(() => f.admission.admit(name));
524
+ if (change === 'close') {
525
+ assert.deepEqual(recovered, { outcome: 'unavailable' });
526
+ assert.equal(f.calls.length, 1);
527
+ } else {
528
+ capability(recovered);
529
+ assert.equal(f.calls.length, 2, 'the joined operation releases its reservation');
530
+ }
531
+ }
532
+ },
533
+ );
534
+
535
+ it(
536
+ '3 rejects a simulated final-checkpoint identity gap and never inspects provider errors or accessor payloads',
537
+ { timeout: 15_000 },
538
+ async () => {
539
+ const f = controlled();
540
+ const admitted = capability(await withinBoundary(() => f.admission.admit(name)));
541
+ let business = 0;
542
+ // A controlled consumer seam, not proof that an installed production consumer calls it.
543
+ async function finalCheckpoint() {
544
+ await Promise.resolve(); // Simulated asynchronous identity work ends before the fresh checkpoint.
545
+ const fresh = await f.admission.recheck(admitted);
546
+ if (fresh.outcome === 'ready') {
547
+ business++;
548
+ }
549
+ return fresh;
550
+ }
551
+ f.setRead(() => ({ outcome: 'found', row: row({ id: 42 }) }));
552
+ assert.deepEqual(await withinBoundary(finalCheckpoint), { outcome: 'unavailable' });
553
+ assert.equal(business, 0);
554
+ assert.equal(f.calls.length, 2);
555
+ assert.ok(
556
+ resolveExistingReadyCapability(f.owner, admitted),
557
+ 'a raw-row mismatch is not itself lifecycle revocation',
558
+ );
559
+ f.setRead(() => ({ outcome: 'found', row: row() }));
560
+ assert.equal(capability(await withinBoundary(finalCheckpoint)), admitted);
561
+ assert.equal(business, 1, 'the positive checkpoint control reaches business work');
562
+
563
+ const counts = { property: 0, descriptor: 0, keys: 0, prototype: 0, getter: 0, string: 0 };
564
+ const poison = new Proxy(
565
+ {},
566
+ {
567
+ get() {
568
+ counts.property++;
569
+ throw new Error('poison_get');
570
+ },
571
+ getOwnPropertyDescriptor() {
572
+ counts.descriptor++;
573
+ throw new Error('poison_descriptor');
574
+ },
575
+ ownKeys() {
576
+ counts.keys++;
577
+ throw new Error('poison_keys');
578
+ },
579
+ getPrototypeOf() {
580
+ counts.prototype++;
581
+ throw new Error('poison_prototype');
582
+ },
583
+ },
584
+ );
585
+ for (const reject of [false, true]) {
586
+ f.setRead(() => {
587
+ if (reject) {
588
+ return Promise.reject(poison);
589
+ }
590
+ throw poison;
591
+ });
592
+ assert.deepEqual(await withinBoundary(() => f.admission.admit(name)), {
593
+ outcome: 'unavailable',
594
+ });
595
+ }
596
+ const accessorResult = {};
597
+ for (const key of ['outcome', 'row']) {
598
+ Object.defineProperty(accessorResult, key, {
599
+ get() {
600
+ counts.getter++;
601
+ return poison;
602
+ },
603
+ });
604
+ }
605
+ const accessorRow = row();
606
+ Object.defineProperty(accessorRow, 'id', {
607
+ get() {
608
+ counts.getter++;
609
+ return 41;
610
+ },
611
+ });
612
+ const stringPoison = {
613
+ toString() {
614
+ counts.string++;
615
+ return name;
616
+ },
617
+ };
618
+ for (const result of [
619
+ accessorResult,
620
+ { outcome: 'found', row: accessorRow },
621
+ { outcome: 'found', row: row({ name: stringPoison as unknown as string }) },
622
+ ]) {
623
+ f.setRead(() => result);
624
+ assert.deepEqual(await withinBoundary(() => f.admission.admit(name)), {
625
+ outcome: 'unavailable',
626
+ });
627
+ }
628
+ assert.deepEqual(counts, {
629
+ property: 0,
630
+ descriptor: 0,
631
+ keys: 0,
632
+ prototype: 0,
633
+ getter: 0,
634
+ string: 0,
635
+ });
636
+ f.setRead(() => ({ outcome: 'found', row: row() }));
637
+ capability(await withinBoundary(() => f.admission.admit(name)));
638
+
639
+ const attempt = beginCompletedInstance(f.owner, name);
640
+ let configGets = 0;
641
+ const accessorConfig = Object.defineProperty({}, 'secret', {
642
+ enumerable: true,
643
+ get() {
644
+ configGets++;
645
+ return poison;
646
+ },
647
+ });
648
+ const cycle: { self?: object } = {};
649
+ cycle.self = cycle;
650
+ for (const config of [accessorConfig, cycle, { unsupported: new Date(0) }]) {
651
+ assert.equal(captureCompletedInstance(f.owner, name, attempt, row(), config), undefined);
652
+ }
653
+ assert.equal(configGets, 0);
654
+ finishCompletedInstance(f.owner, name, attempt, {});
655
+ assert.equal(getCompletedPublication(f.owner, name, f.registry, f.route), undefined);
656
+ const stale = captureCompletedInstance(f.owner, name, attempt, row(), { proof: true });
657
+ assert.ok(stale);
658
+ invalidateCompletedInstance(f.owner, name);
659
+ finishCompletedInstance(f.owner, name, attempt, stale);
660
+ assert.equal(getCompletedPublication(f.owner, name, f.registry, f.route), undefined);
661
+ const appAttempt = beginCompletedApplication(f.owner);
662
+ assert.deepEqual(await withinBoundary(() => f.admission.admit(name)), {
663
+ outcome: 'unavailable',
664
+ });
665
+ finishCompletedApplication(f.owner, appAttempt, false);
666
+ assert.equal(f.owner.snapshot().application, 'failed');
667
+ assert.deepEqual(await withinBoundary(() => f.admission.admit(name)), {
668
+ outcome: 'unavailable',
669
+ });
670
+ },
671
+ );
672
+
673
+ it(
674
+ '4 uses real local lifecycle invalidation and queue-owned row/config captures without patching singletons',
675
+ { timeout: 15_000 },
676
+ async () => {
677
+ const inert = localLifecycle();
678
+ await inert.service.instanceStartup(name, { force: true });
679
+ assert.ok(inert.service.getConfig(name));
680
+ assert.equal(
681
+ inert.publication(),
682
+ undefined,
683
+ 'ordinary startup has no authority without explicit inert provisioning',
684
+ );
685
+ assert.deepEqual(await withinBoundary(() => inert.admission.admit(name)), {
686
+ outcome: 'unavailable',
687
+ });
688
+
689
+ // A legacy-ready nonforce skip preserves its ordinary cache, but revokes completed
690
+ // admission and cannot advertise a fresh publication without a completed startup.
691
+ const skipped = localLifecycle();
692
+ skipped.provision();
693
+ await skipped.service.instanceStartup(name, { force: true });
694
+ const beforeSkip = capability(await withinBoundary(() => skipped.admission.admit(name)));
695
+ const cachedBeforeSkip = skipped.service.getConfig(name);
696
+ const readsBeforeSkip = skipped.counts.reads;
697
+ const startupsBeforeSkip = skipped.counts.startups;
698
+ skipped.localApp.meta.appReadyInstances[name] = true;
699
+ skipped.rows.set(name, row({ config: JSON.stringify({ proof: { shared: 'not-loaded' } }) }));
700
+ await skipped.service.instanceStartup(name, { force: false });
701
+ assert.equal(skipped.service.getConfig(name), cachedBeforeSkip);
702
+ assert.equal((skipped.service.getConfig(name) as any).proof.shared, 'db');
703
+ assert.equal(
704
+ skipped.counts.reads,
705
+ readsBeforeSkip,
706
+ 'a ready nonforce skip does not reread a cached instance',
707
+ );
708
+ assert.equal(skipped.counts.startups, startupsBeforeSkip);
709
+ assert.equal(skipped.publication(), undefined);
710
+ assert.equal(resolveExistingReadyCapability(skipped.owner, beforeSkip), undefined);
711
+ assert.deepEqual(await withinBoundary(() => skipped.admission.admit(name)), {
712
+ outcome: 'unavailable',
713
+ });
714
+
715
+ // resetCache revokes immediately, before its held model read, and cannot republish.
716
+ const reset = localLifecycle();
717
+ reset.provision();
718
+ await reset.service.instanceStartup(name, { force: true });
719
+ const beforeReset = capability(await withinBoundary(() => reset.admission.admit(name)));
720
+ const resetEntered = barrier();
721
+ const resetHeld = barrier();
722
+ reset.setRead(async () => {
723
+ resetEntered.release();
724
+ await resetHeld.promise;
725
+ });
726
+ const resetting = reset.service.resetCache(name);
727
+ try {
728
+ await observe(resetEntered.promise);
729
+ assert.equal(reset.publication(), undefined);
730
+ assert.equal(resolveExistingReadyCapability(reset.owner, beforeReset), undefined);
731
+ } finally {
732
+ resetHeld.release();
733
+ await resetting;
734
+ }
735
+ assert.equal(reset.publication(), undefined);
736
+ reset.setRead(async () => {});
737
+ await reset.service.instanceStartup(name, { force: true });
738
+ assert.ok(reset.publication());
739
+
740
+ // All cached names are revoked before resetAllCaches enters even its first context.
741
+ const all = localLifecycle();
742
+ const other = 'other' as InstanceName;
743
+ all.rows.set(other, row({ id: 42, name: other }));
744
+ for (const selected of [name, other] as InstanceName[]) {
745
+ all.provision(selected);
746
+ await all.service.instanceStartup(selected, { force: true });
747
+ assert.ok(all.publication(selected));
748
+ }
749
+ const allEntered = barrier();
750
+ const allHeld = barrier();
751
+ all.setContext(async () => {
752
+ allEntered.release();
753
+ await allHeld.promise;
754
+ });
755
+ const resettingAll = all.service.resetAllCaches();
756
+ try {
757
+ await observe(allEntered.promise);
758
+ assert.equal(all.publication(name), undefined);
759
+ assert.equal(all.publication(other), undefined);
760
+ } finally {
761
+ allHeld.release();
762
+ await resettingAll;
763
+ }
764
+ assert.equal(all.counts.contexts, 2);
765
+ assert.equal(all.publication(name), undefined);
766
+ assert.equal(all.publication(other), undefined);
767
+
768
+ // Observe each entry before a downstream lifecycle method could revoke on its behalf.
769
+ for (const operation of ['isolate', 'reloadWorker', 'reload'] as const) {
770
+ const f = localLifecycle();
771
+ f.provision();
772
+ await f.service.instanceStartup(name, { force: true });
773
+ const prior = capability(await withinBoundary(() => f.admission.admit(name)));
774
+ const entered = barrier();
775
+ const held = barrier();
776
+ let revokedAtEntry = false;
777
+ if (operation === 'isolate') {
778
+ f.setContext(async () => {
779
+ revokedAtEntry = f.publication() === undefined;
780
+ entered.release();
781
+ await held.promise;
782
+ });
783
+ } else if (operation === 'reloadWorker') {
784
+ Object.defineProperty(f.service, 'instanceStartupIsolate', {
785
+ value: async (...args: Parameters<ServiceInstance['instanceStartupIsolate']>) => {
786
+ revokedAtEntry = f.publication() === undefined;
787
+ entered.release();
788
+ await held.promise;
789
+ return ServiceInstance.prototype.instanceStartupIsolate.apply(f.service, args);
790
+ },
791
+ });
792
+ } else {
793
+ Object.defineProperty(f.bean, 'reloadWorker', {
794
+ value: async (selected: InstanceName) => {
795
+ revokedAtEntry = f.publication() === undefined;
796
+ entered.release();
797
+ await held.promise;
798
+ return BeanInstance.prototype.reloadWorker.call(f.bean, selected);
799
+ },
800
+ });
801
+ }
802
+ const task =
803
+ operation === 'isolate'
804
+ ? f.service.instanceStartupIsolate(name, { force: true })
805
+ : operation === 'reloadWorker'
806
+ ? f.bean.reloadWorker(name)
807
+ : f.bean.reload();
808
+ try {
809
+ await observe(entered.promise);
810
+ assert.equal(revokedAtEntry, true, operation);
811
+ assert.equal(resolveExistingReadyCapability(f.owner, prior), undefined);
812
+ } finally {
813
+ held.release();
814
+ await task;
815
+ }
816
+ assert.ok(f.publication());
817
+ assert.equal(f.counts.broadcasts, operation === 'reload' ? 1 : 0);
818
+ }
819
+
820
+ const failed = localLifecycle();
821
+ failed.provision();
822
+ await failed.service.instanceStartup(name, { force: true });
823
+ const failure = new Error('controlled_startup_failure');
824
+ failed.setStartup(async () => {
825
+ throw failure;
826
+ });
827
+ const failing = failed.service.instanceStartup(name, { force: true });
828
+ assert.equal(failed.publication(), undefined, 'startup revokes before queue execution');
829
+ await assert.rejects(failing, error => error === failure);
830
+ assert.equal(failed.publication(), undefined);
831
+ failed.setStartup(async () => {});
832
+ await failed.service.instanceStartup(name, { force: true });
833
+ assert.ok(failed.publication(), 'failed work does not poison the startup queue');
834
+
835
+ // Two local service objects share one real queue. The queued closure owns its prepared
836
+ // base, persisted row and detached merge; neither an older attempt nor a later cache wins.
837
+ const queued = localLifecycle();
838
+ queued.provision();
839
+ const secondService = queued.anotherService();
840
+ const firstEntered = barrier();
841
+ const firstHeld = barrier();
842
+ const secondEntered = barrier();
843
+ const secondHeld = barrier();
844
+ const firstRow = { ...queued.rows.get(name)! };
845
+ queued.setStartup(async () => {
846
+ if (queued.counts.startups === 1) {
847
+ firstEntered.release();
848
+ await firstHeld.promise;
849
+ } else {
850
+ secondEntered.release();
851
+ await secondHeld.promise;
852
+ }
853
+ });
854
+ const first = queued.service.instanceStartup(name, { force: true });
855
+ let second: Promise<unknown> | undefined;
856
+ let capturedRow!: Row;
857
+ try {
858
+ await observe(firstEntered.promise);
859
+ assert.equal(
860
+ queued.publication(),
861
+ undefined,
862
+ 'startup hooks must complete before publication',
863
+ );
864
+ assert.equal((queued.service.getConfig(name) as any).proof.shared, 'db');
865
+ capturedRow = row({
866
+ config: JSON.stringify({ proof: { db: 2, shared: 'db2', nested: { db: 2 } } }),
867
+ });
868
+ queued.rows.set(name, capturedRow);
869
+ second = secondService.instanceStartup(name, {
870
+ force: true,
871
+ configInstanceBase: {
872
+ config: { proof: { base: 2, shared: 'base2', nested: { base: 2 } } },
873
+ } as any,
874
+ });
875
+ await nextTurn();
876
+ assert.equal(
877
+ queued.counts.reads,
878
+ 1,
879
+ 'queued preparation must not run ahead of the first startup',
880
+ );
881
+ firstHeld.release();
882
+ await observe(first);
883
+ await observe(secondEntered.promise);
884
+ assert.equal(
885
+ queued.publication(),
886
+ undefined,
887
+ 'the superseded first attempt cannot publish',
888
+ );
889
+ assert.equal(queued.counts.reads, 2);
890
+ assert.deepEqual((secondService.getConfig(name) as any).proof, {
891
+ global: 1,
892
+ base: 2,
893
+ db: 2,
894
+ shared: 'db2',
895
+ nested: { global: 1, base: 2, db: 2 },
896
+ });
897
+ capturedRow.config = JSON.stringify({ proof: { shared: 'later-row' } });
898
+ (secondService.getConfig(name) as any).proof.shared = 'later-cache';
899
+ (secondService.getConfig(name) as any).proof.nested.db = 99;
900
+ } finally {
901
+ firstHeld.release();
902
+ secondHeld.release();
903
+ await Promise.all([first, ...(second ? [second] : [])]);
904
+ }
905
+ const publication = queued.publication();
906
+ assert.ok(publication);
907
+ const completedRow = row({
908
+ config: JSON.stringify({ proof: { db: 2, shared: 'db2', nested: { db: 2 } } }),
909
+ });
910
+ assert.equal(matchesCompletedRow(publication, firstRow), false);
911
+ assert.equal(matchesCompletedRow(publication, capturedRow), false);
912
+ assert.equal(matchesCompletedRow(publication, completedRow), true);
913
+ queued.rows.set(name, completedRow);
914
+ const ready = capability(await withinBoundary(() => queued.admission.admit(name)));
915
+ const context: any = {};
916
+ assert.equal(
917
+ pinExistingReadyContext(queued.owner, ready, queued.localApp, context),
918
+ false,
919
+ 'owner identity must match the provisioned app',
920
+ );
921
+ assert.equal(pinExistingReadyContext(queued.owner, ready, queued.identity, context), true);
922
+ assert.equal(isExistingReadyContext(context), true);
923
+ assert.equal(getExistingReadyContextConfig(context), context.config);
924
+ assert.deepEqual(
925
+ JSON.parse(JSON.stringify(context.config.proof)),
926
+ {
927
+ global: 1,
928
+ base: 2,
929
+ db: 2,
930
+ shared: 'db2',
931
+ nested: { global: 1, base: 2, db: 2 },
932
+ },
933
+ 'the real queued publication retains its pre-mutation merged snapshot',
934
+ );
935
+ assert.notEqual(context.config, secondService.getConfig(name));
936
+ assert.notEqual(context.config.proof, (secondService.getConfig(name) as any).proof);
937
+ assert.equal((secondService.getConfig(name) as any).proof.shared, 'later-cache');
938
+ assert.equal((secondService.getConfig(name) as any).proof.nested.db, 99);
939
+ assert.equal(context.instance.config, completedRow.config);
940
+ assert.equal(Object.isFrozen(context.config.proof.nested), true);
941
+ assert.equal(context.innerAccess, false);
942
+ assert.equal(context.instanceName, name);
943
+ assert.equal(pinExistingReadyContext(queued.owner, ready, queued.identity, context), false);
944
+ assert.throws(() => {
945
+ context.config.proof.shared = 'changed';
946
+ }, TypeError);
947
+ },
948
+ );
949
+ });