Skip to content

Commit 0bb3c97

Browse files
committed
docs: bootStackOnce's second options key and the no-automation composition, as #22301 now has them
The bootStackOnce TSDoc states that a second options key on a configuration whose first boot is still live is refused by the instance rule. The platform-core activation-ledger item (revision 4) and FOLLOW-UPS restate the no-automation composition as the dogfood suite builds it: a showcase-derived configuration that does not declare automation. Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
1 parent db8e398 commit 0bb3c97

3 files changed

Lines changed: 26 additions & 7 deletions

File tree

‎docs/qa/platform-checklist/FOLLOW-UPS.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,13 @@ posture, then decide which shape is wanted).
401401
- a `group`/`isolated` posture boot recipe → unblocks the §5 operator-gate legs
402402
(`access-security.activation-write-operator-gate`) and card row D2.
403403
- a documented no-automation lean-composition boot for manual runners → the dogfood
404-
harness (`bootStack(showcaseStack)` minus automation) is currently the only path for
405-
`platform-core.activation-ledger-registration-home`'s 503-turnaround leg.
404+
harness is currently the only path for
405+
`platform-core.activation-ledger-registration-home`'s 503-turnaround leg. It boots a
406+
showcase-derived configuration that does not declare automation: the `automation`
407+
token is taken out of `requires`, and the app plugins that depend on the automation
408+
service are left out (`packaged-activation-ledger-reach`). Since #22301,
409+
`bootStack(showcaseStack)` composes what `os serve` composes, so it mounts automation
410+
whether or not the `automation` option is passed.
406411

407412
### 8e. Checked and CLEAN (so the next sweep does not re-derive)
408413

