Skip to content

Commit 0904d85

Browse files
fix(dogfood): the wall /packages case's operator package declares the CRM dependency its view binds (#24724)
Fixes #24700 Clause-②: no The hourly full run on `main` (run 39345244575, `5018fc303d`) is red in `Dogfood Regression Gate (2/3)` on one case of `packages/qa/dogfood/test/wall-environment-metadata-refusal.dogfood.test.ts`. The case is "/packages: each of the thirteen write doors refuses A 403 PERMISSION_DENIED, and B reads the operator's package unchanged", and it fails in both walled postures (`isolated` and `group`) at `expect(commits.length).toBeGreaterThan(0)`. ## Cause (measured, see Evidence) - The case's operator creates `com.em.iso.op` / `com.em.gro.op` with no `dependencies`. - It saves a draft list view bound to `crm_lead` into that package, then calls `POST /packages/:id/publish-drafts`. - Since PR #24630 (`9f12d06770`, the ruled narrowing), a package draft publish judges a list view against the package's closure. `crm_lead` belongs to `com.example.crm`, which is outside that closure. - The publish therefore answers `200` with `data.success: false`, `outcome: refused`, `publishedCount: 0`. - The case's `ok()` reads only the status, so the refusal surfaced three lines later as zero commits. The narrowing is ruled and stays. This PR changes one test file and nothing else. ## What changes (one case, one file) - **The operator's package declares the dependency.** At `POST /packages` the package now declares `dependencies: { 'com.example.crm': '*' }`, in the bare-manifest body the case already sent. - `dependencies` is a declared `ManifestSchema` row, and `PackageInstallManifestSchema` extends `ManifestSchema`. - The view stays on `crm_lead`, so every later line of the case keeps the names it read before: the re-save as draft, the orphan view, the thirteen doors and B's reads. Zero downstream lines change. - The other shape, binding the view to an object inside the closure, would have needed a new object in the package and a rename of every `crm_lead.em_PREFIX_pv` reference in the case. - **The publish line asserts the receipt**, in `data`: `{ success: true, outcome: 'published', publishedCount: 1 }`, with `failed[]` as the assertion message. - A refused publish now fails at the publish line and names its cause. - The top-level `success` of the `200` envelope is constant, so the assertion reads `data`, as `view-container-cross-package-default.dogfood.test.ts` does. - **`CRM_PKG`** names `com.example.crm`, which `examples/app-crm/objectstack.config.ts` declares as `manifest.id`. This follows the same literal convention as that sibling file's `SHOWCASE`. ## Evidence Every run used the same command, `pnpm exec vitest run --maxWorkers=2 test/wall-environment-metadata-refusal.dogfood.test.ts`, run in `packages/qa/dogfood` under the shared verify lock. - The closure was built by `pnpm turbo run build --filter=@objectstack/dogfood`, the dogfood gate's own build step. - The file runs the case in both walled postures, so one run covers both variants the gate runs, plus the `single` control. | Run | Tree | Result | |---|---|---| | R1 (red) | base `1c1144fcd`, the file as on `main` | `2 failed, 21 passed`. In both postures: `AssertionError: expected 0 to be greater than 0` at `:242:30`, the hourly run's line | | R2 (cause isolated) | same tree and the same `main` file. Only #24630's rule-context line is removed from `packages/lint/src/runtime-gate.ts` (the `packageScopeInForce(args.packageScope)` spread). Lint rebuilt, `ablation-dist-preflight --absent` green | `23 passed` | | R3 (green) | `70fe76cd6`, this change | `23 passed` | | R4 (reverse leg) | `70fe76cd6` with the dependency emptied (`dependencies: {}`) | `2 failed` at the new publish line, `:253:10`. The message is the refusal: `view/crm_lead.em_iso_pv failed author-time validation: 1 issue, views[0].object [list-view-object-outside-closure]`. Its hint: "Declare a dependency on the package that defines crm_lead in the dependencies of com.em.iso.op's manifest" (the `group` posture is the same with `em_gro`) | | R6 (final) | `a11658eb2`, after merging `origin/main` `78b1a63bc` | closure rebuilt (66/66), `23 passed` | - **Why R2 instead of a run at `9f12d06770^`.** R2 stands in for that run and is a single-variable control on the current tree. With only the narrowing's line gone, the unchanged case is green. So the narrowing is the case's only cause of red, and no other line of the case is stale against it. - **Mutation discipline.** Each mutation ran through `scripts/ablation-replace.mjs` (WRAP), was restored and proven hash-equal to `HEAD`, and left a clean tree. - After R2, lint was rebuilt and `ablation-dist-preflight` found the line back in all four lint `dist` files. - The first R4 attempt was refused by the tool before any run, because its replacement text overlapped the anchor. It ran nothing. - No permanent test file was added. - **Typecheck and gates.** - `tsc --noEmit` in `packages/qa/dogfood` exits 0 on `a11658eb2`, and `--listFiles` includes this file. - The derived gate families (`dispatch-gates.mjs --commands`, 55) ran on `a11658eb2`: 54 exit 0. `check:dual-build-cjs-loads` exits 3 with PREREQUISITE NOT MET, because it reads every package's `dist` and eight packages outside the dogfood closure are unbuilt locally. That is NOT MEASURED locally, and CI's `Lint & Repo Gates` reads it. - `pnpm lint` on `a11658eb2`: `LINT_EXIT=0` (the `eslint . --no-inline-config` run printed no finding). Dogfood is not in the PR or queue fast set; it runs on `main`'s hourly full battery. The runs above are the pre-merge reading, and the next hourly run refreshes #24700. ## Changeset None. `skip-changeset`: the diff is one file under `packages/qa/dogfood/test/`, `@objectstack/dogfood` is `"private": true`, and it ships nothing. ## Acceptance notes - **The other two cases' publish lines still read a refused publish as a write.** These are the walled `CONTROL: the platform operator writes at every one of these doors` and the `single (CONTROL)` case in this same file. Both were held off by this dispatch, so they are not changed here. - An instrumented run of the committed file logged every `publish-drafts` receipt (R5, restored and proven hash-equal to `HEAD`). This case's two publishes answer `published, publishedCount 1`. - Three CONTROL publishes answer `200` with `success: false, outcome: refused, publishedCount: 0`, each with `list-view-object-outside-closure`: `crm_lead.em_iso_pkgv` into `com.em.iso.opctl`, `crm_lead.em_gro_pkgv` into `com.em.gro.opctl`, and `crm_lead.em_sin_pv` into `com.em.sin.a`. - Their status-only `ok()` reads each as "the operator writes" (walled) or "writes as before" (`single`), so these controls measure nothing at the publish-drafts door. - The same two-line shape applies there: declare `com.example.crm` at create and assert the receipt. Raised with the seat. - `ok()` stays status-only for every other line. Only this case's publish line reads the receipt. --- _Generated by [Claude Code](https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent d0514e6 commit 0904d85

