Skip to content

Commit e895dda

Browse files
committed
docs(plugin-dev): the production doors are not uniform, in every carrier
The at-tier review's fail basis: this PR tightened the `os validate` clause in two carriers and left it flat in three, while its own changeset called the flat wording an overstatement. Three named sites, all comment-only: - dev-plugin.ts:386 -- the docblock sentence, which ships to dist/*.d.ts and was a flat universal claim over BOTH rows of its own table. - dev-plugin-malformed-stack-posture.test.ts:4 -- the file header. - same file, the comment on the MISSING_IDENTITY case, which asserted the false half about the very fixture it is attached to. Extended to two more carriers so the PR does not ship a THIRD posture in 3 of 7 places -- content/docs/plugins/packages.mdx and the changeset -- because the reading below falsifies their `build` half too, not only `os validate`. Measured, first-hand, beyond what the review covered: - `os build` is `compile.ts` (build.ts is `class Build extends Compile`), and at compile.ts:347 it runs the SAME `ObjectStackDefinitionSchema.safeParse` validate runs, exiting 1 at :352/:368. So both doors refuse a malformed `packages[]`. - compile.ts contains NO `manifest.id` requirement, and its own comment at :967-971 says the structural advisories are absent because "os compile never computes them at all (this file has no 'No objects defined' / 'may not do much' string, in any face)". => `os build` is SILENT on a stack with no `manifest.id`, so "build refuses it" was overstated exactly as `os validate` was. - validate.ts text face re-read at :760-774: `this.exit(1)` fires only inside `if (flags.strict)`, confirming the `--json` ternary at :740 is not the only exit and both read one `warnings` list. - lower-callables.ts:315-319 passes a non-`{ manifest: object }` entry through untouched, so the schema probe's verdict transfers to what the CLI actually parses. - `publish` is not a door this card measured; the claim is dropped rather than restated. Zero non-comment changed lines in both source files (classifier lit on a planted code line). No behaviour change. Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx Co-authored-by: Claude <noreply@anthropic.com>
1 parent a3f52cf commit e895dda

4 files changed

Lines changed: 43 additions & 17 deletions

File tree

‎.changeset/15292-dev-plugin-malformed-stack-posture.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"@objectstack/plugin-dev": patch
33
---
44

5-
`DevPlugin`'s boot posture on a malformed stack is now written down: dev boot tolerates and reports; the production doors refuse (#15292).
5+
`DevPlugin`'s boot posture on a malformed stack is now written down: dev boot tolerates and reports; refusing belongs to the production doors, which are not uniform about it (#15292).
66

77
Clause-②: no
88

