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,558 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { describe, test } from 'node:test';
4
+
5
+ import type { ExistingReadyCapability } from './completedReadiness.ts';
6
+
7
+ import {
8
+ beginCompletedApplication,
9
+ beginCompletedInstance,
10
+ captureCompletedInstance,
11
+ closeCompletedReadiness,
12
+ createCompletedReadiness,
13
+ finishCompletedApplication,
14
+ finishCompletedInstance,
15
+ getCompletedPublication,
16
+ getExistingReadyContextConfig,
17
+ invalidateCompletedInstance,
18
+ isExistingReadyContext,
19
+ issueExistingReadyCapability,
20
+ matchesCompletedRow,
21
+ pinExistingReadyContext,
22
+ provisionCompletedInstanceForTest,
23
+ resolveExistingReadyCapability,
24
+ } from './completedReadiness.ts';
25
+ import { createRequestBoundary, runWithRequestBoundary } from './requestBoundary.ts';
26
+
27
+ // These are inert identities, not Vona applications. No singleton, environment,
28
+ // persistence, listener, or application event emitter is used by this suite.
29
+ function fixture(functions: readonly Function[] = []) {
30
+ const app = Object.freeze({});
31
+ const owner = createCompletedReadiness(app);
32
+ const registry = Object.freeze({});
33
+ const route = Object.freeze({});
34
+ const handle = Object.freeze({});
35
+ const name = 'completed-readiness-test';
36
+ const row = { id: 1, name, disabled: false, isolate: false, config: null as string | null };
37
+ provisionCompletedInstanceForTest(owner, name, {
38
+ registry,
39
+ route,
40
+ handle,
41
+ isolate: false,
42
+ functions,
43
+ });
44
+ return { app, owner, registry, route, handle, name, row };
45
+ }
46
+
47
+ type Fixture = ReturnType<typeof fixture>;
48
+
49
+ function ready(local: Fixture) {
50
+ const attempt = beginCompletedApplication(local.owner);
51
+ finishCompletedApplication(local.owner, attempt, true);
52
+ }
53
+
54
+ function current(local: Fixture) {
55
+ return getCompletedPublication(local.owner, local.name, local.registry, local.route);
56
+ }
57
+
58
+ function capture(local: Fixture, config: unknown, row: unknown = local.row) {
59
+ const attempt = beginCompletedInstance(local.owner, local.name);
60
+ assert.ok(attempt);
61
+ const captured = captureCompletedInstance(local.owner, local.name, attempt, row, config);
62
+ return { attempt, captured };
63
+ }
64
+
65
+ function publish(local: Fixture, config: unknown) {
66
+ const { attempt, captured } = capture(local, config);
67
+ assert.ok(captured);
68
+ finishCompletedInstance(local.owner, local.name, attempt, captured);
69
+ const publication = current(local);
70
+ assert.equal(publication, captured);
71
+ assert.ok(publication);
72
+ return { attempt, publication };
73
+ }
74
+
75
+ describe(
76
+ 'completed readiness inert qualification',
77
+ { concurrency: false, timeout: 120_000 },
78
+ () => {
79
+ test(
80
+ 'lifecycle rejects stale attempts, failure, and synchronous close',
81
+ {
82
+ concurrency: false,
83
+ timeout: 15_000,
84
+ },
85
+ async () => {
86
+ const local = fixture();
87
+ const { owner, name } = local;
88
+ const initial = owner.snapshot();
89
+ assert.equal(initial.application, 'starting');
90
+ assert.equal(Object.isFrozen(owner), true);
91
+ assert.equal(Object.isFrozen(initial), true);
92
+
93
+ const staleApplication = beginCompletedApplication(owner);
94
+ const application = beginCompletedApplication(owner);
95
+ assert.notEqual(application, staleApplication);
96
+ finishCompletedApplication(owner, staleApplication, true);
97
+ assert.equal(owner.snapshot().application, 'starting');
98
+ finishCompletedApplication(owner, staleApplication, false);
99
+ assert.equal(owner.snapshot().application, 'starting');
100
+
101
+ const staleInstance = capture(local, { revision: 1 });
102
+ const instance = capture(local, { revision: 2 });
103
+ assert.ok(staleInstance.captured);
104
+ assert.ok(instance.captured);
105
+ assert.notEqual(staleInstance.attempt, instance.attempt);
106
+ finishCompletedInstance(owner, name, staleInstance.attempt, staleInstance.captured);
107
+ finishCompletedApplication(owner, application, true);
108
+ assert.equal(current(local), undefined);
109
+ finishCompletedInstance(owner, name, instance.attempt, instance.captured);
110
+ assert.equal(current(local), instance.captured);
111
+ assert.equal(owner.snapshot().application, 'ready');
112
+ assert.equal(initial.application, 'starting');
113
+
114
+ const capability = issueExistingReadyCapability(owner, instance.captured, 100, () => 0);
115
+ assert.ok(resolveExistingReadyCapability(owner, capability, local.app));
116
+ const failingApplication = beginCompletedApplication(owner);
117
+ assert.equal(current(local), undefined);
118
+ assert.equal(resolveExistingReadyCapability(owner, capability, local.app), undefined);
119
+ finishCompletedInstance(owner, name, instance.attempt, instance.captured);
120
+ const duringStartup = capture(local, { revision: 3 });
121
+ assert.ok(duringStartup.captured);
122
+ finishCompletedInstance(owner, name, duringStartup.attempt, duringStartup.captured);
123
+ assert.equal(current(local), undefined);
124
+ finishCompletedApplication(owner, failingApplication, false);
125
+ assert.equal(owner.snapshot().application, 'failed');
126
+ finishCompletedInstance(owner, name, duringStartup.attempt, duringStartup.captured);
127
+ assert.equal(current(local), undefined);
128
+
129
+ ready(local);
130
+ assert.equal(current(local), undefined);
131
+ const { publication } = publish(local, { revision: 4 });
132
+ const closingCapability = issueExistingReadyCapability(owner, publication, 100, () => 0);
133
+ const pending = capture(local, { revision: 5 });
134
+ const pendingApplication = beginCompletedApplication(owner);
135
+ // Pure synchronous witness. The source assertion below separately ties this
136
+ // helper to AppMeta.close, without importing its application dependency graph.
137
+ closeCompletedReadiness(owner);
138
+ assert.equal(owner.snapshot().application, 'closing');
139
+ assert.equal(current(local), undefined);
140
+ assert.equal(
141
+ resolveExistingReadyCapability(owner, closingCapability, local.app),
142
+ undefined,
143
+ );
144
+ assert.equal(
145
+ captureCompletedInstance(owner, name, pending.attempt, local.row, {}),
146
+ undefined,
147
+ );
148
+ finishCompletedInstance(owner, name, pending.attempt, pending.captured);
149
+ finishCompletedApplication(owner, pendingApplication, true);
150
+ finishCompletedApplication(owner, pendingApplication, false);
151
+ const afterClose = beginCompletedApplication(owner);
152
+ finishCompletedApplication(owner, afterClose, true);
153
+ closeCompletedReadiness(owner);
154
+ assert.equal(owner.snapshot().application, 'closing');
155
+ assert.equal(current(local), undefined);
156
+
157
+ const metaSource = await readFile(new URL('./meta.ts', import.meta.url), 'utf8');
158
+ assert.match(
159
+ metaSource,
160
+ /async close\(\)\s*\{\s*closeCompletedReadiness\(this\.completedReadiness\);\s*if \(!this\[SymbolClosePromise\]\)\s*\{\s*this\[SymbolClosePromise\] = this\._closeInner\(\);\s*\}\s*return this\[SymbolClosePromise\];\s*\}/,
161
+ );
162
+ },
163
+ );
164
+
165
+ test(
166
+ 'configuration detaches data and rejects getters, exotic values, cycles, and unsafe functions',
167
+ {
168
+ concurrency: false,
169
+ timeout: 15_000,
170
+ },
171
+ () => {
172
+ let calls = 0;
173
+ const allowed = Object.freeze(() => {
174
+ calls++;
175
+ });
176
+ const lookalike = Object.freeze(() => {
177
+ calls++;
178
+ });
179
+ const mutable = Object.assign(
180
+ () => {
181
+ calls++;
182
+ },
183
+ { mutable: true },
184
+ );
185
+ const withObject = Object.freeze(
186
+ Object.assign(
187
+ () => {
188
+ calls++;
189
+ },
190
+ { state: {} },
191
+ ),
192
+ );
193
+ const withFunction = Object.freeze(
194
+ Object.assign(
195
+ () => {
196
+ calls++;
197
+ },
198
+ { callback: allowed },
199
+ ),
200
+ );
201
+ const ordinary = Object.freeze(function ordinaryFunction() {
202
+ calls++;
203
+ });
204
+ let getterReads = 0;
205
+ const read = () => {
206
+ getterReads++;
207
+ throw new Error('Synthetic getter must not run');
208
+ };
209
+ const withGetter = Object.freeze(
210
+ Object.defineProperty(
211
+ () => {
212
+ calls++;
213
+ },
214
+ 'state',
215
+ { get: read },
216
+ ),
217
+ );
218
+ const functions = [allowed, mutable, withObject, withFunction, ordinary, withGetter];
219
+ const local = fixture(functions);
220
+ ready(local);
221
+ // Provisioning copies the allowlist; later mutation must not grant identity.
222
+ functions.push(lookalike);
223
+
224
+ const shared = { value: 1 };
225
+ const nullPrototype = Object.assign(Object.create(null), { flag: true });
226
+ const source = {
227
+ nested: { items: [shared, shared] },
228
+ empty: nullPrototype,
229
+ callback: allowed,
230
+ };
231
+ Object.defineProperty(source, 'hidden', { value: 'retained', enumerable: false });
232
+ const { publication } = publish(local, source);
233
+ const context: Record<string, unknown> = {};
234
+ const capability = issueExistingReadyCapability(local.owner, publication, 100, () => 0);
235
+ assert.equal(pinExistingReadyContext(local.owner, capability, local.app, context), true);
236
+ const detached = context.config as typeof source & { hidden: string };
237
+ assert.notEqual(detached, source);
238
+ assert.notEqual(detached.nested, source.nested);
239
+ assert.notEqual(detached.nested.items, source.nested.items);
240
+ assert.notEqual(detached.nested.items[0], shared);
241
+ assert.notEqual(detached.nested.items[0], detached.nested.items[1]);
242
+ assert.equal(detached.callback, allowed);
243
+ assert.equal(Object.getPrototypeOf(detached.empty), null);
244
+ assert.equal(detached.hidden, 'retained');
245
+ assert.equal(Object.getOwnPropertyDescriptor(detached, 'hidden')?.enumerable, false);
246
+ for (const value of [
247
+ detached,
248
+ detached.nested,
249
+ detached.nested.items,
250
+ ...detached.nested.items,
251
+ detached.empty,
252
+ ]) {
253
+ assert.equal(Object.isFrozen(value), true);
254
+ }
255
+ shared.value = 2;
256
+ source.nested.items.push({ value: 3 });
257
+ nullPrototype.flag = false;
258
+ assert.equal(detached.nested.items.length, 2);
259
+ assert.equal(detached.nested.items[0].value, 1);
260
+ assert.equal(detached.empty.flag, true);
261
+
262
+ const cycle: Record<string, unknown> = {};
263
+ cycle.self = cycle;
264
+ const arrayCycle: unknown[] = [];
265
+ arrayCycle.push(arrayCycle);
266
+ const getter = Object.defineProperty({}, 'secret', { get: read, enumerable: true });
267
+ const hiddenGetter = Object.defineProperty({}, 'secret', { get: read });
268
+ const symbolKey = { [Symbol('unsupported')]: true };
269
+ const exoticArray = Object.setPrototypeOf([], null);
270
+ const rejected: readonly [string, unknown][] = [
271
+ ['enumerable getter', { nested: getter }],
272
+ ['non-enumerable getter', hiddenGetter],
273
+ ['object cycle', cycle],
274
+ ['array cycle', arrayCycle],
275
+ ['date', { value: new Date(0) }],
276
+ ['map', { value: new Map() }],
277
+ ['set', { value: new Set() }],
278
+ ['regexp', { value: /synthetic/ }],
279
+ ['typed array', { value: new Uint8Array(1) }],
280
+ ['custom prototype', Object.create({ inherited: true })],
281
+ ['custom array prototype', exoticArray],
282
+ ['symbol key', symbolKey],
283
+ ['symbol value', { value: Symbol('unsupported') }],
284
+ ['bigint', { value: 1n }],
285
+ ['non-finite number', { value: Infinity }],
286
+ ['NaN', { value: Number.NaN }],
287
+ ['unlisted function', { callback: () => undefined }],
288
+ ['frozen lookalike added after provisioning', { callback: lookalike }],
289
+ ['allowlisted writable function property', { callback: mutable }],
290
+ ['allowlisted object function property', { callback: withObject }],
291
+ ['allowlisted function function property', { callback: withFunction }],
292
+ ['allowlisted ordinary function prototype', { callback: ordinary }],
293
+ ['allowlisted function getter', { callback: withGetter }],
294
+ ['primitive root', 'not an object'],
295
+ ['null root', null],
296
+ ];
297
+ for (const [label, value] of rejected) {
298
+ const { attempt, captured } = capture(local, value);
299
+ assert.equal(captured, undefined, label);
300
+ finishCompletedInstance(local.owner, local.name, attempt, captured);
301
+ assert.equal(current(local), undefined, label);
302
+ }
303
+ const rowWithGetter = Object.defineProperty({ ...local.row }, 'config', { get: read });
304
+ assert.equal(capture(local, {}, rowWithGetter).captured, undefined);
305
+ assert.equal(getterReads, 0);
306
+ assert.equal(calls, 0);
307
+ },
308
+ );
309
+
310
+ test(
311
+ 'publication and capability identity reject forgery, foreign owners, expiration, and stale state',
312
+ {
313
+ concurrency: false,
314
+ timeout: 15_000,
315
+ },
316
+ () => {
317
+ const local = fixture();
318
+ const foreign = fixture();
319
+ ready(local);
320
+ ready(foreign);
321
+ const { attempt, publication } = publish(local, { revision: 1 });
322
+ assert.equal(Object.isFrozen(publication), true);
323
+ assert.equal(getCompletedPublication(local.owner, local.name, {}, local.route), undefined);
324
+ assert.equal(
325
+ getCompletedPublication(local.owner, local.name, local.registry, {}),
326
+ undefined,
327
+ );
328
+ assert.equal(
329
+ getCompletedPublication(foreign.owner, local.name, local.registry, local.route),
330
+ undefined,
331
+ );
332
+ assert.equal(matchesCompletedRow(publication, { ...local.row }), true);
333
+ for (const changed of [
334
+ { id: 2 },
335
+ { name: 'other' },
336
+ { disabled: true },
337
+ { isolate: true },
338
+ { config: '{}' },
339
+ ]) {
340
+ assert.equal(matchesCompletedRow(publication, { ...local.row, ...changed }), false);
341
+ }
342
+ let rowReads = 0;
343
+ const rowWithGetter = Object.defineProperty({ ...local.row }, 'id', {
344
+ get() {
345
+ rowReads++;
346
+ return 1;
347
+ },
348
+ });
349
+ assert.equal(matchesCompletedRow(publication, rowWithGetter), false);
350
+ assert.equal(rowReads, 0);
351
+
352
+ for (const forged of [{}, { ...publication }, Object.create(publication)]) {
353
+ assert.throws(
354
+ () => issueExistingReadyCapability(local.owner, forged, 20, () => 10),
355
+ /Existing-ready unavailable/,
356
+ );
357
+ finishCompletedInstance(foreign.owner, foreign.name, attempt, forged);
358
+ assert.equal(current(foreign), undefined);
359
+ }
360
+ finishCompletedInstance(foreign.owner, foreign.name, attempt, publication);
361
+ assert.equal(current(foreign), undefined);
362
+
363
+ let now = 10;
364
+ const capability = issueExistingReadyCapability(local.owner, publication, 20, () => now);
365
+ assert.equal(Object.isFrozen(capability), true);
366
+ const resolved = resolveExistingReadyCapability(local.owner, capability, local.app);
367
+ assert.ok(resolved);
368
+ assert.equal(Object.isFrozen(resolved), true);
369
+ assert.equal(resolved.publication, publication);
370
+ assert.equal(resolved.name, local.name);
371
+ assert.equal(resolved.handle, local.handle);
372
+ assert.equal(
373
+ resolveExistingReadyCapability(foreign.owner, capability, foreign.app),
374
+ undefined,
375
+ );
376
+ assert.equal(
377
+ resolveExistingReadyCapability(local.owner, capability, foreign.app),
378
+ undefined,
379
+ );
380
+ for (const forged of [{}, { ...capability }, Object.create(capability)]) {
381
+ assert.equal(
382
+ resolveExistingReadyCapability(
383
+ local.owner,
384
+ forged as ExistingReadyCapability,
385
+ local.app,
386
+ ),
387
+ undefined,
388
+ );
389
+ }
390
+ const misissued = issueExistingReadyCapability(foreign.owner, publication, 20, () => 10);
391
+ assert.equal(
392
+ resolveExistingReadyCapability(foreign.owner, misissued, foreign.app),
393
+ undefined,
394
+ );
395
+ now = 19;
396
+ assert.ok(resolveExistingReadyCapability(local.owner, capability, local.app));
397
+ now = 20;
398
+ assert.equal(resolveExistingReadyCapability(local.owner, capability, local.app), undefined);
399
+ for (const clock of [
400
+ () => Number.NaN,
401
+ () => Infinity,
402
+ () => {
403
+ throw new Error('Synthetic clock failure');
404
+ },
405
+ ]) {
406
+ const invalidClock = issueExistingReadyCapability(local.owner, publication, 20, clock);
407
+ assert.equal(
408
+ resolveExistingReadyCapability(local.owner, invalidClock, local.app),
409
+ undefined,
410
+ );
411
+ }
412
+
413
+ now = 10;
414
+ invalidateCompletedInstance(local.owner, local.name);
415
+ assert.equal(current(local), undefined);
416
+ assert.equal(resolveExistingReadyCapability(local.owner, capability, local.app), undefined);
417
+ finishCompletedInstance(local.owner, local.name, attempt, publication);
418
+ assert.equal(current(local), undefined);
419
+ const replacement = publish(local, { revision: 2 });
420
+ assert.notEqual(replacement.publication, publication);
421
+ assert.equal(resolveExistingReadyCapability(local.owner, capability, local.app), undefined);
422
+ const replacementCapability = issueExistingReadyCapability(
423
+ local.owner,
424
+ replacement.publication,
425
+ 20,
426
+ () => 10,
427
+ );
428
+ assert.ok(resolveExistingReadyCapability(local.owner, replacementCapability, local.app));
429
+ provisionCompletedInstanceForTest(local.owner, local.name, {
430
+ registry: local.registry,
431
+ route: local.route,
432
+ handle: local.handle,
433
+ isolate: false,
434
+ });
435
+ assert.equal(current(local), undefined);
436
+ assert.equal(
437
+ resolveExistingReadyCapability(local.owner, replacementCapability, local.app),
438
+ undefined,
439
+ );
440
+ assert.throws(
441
+ () =>
442
+ runWithRequestBoundary(createRequestBoundary(), () => {
443
+ provisionCompletedInstanceForTest(local.owner, local.name, {
444
+ registry: local.registry,
445
+ route: local.route,
446
+ handle: local.handle,
447
+ isolate: false,
448
+ });
449
+ }),
450
+ /Provisioning requires lifecycle boundary/,
451
+ );
452
+ },
453
+ );
454
+
455
+ test(
456
+ 'context pins immutable detached configuration and registry identity to the exact context owner',
457
+ {
458
+ concurrency: false,
459
+ timeout: 15_000,
460
+ },
461
+ async () => {
462
+ const local = fixture();
463
+ const foreign = fixture();
464
+ ready(local);
465
+ ready(foreign);
466
+ const config = { nested: { enabled: true }, values: [1, 2] };
467
+ const { publication } = publish(local, config);
468
+ const capability = issueExistingReadyCapability(local.owner, publication, 20, () => 10);
469
+ const denied: Record<string, unknown> = {};
470
+ assert.equal(
471
+ pinExistingReadyContext(foreign.owner, capability, foreign.app, denied),
472
+ false,
473
+ );
474
+ assert.equal(pinExistingReadyContext(local.owner, capability, foreign.app, denied), false);
475
+ const expired = issueExistingReadyCapability(local.owner, publication, 10, () => 10);
476
+ assert.equal(pinExistingReadyContext(local.owner, expired, local.app, denied), false);
477
+ assert.deepEqual(Reflect.ownKeys(denied), []);
478
+ assert.equal(isExistingReadyContext(denied), false);
479
+
480
+ const context: Record<string, unknown> = { app: local.app };
481
+ assert.equal(pinExistingReadyContext(local.owner, capability, local.app, context), true);
482
+ assert.equal(isExistingReadyContext(context), true);
483
+ const pinnedConfig = context.config as typeof config;
484
+ const pinnedRow = context.instance as typeof local.row;
485
+ assert.equal(getExistingReadyContextConfig(context), pinnedConfig);
486
+ assert.notEqual(pinnedConfig, config);
487
+ assert.notEqual(pinnedRow, local.row);
488
+ assert.equal(context.instanceName, local.name);
489
+ assert.equal(context.innerAccess, false);
490
+ for (const key of ['config', 'instance', 'instanceName', 'innerAccess']) {
491
+ const descriptor = Object.getOwnPropertyDescriptor(context, key);
492
+ assert.ok(descriptor);
493
+ assert.equal(descriptor.writable, false);
494
+ assert.equal(descriptor.configurable, false);
495
+ assert.equal(descriptor.enumerable, true);
496
+ assert.equal(Reflect.set(context, key, {}), false);
497
+ assert.equal(Reflect.deleteProperty(context, key), false);
498
+ assert.equal(Reflect.defineProperty(context, key, { value: {} }), false);
499
+ }
500
+ assert.equal(Reflect.set(pinnedConfig.nested, 'enabled', false), false);
501
+ assert.equal(Reflect.set(pinnedConfig.values, '0', 99), false);
502
+ assert.equal(Reflect.set(pinnedRow, 'id', 99), false);
503
+ assert.equal(Object.isFrozen(pinnedRow), true);
504
+ assert.equal(pinExistingReadyContext(local.owner, capability, local.app, context), false);
505
+ for (const lookalike of [{ ...context }, Object.create(context)]) {
506
+ assert.equal(isExistingReadyContext(lookalike), false);
507
+ assert.equal(getExistingReadyContextConfig(lookalike), undefined);
508
+ }
509
+ assert.equal(isExistingReadyContext(undefined), false);
510
+ assert.equal(isExistingReadyContext(null), false);
511
+
512
+ config.nested.enabled = false;
513
+ config.values.push(3);
514
+ local.row.id = 2;
515
+ local.row.name = 'mutated-source';
516
+ local.row.config = '{}';
517
+ assert.equal(pinnedConfig.nested.enabled, true);
518
+ assert.deepEqual(pinnedConfig.values, [1, 2]);
519
+ assert.equal(pinnedRow.id, 1);
520
+ assert.equal(pinnedRow.name, local.name);
521
+ assert.equal(pinnedRow.config, null);
522
+ assert.equal(context.instanceName, local.name);
523
+
524
+ invalidateCompletedInstance(local.owner, local.name);
525
+ assert.equal(resolveExistingReadyCapability(local.owner, capability, local.app), undefined);
526
+ const afterInvalidation = {};
527
+ assert.equal(
528
+ pinExistingReadyContext(local.owner, capability, local.app, afterInvalidation),
529
+ false,
530
+ );
531
+ assert.equal(isExistingReadyContext(afterInvalidation), false);
532
+ // An already pinned context retains its captured data; this is not a claim
533
+ // that an invalidated capability can admit another context.
534
+ assert.equal(getExistingReadyContextConfig(context), pinnedConfig);
535
+ const foreignPublication = publish(foreign, { foreign: true }).publication;
536
+ const foreignCapability = issueExistingReadyCapability(
537
+ foreign.owner,
538
+ foreignPublication,
539
+ 20,
540
+ () => 10,
541
+ );
542
+ assert.equal(
543
+ pinExistingReadyContext(foreign.owner, foreignCapability, foreign.app, context),
544
+ false,
545
+ );
546
+ assert.equal(getExistingReadyContextConfig(context), pinnedConfig);
547
+
548
+ // Trace the existing getter's pin-first integration without importing the
549
+ // bean container or touching production application configuration.
550
+ const contextSource = await readFile(new URL('./context.ts', import.meta.url), 'utf8');
551
+ assert.match(
552
+ contextSource,
553
+ /get config\(\)\s*\{\s*const self = cast\(this\);\s*const pinned = getExistingReadyContextConfig\(self\);\s*if \(pinned\) return pinned as any;\s*const serviceInstance =/,
554
+ );
555
+ },
556
+ );
557
+ },
558
+ );