‎docs/qa/platform-checklist/areas/platform-core.json‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,22 +1435,22 @@
14351435
"title": "The activation ledger's registration home: sys_metadata_activation is registered by PlatformObjectsPlugin under its OWN manifest, so packaged disable works with or without the automation service — one owner, datasource binding carried across the move",
14361436
"since": "v17",
14371437
"status": "active",
1438-
"revision": 3,
1438+
"revision": 4,
14391439
"priority": "P1",
14401440
"surface": "api",
14411441
"personas": ["seeded admin (admin@objectos.ai / admin123)"],
14421442
"fixtures": {
14431443
"app": "showcase",
14441444
"requires": [
14451445
"the with-automation composition is any stock boot: `objectstack dev`/`os serve` composes AutomationServicePlugin whenever the app requires the 'automation' capability token (packages/cli/src/commands/serve.ts CAPABILITY_PROVIDERS), and the showcase does — so the live-boot legs below need nothing beyond an isolated stock boot",
1446-
"the NO-automation composition — the one #12359 measured the 503 on — is `bootStack(showcaseStack)` from @objectstack/verify with the `automation` option omitted (packages/verify/src/harness.ts; the option is only honored), which is exactly how the pinned dogfood suite constructs it"
1446+
"the NO-automation composition — the one #12359 measured the 503 on — is a showcase-derived configuration that does not declare automation, booted with `bootStack` from @objectstack/verify: the showcase with the 'automation' token taken out of its `requires`, and with the plugins of its own `plugins` array that depend on com.objectstack.service-automation left out, which is exactly how the pinned dogfood suite constructs it (packages/qa/dogfood/test/packaged-activation-ledger-reach.dogfood.test.ts#showcaseWithoutAutomation). `bootStack(showcaseStack)` with the `automation` option omitted is NOT that composition: since #22301 bootStack composes what `os serve` composes, so the showcase's own `requires` mounts the automation service (packages/verify/src/harness.ts; the option only adds the service, never removes it)"
14471447
],
14481448
"knownGaps": [
14491449
"NO stock CLI path boots the showcase WITHOUT the automation service: serve/dev compose it from the app's own `requires`, so a manual runner cannot stage the no-automation composition with `os dev` flags alone. The no-automation legs therefore ride the pinned dogfood suite (automated.ref — its first describe carries an anti-vacuity control asserting the automation service is genuinely absent) or an authored scratch stack config that drops the requirement; the run records WHICH of the two its verdict rests on. Scoring those legs off a stock boot measures the wrong composition"
14501450
]
14511451
},
14521452
"steps": [
1453-
"run the pinned suite: pnpm --filter @objectstack/dogfood exec vitest run test/packaged-activation-ledger-reach.dogfood.test.ts — BOTH describes: '#12359 — actions and NO automation service' (the 503-turnaround leg, bootStack with no automation) and '#12159 Part 1 — a composition WITH automation' (the move's second end); capture the full output",
1453+
"run the pinned suite: pnpm --filter @objectstack/dogfood exec vitest run test/packaged-activation-ledger-reach.dogfood.test.ts — BOTH describes: '#12359 — actions and NO automation service' (the 503-turnaround leg, booted on the showcase-derived configuration that does not declare automation) and '#12159 Part 1 — a composition WITH automation' (the move's second end); capture the full output",
14541454
"live with-automation boot (stock `objectstack dev`, isolated port/DB), as admin: POST /api/v1/actions/_activation/showcase_task/showcase_mark_done {\"enabled\":false} → 200; GET /api/v1/data/sys_metadata_activation and capture the row (metadata_type 'action', name showcase_mark_done, active false/0) together with its KEY SET — organization_id is ABSENT from it, the ledger having no tenant column at all (#15024 / ADR-0131 D7). ⛔ Never capture the tenant half as a value: `row.organization_id ?? null` answers `null` for a column that does not exist, so a value read passes while measuring nothing (the sibling item platform-core.activation-ledger-row-contract owns the schema-side probe)",
14551455
"same boot, the flow half: POST /api/v1/automation/showcase_task_completed/toggle {\"enabled\":false} → 200; re-read the ledger — a metadata_type 'flow' row for showcase_task_completed appears BESIDE the action row, and the flow-name and action-name lists never cross (the discriminator is load-bearing, not decorative)",
14561456
"restore both switches ({\"enabled\":true} / toggle on) — both rows persist with active true (updated, not deleted); leave the boot as found",
@@ -1504,7 +1504,7 @@
15041504
"packages/platform-objects/src/plugin.ts#ACTIVATION_LEDGER_MANIFEST (the registration-home rationale — the measured 503; MOVE-not-add), (why the ledger rides its OWN manifest — the datasource-routing measurement), (ACTIVATION_LEDGER_MANIFEST), (the two register calls), (lean-kernel degradation: no manifest service → the door refuses loudly with 503 rather than keeping a bit that reverts)",
15051505
"packages/services/service-automation/src/plugin.ts#runObjectRegistered (the flow leg attaches by probe() of the real table — runObjectRegistered no longer vouches for it; not attached on a failed probe)",
15061506
"packages/spec/src/system/constants/platform-object-names.ts#PLATFORM_OBJECTS_BY_PACKAGE (PLATFORM_OBJECTS_BY_PACKAGE receipt)",
1507-
"packages/verify/src/harness.ts#bootStack (bootStack's automation option — how the no-automation composition is constructed)",
1507+
"packages/verify/src/harness.ts#bootStack (bootStack composes what the configuration declares, as `os serve` does, so the no-automation composition is a configuration that does not declare automation — the `automation` option only adds the service, never removes it)",
15081508
"docs/adr/0126-packaged-metadata-customization-model.md §4 (one generic activation ledger)",
15091509
"docs/adr/0131-total-organization-ownership-no-null-organization-id.md D7 (deployment-level state has no organization column — sys_metadata_activation is named there as reverted before 17.3 and not returning, which is why the row reads here assert a key set rather than a NULL value)",
15101510
"#12438 (the scoped sweep this item lands from), #12419 (the registration-home PR), #12359 (the 503 measurement + the 2026-08-26 「同意」 ruling: registration follows the declaration), #15024 (the column drop these read-backs are re-grounded against)"
@@ -1513,7 +1513,7 @@
15131513
{
15141514
"revision": 1,
15151515
"date": "2026-08-26",
1516-
"change": "new — authored in the #12438 scoped sweep (ADR-0126 disable+clone). Grounding fixed the item's shape twice: (1) the no-automation composition has NO stock CLI path (serve/dev compose automation from the app's own requires), so that leg rides the pinned dogfood suite's bootStack(showcaseStack) with the automation option omitted, recorded as a knownGap rather than pretended manual; (2) the single-owner clause is proven by the boot succeeding plus a registry read, because a double registration is a boot FAILURE (registerObject throws), never an observable duplicate",
1516+
"change": "new — authored in the #12438 scoped sweep (ADR-0126 disable+clone). Grounding fixed the item's shape twice: (1) the no-automation composition has NO stock CLI path (serve/dev compose automation from the app's own requires), so that leg rides the pinned dogfood suite's bootStack(showcaseStack) with the automation option omitted [superseded at revision 4: the suite now boots a showcase-derived configuration that does not declare automation], recorded as a knownGap rather than pretended manual; (2) the single-owner clause is proven by the boot succeeding plus a registry read, because a double registration is a boot FAILURE (registerObject throws), never an observable duplicate",
15171517
"ref": "#12438"
15181518
},
15191519
{
@@ -1527,6 +1527,12 @@
15271527
"date": "2026-10-04",
15281528
"change": "A5 / step 6 re-pointed: the literal array count is dropped — it read 41, the 'platform-objects' array holds 46 names at 316be321ef (platform-object-names.ts), and the count moves with every platform object added while the one-owner membership is the point. Per RUNNER.md's convention for counts that move (the discriminator is the fact, never its digits; a literal is pinned only where the validator enforces it, as enumSource does), the clause now asserts single-array membership and records the count as context only. Assertion defect",
15291529
"ref": "#21735"
1530+
},
1531+
{
1532+
"revision": 4,
1533+
"date": "2026-10-09",
1534+
"change": "the no-automation composition is restated as the dogfood suite now builds it. #22301 made bootStack compose what `os serve` composes, the app's own `requires` included, so `bootStack(showcaseStack)` with the automation option omitted now mounts the automation service and no longer stages the #12359 composition. The suite boots a showcase-derived configuration that does not declare automation instead: the 'automation' token taken out of `requires`, and the app plugins that depend on the automation service left out. fixtures.requires, step 1, the harness source entry and revision 1's history note are re-said to match. Steps, legs, clauses and expected verdicts are otherwise unchanged, and the anti-vacuity control is still the suite's first test",
1535+
"ref": "#22301"
15301536
}
15311537
]
15321538
},

‎packages/verify/src/harness.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,14 @@ const SHARED_BOOTS = new WeakMap<object, Map<unknown, Promise<VerifyStack>>>();
13111311
* even one spelled identically, is a different stack: the memo never guesses
13121312
* that two `SecurityPlugin` instances mean the same thing.
13131313
*
1314+
* A different stack over the SAME `config` is still a second boot of that
1315+
* configuration, so the instance rule `bootStack` enforces applies to it: while
1316+
* the first key's boot is live, a second `opts` key on that `config` is refused
1317+
* (`RESOURCE_CONFLICT`, status 409), the returned promise rejects, and the memo
1318+
* drops that key so a later caller boots again. To keep two stacks live at
1319+
* once, pass the second a configuration BUILT AGAIN (its builder called once
1320+
* more, or a fresh module instance), never a `{ ...config }` copy.
1321+
*
13141322
* Sharing only makes sense under `isolate: false` (files in one worker share
13151323
* one module registry); under vitest's default isolation every file still
13161324
* boots its own. The eligibility rules dogfood wrote for its shared stack

0 commit comments

Comments
 (0)