1 file changed

Lines changed: 16 additions & 2 deletions

File tree

‎packages/qa/dogfood/test/wall-environment-metadata-refusal.dogfood.test.ts‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ function packageBody(id: string, version: string, objects: unknown[] = []) {
8484

8585
const META_AUTHOR = 'em_meta_author';
8686
const CATALOG_PKG = 'com.em.catalog';
87+
/** The package that ships `crm_lead` (`examples/app-crm/objectstack.config.ts`, `manifest.id`). */
88+
const CRM_PKG = 'com.example.crm';
8789

8890
interface Harness {
8991
stack: VerifyStack;
@@ -234,9 +236,21 @@ for (const posture of ['isolated', 'group'] as const) {
234236
it("/packages: each of the thirteen write doors refuses A 403 PERMISSION_DENIED, and B reads the operator's package unchanged", async () => {
235237
const pkg = `com.em.${p}.op`;
236238
const pv = `crm_lead.em_${p}_pv`;
237-
await ok(await h.stack.apiAs(h.operator, 'POST', '/packages', { id: pkg, name: 'op', version: '1.0.0', type: 'app' }), 201);
239+
// The operator's package DECLARES the CRM package as a dependency: a
240+
// package's draft publish judges a list view against the package's
241+
// closure and refuses a binding outside it
242+
// (`list-view-object-outside-closure`, #24563), and `crm_lead` is the
243+
// CRM package's object.
244+
await ok(await h.stack.apiAs(h.operator, 'POST', '/packages',
245+
{ id: pkg, name: 'op', version: '1.0.0', type: 'app', dependencies: { [CRM_PKG]: '*' } }), 201);
238246
await ok(await h.stack.apiAs(h.operator, 'PUT', `/meta/view/${pv}?package=${pkg}&mode=draft`, view(pv, 'c1')));
239-
await ok(await h.stack.apiAs(h.operator, 'POST', `/packages/${pkg}/publish-drafts`, {}));
247+
// A 200 is not a publish: a refused batch answers 200 with `success: false`
248+
// in `data`, so the receipt is read here, where a refusal names its cause.
249+
const published = await h.stack.apiAs(h.operator, 'POST', `/packages/${pkg}/publish-drafts`, {});
250+
await ok(published);
251+
const receipt = ((await published.json()) as { data?: Record<string, unknown> }).data;
252+
expect({ success: receipt?.success, outcome: receipt?.outcome, publishedCount: receipt?.publishedCount }, JSON.stringify(receipt?.failed))
253+
.toEqual({ success: true, outcome: 'published', publishedCount: 1 });
240254
const commits = (((await (await h.stack.apiAs(h.operator, 'GET', `/packages/${pkg}/commits`)).json()) as any).data?.commits ?? [])
241255
.map((c: { id: string }) => c.id);
242256
expect(commits.length).toBeGreaterThan(0);

0 commit comments

Comments
 (0)