Skip to content

Commit 68398a0

Browse files
committed
fix(lint,spec): close the round-2 pin gap and correct false-at-head wording
REWORK round 2 for #20206 (at-tier record 5856823202, items 1-2): 1. `validate-object-references.test.ts`'s non-array refusal test pinned only [null, 42, 'core']; add `{}` and a keyed object so all three validators pin the identical set `packagesOf` itself does. 2. Text corrected to match the head, wording only: (a) "four call sites" / "four copies" -> "five ... three files" in the `packagesOf` docblock and the `object-graph.test.ts` comment, now that `validate-mapping-target-fields.ts` is a fifth site. (b) every sentence claiming lint reads `null` "the way `resolveArtifactPackageOrder` does" or raises "the SAME code ... for the identical defect" is qualified for `null` specifically: on `main`, core still reads `null` as absent and names it via `typeof`; that only changes once #19926 (PR #20228) lands. Fixed in the docblock, the inline naming comment, the lint changeset and the ledger-row comment in `error-code-ledger.zod.ts`. (c) the lint changeset's door-reachability claim corrected: only `os lint` reaches `packagesOf`'s refusal (exit 1, message on stdout via `printError`, `code` under `--json`); `os validate` and `os build` already refuse a malformed `packages` earlier, at `ObjectStackDefinitionSchema.safeParse`, before these rules run (re-read `validate.ts:293`, `compile.ts:356`, `lint.ts:673/1140`, `format.ts:369` directly to confirm). No `packagesOf` semantics changed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
1 parent a4b05d6 commit 68398a0

5 files changed

Lines changed: 41 additions & 29 deletions

File tree

‎.changeset/20206-lint-packages-non-array-refused.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
"@objectstack/lint": minor
33
---
44

5-
`packages/lint`'s five `stack.packages` readers (four named by #20206, plus one added by #20208 after that card's site census) now refuse a PRESENT non-array `packages` — `{}`, `0`, `'x'`, a keyed object, and (as of this round) `null` too — the same way `@objectstack/core`'s `resolveArtifactPackageOrder` already does, instead of silently reading it as "no packages" (#20206, ruling A on #15293 comment 5634034754; the `null` leg is ruling A on #19926, comment 5805260775: `null` is malformed, everywhere).
5+
`packages/lint`'s five `stack.packages` readers (four named by #20206, plus one added by #20208 after that card's site census) now refuse a PRESENT non-array `packages` — `{}`, `0`, `'x'`, a keyed object, and (as of this round) `null` too — instead of silently reading it as "no packages" (#20206, ruling A on #15293 comment 5634034754; the `null` leg is ruling A on #19926, comment 5805260775: `null` is malformed, everywhere). For every shape other than `null`, this is the same way `@objectstack/core`'s `resolveArtifactPackageOrder` already refuses it, with the same registered code; for `null`, `resolveArtifactPackageOrder` still reads it as absent on `main`, and the two readers align only once #19926 (PR #20228) lands.
66

77
Clause-②: no (narrowing)
88

99
<!-- adr-0087: not-required (no-migration-prescription) an already-malformed `packages` value is refused rather than converted; no key, export or stored value moves, and nothing in this repo emits the shape today -->
1010

11-
- **What changes**: `validateObjectReferences`, `validateTranslationReferences` and `validateMappingTargetFields` (the three public `@objectstack/lint` functions these readers sit behind) now throw an `INVALID_ARTIFACT_PACKAGES` error (ADR-0112, `status: 422`) instead of returning findings, when the stack they are handed carries a `packages` key that is present but not an array — `null` included. `os validate` / `os lint` / `os build` surface it as a refusal on stderr (and in `error`/`code` under `--json`) instead of reporting the stack as clean.
11+
- **What changes**: `validateObjectReferences`, `validateTranslationReferences` and `validateMappingTargetFields` (the three public `@objectstack/lint` functions these readers sit behind) now throw an `INVALID_ARTIFACT_PACKAGES` error (ADR-0112, `status: 422`) instead of returning findings, when the stack they are handed carries a `packages` key that is present but not an array — `null` included. Only `os lint` reaches this refusal — exit 1, the message on stdout (`printError`), `code` under `--json`; `os validate` and `os build` already refuse a malformed `packages` earlier, at `ObjectStackDefinitionSchema.safeParse`, before these rules ever run.
1212
- **What does not change**: an absent `packages` (the key omitted, or explicitly `undefined`) is still read as "no packages" — unchanged. A well-formed `packages[]` array is read exactly as before, junk entries dropped exactly as before.
1313
- **Fix**: write `packages` as an array of `{ manifest: … }` entries, or omit the key entirely for a single-package stack.

‎packages/lint/src/object-graph.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,8 @@ describe('object-graph — packagesOf (#20206, ruling A on #15293 `5634034754`)'
297297
// `packages` is declared `z.array(ArtifactPackageSchema).optional()` — array
298298
// or absent, never map-or-array like `objects`/`sections`/`tabs`. A PRESENT
299299
// non-array `packages` ({}, 0, 'x', a keyed object) is malformed, not
300-
// absent, and every reader refuses it. This is the ONE reader the four
301-
// `packages/lint` call sites now share, replacing four private copies of
300+
// absent, and every reader refuses it. This is the ONE reader the five
301+
// `packages/lint` call sites now share, replacing five private copies of
302302
// `recordsOf(stack.packages)`.
303303

304304
it('CONTROL — an array is read exactly as `recordsOf` read it: iterated, junk dropped', () => {

‎packages/lint/src/object-graph.ts‎

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,8 @@ export type StackPackagesError = Error & { code: string; status: number };
225225

226226
/**
227227
* Every entry of `stack.packages` — the release artifact's package list
228-
* (ADR-0130 D4) — read the way {@link resolveArtifactPackageOrder}
229-
* (`@objectstack/core`) reads it, ⛔ NOT the way {@link recordsOf} reads
230-
* `objects` / `sections` / `tabs`.
228+
* (ADR-0130 D4) — read on `packages/lint`'s own terms, ⛔ NOT the way
229+
* {@link recordsOf} reads `objects` / `sections` / `tabs`.
231230
*
232231
* `packages` is declared `z.array(ArtifactPackageSchema).optional()`
233232
* (`ObjectStackDefinitionSchema`, `@objectstack/spec`) — array-or-absent,
@@ -242,22 +241,27 @@ export type StackPackagesError = Error & { code: string; status: number };
242241
* - **Absent** (`undefined` ONLY) → `[]`. A single-package artifact
243242
* contributes nothing here — this answers "what does `packages[]` add",
244243
* never "what does this stack provide". `null` is malformed, per ruling
245-
* `5805260775` on #19926.
244+
* `5805260775` on #19926 — for `null` specifically, that disagrees with
245+
* {@link resolveArtifactPackageOrder} (`@objectstack/core`), which on
246+
* `main` still reads `null` as absent; the two readers align once #19926
247+
* (PR #20228) lands.
246248
* - **An array** → iterated, non-record members dropped — unchanged from
247-
* what every one of these four call sites did through `recordsOf` before
249+
* what every one of these five call sites did through `recordsOf` before
248250
* this function existed.
249251
* - **Anything else present, `null` included** → refused, once, here —
250-
* replacing four copies of the same read across
251-
* `validate-object-references.ts` and `validate-translation-references.ts`
252-
* (#20206).
252+
* replacing five copies of the same read across
253+
* `validate-object-references.ts`, `validate-translation-references.ts`
254+
* and `validate-mapping-target-fields.ts` (#20206).
253255
*
254256
* ⛔ Do not fold this into `recordsOf` itself (#20206's card): that reader
255257
* stays the shared map-or-array reader its other callers need.
256258
*
257259
* @throws A {@link StackPackagesError} — `code: 'INVALID_ARTIFACT_PACKAGES'`,
258-
* `status: 422`, the SAME registered code `resolveArtifactPackageOrder`
259-
* raises for the identical defect on the assembled artifact — never a new
260-
* one.
260+
* `status: 422` — for a present non-array `packages` OTHER than `null`,
261+
* the SAME registered code {@link resolveArtifactPackageOrder} already
262+
* raises for the identical defect on the assembled artifact; for `null`,
263+
* only once #19926 (PR #20228) lands does core raise it too. Never a new
264+
* code, either way.
261265
*/
262266
export function packagesOf(stack: unknown): AnyRec[] {
263267
const declared = (stack as { packages?: unknown } | null | undefined)?.packages;
@@ -270,8 +274,9 @@ export function packagesOf(stack: unknown): AnyRec[] {
270274
'A stack\'s `packages` must be an array of package entries (ADR-0130 D4, '
271275
+ '`ArtifactPackageSchema`), but this stack carries `packages` of type '
272276
// `typeof null` is `'object'`, which would name a `{}` the author never
273-
// wrote; `null` is named as itself (matching `resolveArtifactPackageOrder`
274-
// in `@objectstack/core`).
277+
// wrote; `null` is named as itself here — the naming #19926 (PR #20228)
278+
// gives `resolveArtifactPackageOrder` once it lands, adopted early by
279+
// this reader.
275280
+ `${declared === null ? 'null' : typeof declared}. Omit the key entirely for a `
276281
+ 'single-package stack — `manifest` is retained, not replaced.',
277282
) as StackPackagesError;

‎packages/lint/src/validate-object-references.test.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,11 @@ describe('validateObjectReferences — artifact packages[] as resolution context
308308
// exactly the fall-through the ruling closes: PRESENT but not an array is
309309
// malformed, not absent, and every reader refuses it. `null` joins this set
310310
// in rework round 1 (ruling A on #19926, `5805260775`): it is present, not
311-
// absent, so it is no longer a control.
311+
// absent, so it is no longer a control. `{}` and a keyed object join in
312+
// rework round 2, so this validator pins the same set `packagesOf` and the
313+
// other two validators do.
312314
it('refuses a PRESENT non-array `packages` instead of silently ignoring it', () => {
313-
for (const packages of [null, 42, 'core']) {
315+
for (const packages of [null, 42, 'core', {}, { a: { manifest: {} } }]) {
314316
expect(() => validateObjectReferences(perPackageStack(ORDERS_BODY, packages))).toThrow(
315317
expect.objectContaining({ code: 'INVALID_ARTIFACT_PACKAGES', status: 422 }),
316318
);

‎packages/spec/src/api/error-code-ledger.zod.ts‎

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,16 +1359,21 @@ export const ERROR_CODE_LEDGER = {
13591359
],
13601360
'@objectstack/lint': [
13611361
// [#20206] `packagesOf` (`object-graph.ts`) refuses a present non-array
1362-
// `stack.packages` ({}, 0, 'x', a keyed object, `null`) with the SAME
1363-
// registered code `@objectstack/core`'s `resolveArtifactPackageOrder`
1364-
// raises for the identical defect on an assembled artifact (ruling A on
1365-
// #15293 `5634034754`; the `null` leg is ruling A on #19926 `5805260775`)
1366-
// — deliberately reused, never minted, so an author sees one code
1367-
// regardless of which reader catches the malformed shape first. `door:
1368-
// 'none'`, the #16449 reading: `packages/lint`'s rules are pure
1369-
// `(stack) => Finding[]` functions called from `os validate` / `os lint`
1370-
// / `os build` (`packages/cli`), never through an HTTP boundary — the
1371-
// same posture `@objectstack/spec`'s own `STACK_*` rows above record.
1362+
// `stack.packages` ({}, 0, 'x', a keyed object, `null`) — for every shape
1363+
// OTHER than `null`, with the SAME registered code `@objectstack/core`'s
1364+
// `resolveArtifactPackageOrder` already raises for the identical defect
1365+
// on an assembled artifact (ruling A on #15293 `5634034754`). For `null`
1366+
// (ruling A on #19926 `5805260775`), core still reads it as absent on
1367+
// `main`; core raises this code for `null` too only once #19926 (PR
1368+
// #20228) lands — deliberately reused either way, never minted, so an
1369+
// author sees one code regardless of which reader catches the malformed
1370+
// shape first. `door: 'none'`, the #16449 reading: `packages/lint`'s
1371+
// rules are pure `(stack) => Finding[]` functions, reachable only from
1372+
// `os lint` (`packages/cli`) — `os validate` / `os build` refuse a
1373+
// malformed `packages` earlier, at `ObjectStackDefinitionSchema.safeParse`,
1374+
// before these rules ever run — never through an HTTP boundary either
1375+
// way, the same posture `@objectstack/spec`'s own `STACK_*` rows above
1376+
// record.
13721377
'INVALID_ARTIFACT_PACKAGES',
13731378
],
13741379
} as const satisfies Record<string, readonly string[]>;

0 commit comments

Comments
 (0)