@@ -11,5 +11,5 @@ No behaviour changes. `DevPlugin` already degraded a stack the platform would re
1111
- **The two branches are not one defect handled two ways.** `new AppPlugin(stack)` reads `manifest.id` / `manifest.name` and nothing else, so a malformed `packages[]` passes the constructor untouched and is refused one branch later: `AppPlugin.init()`'s LAST statement hands the bundle to the `manifest` service, whose `register()` calls `resolveArtifactPackageOrder` unguarded, and `DevPlugin`'s child-`init()` loop degrades that refusal to an `error` line. The lazy `collections` getter is not on that path at all — it is not read during `init()`, and its first read is in `AppPlugin.start()`, where it reaches the same refusal on the same bytes. Both in-file comments that named the constructor as the stack's parse door (*"a malformed stack throws HERE"*, and §3b's *"twenty lines above, `new AppPlugin(...)` parses the SAME object"*) overclaim for that reason, and both are corrected in this PR.
1212
- **The two malformations are exact complements, measured with a lit control.** An app payload with no `manifest.id` / `manifest.name` throws from the constructor (a bare `Error`, no ADR-0112 `code` / `status`) and is invisible to the package-list parse; a `packages[]` entry that is not a package entry (ADR-0130 D4) is invisible to the constructor and refused by the parse as `INVALID_ARTIFACT_PACKAGE_ENTRY` / `422`. A stack carrying neither is silent on both. So a clean boot past one branch is no evidence about the other — which is why the division is now documented rather than left to be re-derived.
1313
- **Tolerating is not hiding.** The posture's second half is that a boot which skipped something is never byte-identical to a healthy one: a silent degrade lets an author, or a coding agent, read "it started" as "I wrote it correctly".
14-
- **The `os validate` door is not uniform, and the docs page now says so.** A malformed `packages[]` fails `ObjectStackDefinitionSchema` and exits 1; an app payload with no `manifest.id` parses green and is reported as the advisory *"Missing manifest.id — required for deployment"*, which only fails under `--strict` (`packages/cli/src/commands/validate.ts` — the advisory is pushed at the structural-warnings block and the exit is `flags.strict && warnings.length > 0 ? 1 : 0`). The page's flat "`os validate` … refuses it" overstated the second half.
14+
- **The production doors are not uniform, and every carrier now says so.** A malformed `packages[]` fails `ObjectStackDefinitionSchema` — `packages: z.array(ArtifactPackageSchema)`, the SAME entry schema the runtime parse uses — and both `os validate` and `os build` parse the lowered stack against it and exit 1 (`validate.ts` step 2; `compile.ts` step 3). `lowerCallables` passes a non-`{ manifest: object }` entry through untouched, so the verdict transfers to what the CLI actually parses. An app payload with no `manifest.id` parses green at BOTH: `os validate` reports it only as the structural advisory *"Missing manifest.id — required for deployment"*, which fails only under `--strict` (both exit faces read one `warnings` list — the `--json` ternary and the text face's `if (flags.strict)` block), and `os compile` "never computes them at all" in its own words, so `os build` is silent on it. The flat "`os validate` / build / publish refuse" overstated BOTH doors for that half, and `publish` is simply not a door this card measured, so it is no longer claimed.
1515
- **What ships**: the `DevPlugin` docblock (which reaches the published `dist/*.d.ts`), the two in-file comments named above, and `content/docs/plugins/packages.mdx`, plus a test pinning the posture, its division and the init-time path the refusal actually takes. The wording of the malformed-metadata diagnostic itself is deliberately not pinned — that text is a sibling change.

‎content/docs/plugins/packages.mdx‎

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -368,12 +368,15 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
368368

369369
#### Malformed metadata: dev boot tolerates and reports
370370

371-
**`os dev` keeps booting on a stack the platform would reject; build and publish
372-
refuse it, and `os validate` reports it — as a failure for anything the stack schema
373-
refuses, and as an advisory that `--strict` promotes to one for the rest.** The dev
374-
server skips only the part it could not read, boots the rest, and prints an `error`
375-
line naming what was malformed and what was skipped — the shape every mainstream dev
376-
server takes, where the error overlay stays on screen while the server keeps serving.
371+
**`os dev` keeps booting on a stack the platform would reject; refusing belongs to
372+
the doors an artifact leaves your machine through — and those doors are not uniform
373+
about it.** `os validate` and `os build` both parse your stack against the same
374+
protocol schema and exit 1 when it fails; what that schema accepts but a deployment
375+
still needs comes back from `os validate` as an advisory instead, which `--strict`
376+
promotes to a failure. The dev server skips only the part it could not read, boots the
377+
rest, and prints an `error` line naming what was malformed and what was skipped — the
378+
shape every mainstream dev server takes, where the error overlay stays on screen while
379+
the server keeps serving.
377380

378381
This is deliberate, and it is one posture rather than two. Metadata that is incomplete
379382
halfway through an edit is the **normal** state of a project you are actively working
@@ -398,11 +401,12 @@ metadata it names.
398401
`manifest` service and its package list is parsed; it reports as
399402
`INVALID_ARTIFACT_PACKAGE_ENTRY` (422) on the `error` line naming the app plugin.
400403

401-
The two differ at the `os validate` door as well. The malformed `packages[]` fails
402-
the stack schema, so `os validate` exits 1 on it. The missing `manifest.id` parses
403-
green and comes back as the advisory *"Missing manifest.id — required for
404-
deployment"*, which exits 0 unless you pass `--strict`. Use `os validate --strict`
405-
if you want that half to fail too.
404+
The two differ at the production doors as well. The malformed `packages[]` fails
405+
the protocol schema, so both `os validate` and `os build` exit 1 on it. The missing
406+
`manifest.id` parses green for both: `os validate` reports it as the advisory
407+
*"Missing manifest.id — required for deployment"*, which exits 0 unless you pass
408+
`--strict`, and `os build` does not report it at all. So run `os validate --strict`
409+
if you want that half to fail too — a green `os build` is not evidence about it.
406410
</Callout>
407411

408412
### @objectstack/plugin-approvals

‎packages/plugins/plugin-dev/src/dev-plugin-malformed-stack-posture.test.ts‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22
//
33
// #15292 — the documented boot posture: dev boot TOLERATES a malformed stack
4-
// and REPORTS it; `os validate` / build / publish are the doors that refuse.
4+
// and REPORTS it; refusing belongs to the production doors — which are NOT
5+
// uniform about it. `os validate` and `os build` both parse the stack against
6+
// `ObjectStackDefinitionSchema` and exit 1 on a malformed `packages[]`; an app
7+
// payload with no `manifest.id` parses green for both, surfacing only as
8+
// `os validate`'s structural advisory, which exits 0 unless `--strict`.
59
//
610
// What this file pins is the posture and its DIVISION, not any diagnostic's
711
// wording. The wording of the malformed-metadata diagnostic is the subject of
@@ -123,8 +127,16 @@ describe('#15292 — DevPlugin tolerates a malformed stack and reports it', () =
123127
seedAdminUser: false,
124128
});
125129

126-
// TOLERATES — the whole posture in one assertion. `os validate`, build and
127-
// publish are the doors that refuse this same stack.
130+
// TOLERATES — the whole posture in one assertion.
131+
//
132+
// ⛔ And mind WHICH HALF this fixture is. `MISSING_IDENTITY` carries no
133+
// `manifest.id`, and that is the half the production doors do NOT refuse:
134+
// `ObjectStackDefinitionSchema` accepts a stack with no `manifest` block,
135+
// so plain `os validate` exits 0 on it and reports only the structural
136+
// advisory "Missing manifest.id — required for deployment" (`--strict`
137+
// promotes it), while `os build` never computes that advisory at all. The
138+
// half those doors really do refuse is `MALFORMED_PACKAGES` — see the last
139+
// case in this file.
128140
await expect(plugin.init(ctx)).resolves.toBeUndefined();
129141

130142
// REPORTS — the transcript of a degraded boot is never the transcript of a

‎packages/plugins/plugin-dev/src/dev-plugin.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,17 @@ function reportOptionalLoadFailure(ctx: PluginContext, err: unknown, spec: Optio
383383
*
384384
* ## Malformed metadata: dev boot tolerates and reports (#15292)
385385
*
386-
* **Dev boot tolerates and reports; `os validate` / build / publish refuse.**
386+
* **Dev boot tolerates and reports; refusing belongs to the PRODUCTION
387+
* doors — and they are not uniform about it.** Measured: `os validate` and
388+
* `os build` both parse the stack against `ObjectStackDefinitionSchema`
389+
* (`packages: z.array(ArtifactPackageSchema)`) and exit 1 when it fails, so
390+
* both refuse row 2 of the table below. Row 1 parses GREEN at both: a stack
391+
* carrying no `manifest.id` is reported only as `os validate`'s structural
392+
* advisory "Missing manifest.id — required for deployment", which exits 0
393+
* unless `--strict`, and `os build` never computes that advisory at all.
394+
* ⛔ So do not write "the production doors refuse it" flat — that is a claim
395+
* over BOTH rows, and it is false of row 1.
396+
*
387397
* A stack the platform will reject does not stop `os dev`: `init()` keeps
388398
* booting, skips only the part it could not read, and says so at `error`.
389399
* This is the shape every mainstream dev server takes — the error overlay

0 commit comments

Comments
 (0)