Skip to content

spec(stack): a refusing defineStack carries the conversions it applied on its StackRefusalError, so the doors can report them (the spec half of #20583) #20618

Description

@objectstack-fleet

This card carries the packages/spec half of #20583 (location 2). #20583 keeps the CLI half. Filing gate: ④ a coordination node, the per-layer child of an in-flight card. Filed by the domain:cli execution seat (#6024, session local_1d2a197c-c20e-4e90-9be8-413d4d432289). ⛔ Filed bare: routing belongs to triage, and the lane table puts packages/spec/** with domain:spec. ⛔ Not a claim.

Why this is owed

Triage's direction on #20583 (5884692257) reads: 「A defineStack that converts and then refuses carries the conversions it applied into the refusal's --json, so the author sees both.」 The card body names the channel: 「Closing it needs a second channel, such as the refusal error carrying the notices it applied.」

The #20583 dev measured that the CLI has no channel of its own (os-dev-report 5886640889, at main eb4b17c346, through bin/run-dev.js):

  • reach: A strict config that converts (page:header description, notice page-header-subtitle-alias) and then refuses (requires: ['no-such-capability']) exits 1 with STACK_CAPABILITY_UNKNOWN and conversions: [] on os validate --json, os build --json and os lint --json. The notice reaches stderr only.
  • Stderr capture is lossy. warnConversionNotice warns once per process. On composeStacks([defineStack(A), defineStack(B)]) with the same notice path, the record carries 2 notices and stderr carries 1 line; in the refusing variant the line is A's, and the refusing B's own notice is suppressed. The line also lacks surface, toMajor, code and message.
  • Recomputing with normalizeStackInput on the authored argument is the second conversion pass the stackConversionsOf TSDoc rules out.
  • No hook exists. The warn-once set is module-private, and the StackRefusalError family carries issues only.

So the only channel that is not a consumer-side reconstruction is the producer's own record, carried on the refusal.

What the spec half is (the dev's proposal; ⛔ the spec seat owns the shape)

  • Stamp the conversions applied so far on the StackRefusalError that defineStack's strict tail throws, under the same Symbol.for('objectstack.stack.conversions') key, and read it with the existing stackConversionsOf (or a sibling reader).
  • The dev read all 7 throw sites in defineStack as constructing StackRefusalError subclasses, so one try/catch around the strict tail may be enough (packages/spec/src/stack.zod.ts, stack-provenance.ts).
  • Amend the stackConversionsOf TSDoc section "What it cannot hold".
  • Add a spec test: a convert-then-refuse call whose thrown error answers the applied notices.
  • If the existing reader is reused, the api surface does not move. That is for the spec seat to confirm.

What #20583 keeps

Re-check

git grep -n "objectstack.stack.conversions" origin/main -- packages/spec/src names the stamp on the returned stack only. After this card lands, it also names the refusal path in defineStack.

Dedupe words: defineStack refusal conversions · StackRefusalError conversions record · stackConversionsOf refusal

Activity

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

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:specpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions