Skip to content

finding(spec): the option-B readers disagree with @objectstack/core about whether a non-array packages is a refusal #15293

Description

@hotlong

Related #14122 · surfaced by the isolated contract review of #15282 (PR comment 5538108541), which flagged it as a program-level split rather than that card's fault. Filed unassigned.

The split

Two guards, same input, opposite answers:

Reader Guard packages: {}
@objectstack/core's artifact-collection resolver (#15005 / PR #15261) if (!Array.isArray(artifact.packages)) return artifact; not a refusal — falls through to the top level
@objectstack/plugin-dev dev-i18n.ts:108 (#15232 / PR #15282) packages === undefined || packages === null → return, else call the resolver refusal (ADR-0112 envelope out of resolveArtifactPackageOrder)
@objectstack/plugin-security declaredPermissionSets (#15007 / PR #15226) same as plugin-dev refusal

So a stack carrying packages: {} boots one way through the runtime reader and is refused through the other two. Nothing today emits that shape, which is exactly why it is worth pinning before something does: the program's whole thesis is that readers must not disagree about what an artifact CONTAINS.

Which is right is a packages/spec question, not a reader's

Both guards are defensible in isolation:

  • Refuse is the reader cards' stated rule — resolveArtifactPackageOrder is the one traversal and the one gate, so a reader that answers questions about an artifact the loader would refuse is answering about nothing. Under this reading !Array.isArray silently accepts a malformed artifact.
  • Fall through keeps the resolver total: it returns the caller's own object by identity for anything that is not a package list, and the refusal happens at the load boundary that owns it. Under this reading the other two readers refuse in a place with no diagnostic budget.

The tie-break is whether a non-array packages is malformed or absent, and that belongs beside AssembledPackageBodySchema / ArtifactPackageSchema, not in three readers.

Acceptance

  • One answer, stated once, in packages/spec or in resolveArtifactPackageOrder's own documented contract.
  • All three readers above spell the guard the same way afterwards, or the divergence is recorded as deliberate with the reason at each site.
  • A test pinning packages: {} (and packages: 0, packages: 'x') in whichever direction is chosen.

⚠️ Sequencing: this is cheapest after the reader half lands, as part of the convergence pass — three private walks collapse then anyway, and re-spelling a guard twice is worse than once. Do not fold it into any open reader card.

Sibling context: #15005, #15006, #15007, #15229, #15232. Emitter half: #14512.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions