Skip to content

Commit a06feb7

Browse files
fix(cloud-connection)!: install-local judges a ledger entry in the CLI's lifted shape through the install door's flatten, at boot and on the sample-data doors (#28683)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XQQCpJU17CHdHiXkbKYBsc8)_ Fixes #28604 Clause-②: no (narrowing) — at `kernel:ready` the install-local rehydrate no longer loads, and `POST /api/v1/marketplace/install-local/:manifestId/reseed-sample-data` and `/purge-sample-data` answer 422 `OS_PROTOCOL_INCOMPATIBLE` for, a ledger entry an install wrote before #28535 in the body `os package install FILE` sends (`id` and `version` lifted beside a nested `manifest`) whose nested `engines.protocol` excludes this runtime. That entry loaded, and those doors acted on it, before. A flat entry with the same range was already refused the same way. Nothing is widened. Metadata-protocol: none Skills-docs: none — the pages already state the behaviour this restores: `skills/objectstack-platform/SKILL.md` ("The runtime checks it before it loads anything and refuses an out-of-range app at the boundary"). No page described a ledgered CLI install as an exception. **BREAKING** boot-load and accept-set narrowing for already-ledgered install-local entries (`@objectstack/cloud-connection`): the `kernel:ready` rehydrate and the two sample-data doors, reached by a deployment where `os package install ./dist/objectstack.json` ran before PR #28603. ## 维护者速读 - **改了什么**:启动时重新加载已安装应用(`kernel:ready` rehydrate)、以及「重置/清除示例数据」两个接口,读台账条目时先经安装接口已有的同一个展平函数(`flattenPackageBody`)再做协议握手;rehydrate 的依赖排序也读展平后的条目。 - **为什么改**:卡 #28604。PR #28603 之前,`os package install` 发来的产物(`id`/`version` 提到顶层、`engines` 嵌在 `manifest` 下)原样写进台账。启动时只看顶层 `engines`,读不到嵌套的范围,于是不兼容协议的包每次启动都照常加载,没有任何报错。 - **风险与回滚**:只影响「PR #28603 之前用 CLI 装的、且协议范围不兼容」的台账条目:现在与平铺条目同样处理(不加载、一条 error 日志、列表标 `notLoaded`、两个示例数据接口答 422,DELETE 与兼容版本重装照常)。兼容的条目照旧加载,注册的内容与台账一字不差。回滚即 revert。 - **先看**:`packages/cloud-connection/src/marketplace-install-local-plugin.ts`(rehydrate 握手 ≈:1109、`inDependencyOrder`、`refuseProtocolIncompatibleEntry`、`flattenPackageBody` 的空值守卫);`marketplace-install-local-protocol-handshake.test.ts` 新 `[#28604]` 用例组;`.changeset/28604-install-local-rehydrate-lifted-entry.md`。 - **两分钟验证**:`pnpm --filter @objectstack/cloud-connection exec vitest run src/marketplace-install-local-protocol-handshake.test.ts src/marketplace-install-local-sample-data-not-loaded.test.ts src/marketplace-install-local-rehydrate-order.test.ts` → 37/37 通过。 - **需要你做的**:无,席位落地(收窄需先过合约评审)。 ## What changes **The one flatten, read at three more places** (`packages/cloud-connection/src/marketplace-install-local-plugin.ts`). An install before PR #28603 ledgered the CLI's body as it arrived: `{ id, version, manifest: { …, engines, dependencies }, objects, data, … }`. Every reader that decides by a manifest key read it at the top, so the nested keys were invisible. Each now reads the entry through `flattenPackageBody`, the function the install door already uses (no second flatten, no second handshake): - the `kernel:ready` rehydrate's `checkProtocolCompat(flattenPackageBody(entry.manifest))`, so an excluded nested range is not loaded, is recorded in `refusedAtRehydrate` (the listing's `notLoaded` marker) and gets the one error line, exactly as a flat entry; - `refuseProtocolIncompatibleEntry`, the reseed/purge doors' handshake, so both answer the install route's 422 for it before any side effect; - `inDependencyOrder`, so a lifted entry is ordered by the `dependencies` its nested manifest declares (ADR-0141 D5). At boot, a flat entry's dependencies are used for this ordering only (the rehydrate runs no dependency check: `checkDeclaredDependencies` is called only at the install door), so ordering is the parity a lifted entry is owed. `flattenPackageBody` gains a `!isPlainObject(body)` guard so it is total over a ledger value, which `LocalManifestSource` does not shape-check. Measured: a `null`, a string and a number `manifest` produce byte-identical rehydrate logs, registrations and door answers on base and head (probe in the evidence folder below). **What stays as today.** A compatible lifted entry registers, converts, binds and seeds the body exactly as ledgered: only the judging and the ordering read the flattened view. The install door and its #28603 flatten are untouched. ## Why ADR-0087 D1 puts the handshake "before loading a package's metadata", and a rehydrate is a load (#21762). The triage (`6557784727`) graded the reach p3: one closed population, the entries a CLI install wrote before PR #28603 (`7449e479`), since new installs are refused at the door. ## Tests - `marketplace-install-local-protocol-handshake.test.ts`, new group `[#28604] kernel:ready rehydrate of an entry ledgered in the CLI's lifted shape`: the lifted `^17` entry is not registered, nothing is synced, and its error lines equal a flat `^17` entry's on a fresh ledger, byte for byte; the GET listing marks it `notLoaded { code: OS_PROTOCOL_INCOMPATIBLE, requiredRange: ^17 }`; lit control: a lifted `^18` entry rehydrates with no error and registers the body deep-equal to the ledgered one. - `marketplace-install-local-sample-data-not-loaded.test.ts`, new group: precondition (lifted refused not loaded, lifted loadable loaded); reseed and purge on the lifted refused entry answer 422, leave i18n, seed-datasets, the engine and the ledger unchanged, and their payloads equal the flat entry's byte for byte; lit control: both doors answer 200 on the lifted loadable entry. - `marketplace-install-local-rehydrate-order.test.ts`: a lifted module entry listed ahead of its parent registers after it. **Red on base** (plugin source swapped to `origin/main` `0b6a52e8`; on disk: flatten-at-judge count 0, base judge count 2; restored with `git checkout HEAD --`, `git diff HEAD` empty, blob hash equal to HEAD): 5 failed / 32 passed of 37, exactly the five lifted-shape cases above that judge or order; both lit controls pass on both trees. **Green** on head: 37/37. **Ablation**, one leg per site, through `scripts/ablation-replace.mjs` (anchor 1 → 0 on disk, restored to the HEAD blob, `git diff HEAD` empty after each). The tests import the plugin by relative path (source), so there is no dist leg. | Leg | Mutation | Red | |---|---|---| | 1 | rehydrate judges `entry.manifest` | 3: the two boot cases + the sample-data precondition | | 2 | door judges `entry.manifest` | 1: reseed/purge byte parity | | 3 | order reads `entry.manifest` | 1: the lifted dependency order | ## Acceptance notes - **Pre-existing, not changed:** a ledger file whose `manifest` is `null` throws `Cannot read properties of null (reading 'engines')` from `checkProtocolCompat`, at the rehydrate (logged as "rehydrate failed") and out of the reseed/purge handlers. Identical on base and head. No door or producer writes such an entry, so it is noted here and not filed. - Overlap: re-read the 17 open PRs before `pr_create`; none touches the plugin file or the three test files. ## Verification All on head `365d3ac3` (the only commit; the base is `origin/main` `0b6a52e8`). - `pnpm --filter @objectstack/cloud-connection test`: 65 files / 796 tests passed. `pnpm --filter @objectstack/cloud-connection typecheck`: exit 0 (under the verify lock, `VERDICT command-exit 0`). - The three touched files alone: 37/37. - **Full dogfood battery** (owed by the `(narrowing)` arm), closure built then `pnpm --filter @objectstack/dogfood test` under the lock: `VERDICT command-exit 0` (held 2009s) · Test Files 369 passed, 3 skipped (372) · Tests 2981 passed, 28 skipped (3009) · no failures. - **Derived gates:** `dispatch-gates --commands --repo objectstack-ai/objectstack` derived 60 families; all 60 exit 0, exit codes captured before any pipe. `--ran`: "60 derived famil(ies) accounted for — 60 run, 0 NOT-MEASURED (a DERIVED zero — all 60 recorded an exit code and none of them is 3)". Two first runs were not measurements and were re-run to exit 0: `check-plugin-teardown-shape --self-test` exited 3 on the shallow clone until its pinned fixture commit was fetched, and `check:dual-build-cjs-loads` exited 3 until a full `packages/**` build. Record: project file `pm-orders/server/evidence/28604/gates-ran-365d3ac3.txt`. - CI families (lint, Test Core shards and the rest) are read from this PR's check-runs, not re-run locally. - Non-object ledger probe (base vs head byte-identical): `pm-orders/server/evidence/28604/non-object-ledger-probe.txt`. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes (CI). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: n/a. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the 维护者速读 section is the lane's required maintainer summary). - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01FEkQ5piHvtwq5wxJ2RJYfj --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5piHvtwq5wxJ2RJYfj)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent c946cb9 commit a06feb7

5 files changed

Lines changed: 167 additions & 10 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/cloud-connection': patch
3+
---
4+
5+
fix(cloud-connection)!: install-local judges a ledger entry stored in the CLI's lifted shape through the install door's flatten, at the boot rehydrate and on the sample-data doors (#28604)
6+
7+
Clause-②: no (narrowing) — at `kernel:ready` the install-local rehydrate no longer loads, and `POST /api/v1/marketplace/install-local/:manifestId/reseed-sample-data` and `/purge-sample-data` answer 422 `OS_PROTOCOL_INCOMPATIBLE` for, a ledger entry written by an install before #28535 in the body `os package install <file>` sends (`id` and `version` lifted beside a nested `manifest`) whose nested `engines.protocol` excludes this runtime. Such an entry loaded and those doors acted on it before. A flat entry with the same range was already refused the same way. Nothing is widened.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type changes: the rehydrate's existing ADR-0087 handshake, its ADR-0141 D5 dependency order and the sample-data doors' handshake now read a lifted ledger entry through the flatten the install door already uses, as they already read a flat entry. The refusals are the existing shared answers, so there is nothing for a migration to rewrite. -->
11+
12+
**BREAKING** boot-load and accept-set narrowing for already-ledgered install-local entries (`@objectstack/cloud-connection`): the `kernel:ready` rehydrate and the two sample-data doors, reached by a deployment where `os package install ./dist/objectstack.json` ran before #28535.
13+
14+
An install before #28535 ledgered the CLI's body unflattened, so the entry's `engines` and `dependencies` sat one level down, and the rehydrate and the sample-data doors judged `engines.protocol` at the top: an entry built for another protocol was re-registered at every boot with no error. They now read the entry through the install door's one flatten (`flattenPackageBody`), and the rehydrate orders entries by the dependencies they nest too. Such an entry gets exactly what a flat entry with the same range gets: it is not loaded, one error line names the code, the package and the remedy, the listing marks it `notLoaded`, and DELETE or a compatible reinstall still act on it. A compatible lifted entry rehydrates exactly as before, registering the body as it was ledgered.

‎packages/cloud-connection/src/marketplace-install-local-plugin.ts‎

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -609,9 +609,18 @@ function declaredPackageManifest(body: Record<string, any>): Record<string, any>
609609
* is the permission-set collection, its `objects` the definitions — the
610610
* flatten order `AssembledPackageBodySchema` records). A body already flat is
611611
* returned as it is.
612+
*
613+
* [#28604] Also how a LEDGER entry is read wherever a decision reads its
614+
* manifest keys — the `kernel:ready` rehydrate's handshake and dependency
615+
* order, and the sample-data doors' handshake: an install before #28535
616+
* ledgered the body `os package install <file>` sends unflattened (`id` and
617+
* `version` lifted beside the nested `manifest`), so its `engines` and
618+
* `dependencies` sit one level down. Total: a ledger value is not
619+
* shape-checked ({@link LocalManifestSource}), and anything that is not a
620+
* plain object is handed back as it is.
612621
*/
613622
function flattenPackageBody(body: Record<string, any>): Record<string, any> {
614-
if (!isPlainObject(body.manifest)) return body;
623+
if (!isPlainObject(body) || !isPlainObject(body.manifest)) return body;
615624
const { manifest: meta, ...sections } = body;
616625
return { ...meta, ...sections };
617626
}
@@ -1093,7 +1102,11 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
10931102
// `checkProtocolCompat` is the handshake's own judge. Only a
10941103
// positive incompatibility is acted on: an absent or
10951104
// unrecognised range rehydrates exactly as it did before.
1096-
const compat = checkProtocolCompat(entry.manifest);
1105+
// [#28604] Judged on the entry as the install door reads a body
1106+
// ({@link flattenPackageBody}), so an entry ledgered in the
1107+
// CLI's lifted shape before #28535 is judged by the range it
1108+
// nests, exactly as a flat entry with that range is.
1109+
const compat = checkProtocolCompat(flattenPackageBody(entry.manifest));
10971110
if (compat.status === 'incompatible') {
10981111
// [#21822] Recorded where the GET listing reads it, so the
10991112
// entry is listed as not loaded rather than as installed.
@@ -1168,9 +1181,12 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
11681181
*/
11691182
private inDependencyOrder(ctx: PluginContext, entries: InstalledManifestEntry[]): InstalledManifestEntry[] {
11701183
const { ordered, cycles } = orderHydrationBatch(entries.map((entry) => {
1171-
const bodies: unknown[] = Array.isArray(entry.manifest?.packages)
1172-
? entry.manifest.packages.map((p: { manifest?: unknown } | null) => p?.manifest)
1173-
: [entry.manifest];
1184+
// [#28604] Read as the install door reads a body, so a lifted
1185+
// entry's nested `dependencies` order it as a flat entry's do.
1186+
const body = flattenPackageBody(entry.manifest);
1187+
const bodies: unknown[] = Array.isArray(body?.packages)
1188+
? body.packages.map((p: { manifest?: unknown } | null) => p?.manifest)
1189+
: [body];
11741190
return {
11751191
item: entry,
11761192
label: entry.manifestId,
@@ -3833,8 +3849,9 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
38333849
* (`protocolIncompatibleAnswer`). Each door asks this right after it has
38343850
* read the entry, before any side effect.
38353851
*
3836-
* Judged with `checkProtocolCompat` on the entry itself, as the rehydrate
3837-
* judges it, and NOT read from {@link refusedAtRehydrate}: an entry another
3852+
* Judged with `checkProtocolCompat` on the entry itself, read through
3853+
* {@link flattenPackageBody} as the rehydrate judges it (#28604), and NOT
3854+
* read from {@link refusedAtRehydrate}: an entry another
38383855
* runtime wrote to a shared ledger after this boot was never rehydrated
38393856
* here, and the doors refuse it all the same. Only a positive
38403857
* incompatibility is refused. An absent or unrecognised range is admitted,
@@ -3843,7 +3860,7 @@ export class MarketplaceInstallLocalPlugin implements Plugin {
38433860
* operator gets out of the refused state.
38443861
*/
38453862
private refuseProtocolIncompatibleEntry = (c: any, entry: InstalledEntry): Response | undefined => {
3846-
const compat = checkProtocolCompat(entry.manifest);
3863+
const compat = checkProtocolCompat(flattenPackageBody(entry.manifest));
38473864
if (compat.status !== 'incompatible') return undefined;
38483865
const refusal = protocolIncompatibleAnswer(new ProtocolIncompatibleError(compat.diagnostic));
38493866
return c.json({

‎packages/cloud-connection/src/marketplace-install-local-protocol-handshake.test.ts‎

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ function expectNothingWritten(h: { installRegistrations: () => unknown[]; instal
174174
expect(readdirSync(dir).sort()).toEqual(files);
175175
}
176176

177-
function seedEntry(manifest: { id: string; version: string }) {
178-
new LocalManifestSource(dir).write({
177+
function seedEntry(manifest: { id: string; version: string }, into: string = dir) {
178+
new LocalManifestSource(into).write({
179179
packageId: manifest.id,
180180
versionId: manifest.version,
181181
manifestId: manifest.id,
@@ -432,6 +432,59 @@ describe('kernel:ready rehydrate of an already-ledgered incompatible entry', ()
432432
});
433433
});
434434

435+
/**
436+
* [#28604] A ledger entry an install wrote BEFORE #28535: the body
437+
* `os package install <file>` sends, stored as it arrived (manifest nested,
438+
* `id` and `version` lifted beside it). The rehydrate reads it through the
439+
* install door's one flatten, so the outcome is the flat entry's, measured on
440+
* the same harness: on `origin/main` before this change the `^${OLD_MAJOR}` lifted
441+
* entry was registered and synced with no error line.
442+
*/
443+
describe('[#28604] kernel:ready rehydrate of an entry ledgered in the CLI\'s lifted shape', () => {
444+
const errors = (h: { logger: { error: { mock: { calls: unknown[][] } } } }) =>
445+
h.logger.error.mock.calls.map(([m]) => String(m));
446+
const lifted = (manifest: Record<string, any>) => cliInstallBody(compiledArtifact(manifest)).manifest;
447+
448+
it(`an excluded nested range (${OLD_RANGE}) is NOT loaded: the error line a flat entry with that range gets, byte for byte`, async () => {
449+
seedEntry(lifted(INCOMPATIBLE));
450+
const h = await mount(dir);
451+
expect(h.bootRegistrations().map((m) => m.id)).not.toContain(OLD_ID);
452+
expect(h.bootSyncs).toBe(0);
453+
const said = errors(h);
454+
expect(said).toHaveLength(1);
455+
expect(said[0]).toContain('OS_PROTOCOL_INCOMPATIBLE');
456+
457+
// The flat entry with the same range, on a fresh ledger: the same line.
458+
const flatDir = mkdtempSync(join(tmpdir(), 'mil-protocol-flat-'));
459+
try {
460+
seedEntry(INCOMPATIBLE, flatDir);
461+
expect(said).toEqual(errors(await mount(flatDir)));
462+
} finally {
463+
rmSync(flatDir, { recursive: true, force: true });
464+
}
465+
});
466+
467+
it('the entry is listed as not loaded, with the nested range', async () => {
468+
seedEntry(lifted(INCOMPATIBLE));
469+
const h = await mount(dir);
470+
const res = await h.routes.get(`GET ${ROUTE}`)!(makeC(undefined));
471+
expect(res.status, JSON.stringify(res.payload)).toBe(200);
472+
const item = res.payload.data.items.find((i: any) => i.manifestId === OLD_ID);
473+
expect(item.notLoaded).toEqual({ code: 'OS_PROTOCOL_INCOMPATIBLE', requiredRange: OLD_RANGE });
474+
});
475+
476+
it(`lit control: a lifted entry with ${CURRENT_RANGE} rehydrates as before, registering the body as ledgered`, async () => {
477+
const body = lifted(COMPATIBLE);
478+
seedEntry(body);
479+
const h = await mount(dir);
480+
expect(errors(h)).toEqual([]);
481+
const registered = h.bootRegistrations().filter((m) => m.id === COMPATIBLE.id);
482+
expect(registered).toHaveLength(1);
483+
expect(registered[0]).toEqual(body);
484+
expect(h.bootSyncs).toBe(1);
485+
});
486+
});
487+
435488
describe('[#26480] a package AHEAD of this runtime is told to upgrade, never to migrate', () => {
436489
/** The card's reproduction: a floor major one above the running protocol. */
437490
const NEW_MAJOR = PROTOCOL_MAJOR + 1;

‎packages/cloud-connection/src/marketplace-install-local-rehydrate-order.test.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,16 @@ describe('kernel:ready rehydrate — ADR-0141 D5 dependency order', () => {
9494
expect(cycle).toContain('com.acme.a');
9595
expect(cycle).toContain('com.acme.b');
9696
});
97+
98+
it('[#28604] orders an entry ledgered in the CLI\'s lifted shape by the dependencies its nested manifest declares', async () => {
99+
// What an install before #28535 ledgered for `os package install <file>`:
100+
// the manifest nested, `id` and `version` lifted beside it.
101+
const lifted: InstalledManifestEntry = {
102+
...REVENUE,
103+
manifest: { id: REVENUE.manifestId, version: '1.0.0', manifest: { ...REVENUE.manifest } },
104+
};
105+
const { order, errors } = await rehydrate(dir, [lifted, HOTCRM]);
106+
expect(order).toEqual(['app.objectstack.hotcrm', 'app.objectstack.hotcrm.revenue']);
107+
expect(errors.filter((e) => e.includes('cycle'))).toEqual([]);
108+
});
97109
});

‎packages/cloud-connection/src/marketplace-install-local-sample-data-not-loaded.test.ts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,67 @@ describe('reseed and purge on an entry the rehydrate refused to load', () => {
350350
});
351351
});
352352

353+
/**
354+
* [#28604] An entry an install wrote before #28535, in the body
355+
* `os package install <file>` sends: the manifest nested under `manifest:`,
356+
* `id` and `version` lifted beside it, the collections at the top. The doors
357+
* read it through the install door's one flatten, so it is answered as the
358+
* flat entry with the same range is.
359+
*/
360+
function liftedEntryOf(flat: { id: string; version: string; [k: string]: unknown }) {
361+
const { objects, translations, data, ...manifest } = flat;
362+
return {
363+
manifest,
364+
objects,
365+
...(translations !== undefined ? { translations } : {}),
366+
data,
367+
id: flat.id,
368+
version: flat.version,
369+
};
370+
}
371+
372+
describe('[#28604] reseed and purge on an entry ledgered in the CLI\'s lifted shape', () => {
373+
const LIFTED_REFUSED = liftedEntryOf(REFUSED);
374+
const LIFTED_LOADED = liftedEntryOf(LOADED);
375+
376+
it('PRECONDITION: the rehydrate refused the lifted old entry and loaded the lifted current one', async () => {
377+
const h = await restartWith([LIFTED_REFUSED, LIFTED_LOADED], dir);
378+
expect(h.registered()).toContain(LOADED.id);
379+
expect(h.registered()).not.toContain(REFUSED.id);
380+
const said = h.logger.error.mock.calls.map(([m]) => String(m));
381+
expect(said).toHaveLength(1);
382+
expect(said[0]).toContain(`OS_PROTOCOL_INCOMPATIBLE: ${REFUSED.id}@1.0.0 is NOT loaded`);
383+
});
384+
385+
it('reseed and purge answer the flat entry\'s 422, byte for byte, and nothing moves', async () => {
386+
const h = await restartWith([LIFTED_REFUSED, LIFTED_LOADED], dir);
387+
const before = h.state();
388+
const reseed = await h.reseed(REFUSED.id);
389+
const purge = await h.purge(REFUSED.id);
390+
expect([reseed.status, reseed.payload.error.code]).toEqual([422, 'OS_PROTOCOL_INCOMPATIBLE']);
391+
expect(h.state()).toEqual(before);
392+
expect(h.tables.qa_old_account).toEqual([{ id: 'o1', name: 'Acme' }]);
393+
394+
const flatDir = mkdtempSync(join(tmpdir(), 'mil-sample-not-loaded-flat-'));
395+
try {
396+
const flat = await restartWith([REFUSED, LOADED], flatDir);
397+
expect(JSON.stringify(reseed.payload)).toBe(JSON.stringify((await flat.reseed(REFUSED.id)).payload));
398+
expect(JSON.stringify(purge.payload)).toBe(JSON.stringify((await flat.purge(REFUSED.id)).payload));
399+
} finally {
400+
rmSync(flatDir, { recursive: true, force: true });
401+
}
402+
});
403+
404+
it('lit control: on the lifted loadable entry both doors answer 200, as before', async () => {
405+
const h = await restartWith([LIFTED_REFUSED, LIFTED_LOADED], dir);
406+
const reseed = await h.reseed(LOADED.id);
407+
expect(reseed.status, JSON.stringify(reseed.payload)).toBe(200);
408+
const purge = await h.purge(LOADED.id);
409+
expect(purge.status, JSON.stringify(purge.payload)).toBe(200);
410+
expect(purge.payload.data).toMatchObject({ manifestId: LOADED.id, deleted: 1 });
411+
});
412+
});
413+
353414
describe('reseed and purge on a loadable entry answer as before', () => {
354415
it('reseed: 200 with the loader\'s counts, and its side effects land (the control for every "unchanged" above)', async () => {
355416
const h = await restartWith([REFUSED, LOADED], dir);

0 commit comments

Comments
 (0)