Skip to content

feat(cli)!: os validate and os build refuse a picklistExtensions entry whose extend names no declared picklist - #21049

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20825-picklist-extend-refusal
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20825-picklist-extend-refusal

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20825
Clause-②: no (narrowing)

This PR is the item-2 sibling that triage folded into the card (answer 5923151807): a picklistExtensions[].extend that names no picklist is refused by the same judge on the same two doors as a field's dangling picklist. It is one more walk, over a second collection, in the file PR #21003 landed (b84b240b2a). Nothing else is on the card: the generator and scaffold half went to #21018.

What changes

os validate and os build refuse an extension whose extend names no picklist the stack declares. PicklistExtensionSchema checks the spelling of extend and nothing else, so extend: 'industy' beside picklists: [{ name: 'industry', ... }] parsed. os validate exited 0 and os build wrote the artifact carrying the extension. Now both exit 1 with picklist-reference-unknown; the finding names the extension (where), the list it names (message), the lists the stack does declare, and the path (picklistExtensions[N].extend, prefixed with packages[I].manifest. for a package body).

  • Same walk as the field reference, one collection added. A one-package stack is judged on its top-level picklistExtensions. A packages[] stack is judged on each body's own picklistExtensions, and a top-level picklistExtensions beside packages[] is not judged, because the load path does not register from it (pinned). An extend resolves against every picklist the stack declares, including one a sibling package in the same artifact owns.
  • Same outside-dependency arm. When the package declaring the extension lists a manifest.dependencies entry the stack does not carry, an unresolved extend is an info notice (picklist-reference-unverified) in warnings and on the console, naming the extension, the list and the dependencies. It never gates, not even under --strict. That package's own dependencies decide, not a sibling's. The coarse trigger itself (any outside id downgrades every unresolved reference in that package) is unchanged: it stays the recorded line triage kept.

Rule id: reuse picklist-reference-unknown and picklist-reference-unverified, no new sibling id. What is judged is one fact, a name that resolves to no picklist the stack declares, with one cure class (declare the list or correct the name) and the same two verdicts. The finding's where, path and message already say which key carried it, and the sentences that differ (extend: '...' instead of picklist: '...', "the options it adds have no list to join", a hint that offers removing the entry instead of inline options) are chosen per site inside the one judge. A second id would split one verdict in two for everything keyed on the rule: the --json errors[].rule consumers, the door pins, and the already-pending changeset that names picklist-reference-unknown. Nothing in the repo registers these ids in a ledger.

The two doors' refusal heading no longer says "A field". validate.ts (step 2d) and compile.ts (step 3a-bis) printed "A field names a picklist this stack does not declare (N references)" above the refusals, which would be false for an extend refusal. It now reads "A picklist reference names a picklist this stack does not declare (N references)". That literal and the two step comments are the only edits in those files; the call and the notice printing are unchanged, so validate-build-gate-parity.test.ts asked for no registration (no new identifier is called from either command; the whole cli unit tier is green).

packages/spec/**, packages/lint/**, the generator and scaffold, and collectMetadataStats are untouched.

Before / after (real os processes on scratch fixtures, run-dev + tsx)

Stack: picklists: [{ name: 'industry', ... }], a field picklist: 'industry', and one picklistExtensions entry. Before is origin/main at 2f2fa11d75; after is 69f413fb76.

reading before after
os validate, extend: 'industy' exit 0, "Validation passed" exit 1, picklist-reference-unknown naming picklist extension "industy" and 'industy', at picklistExtensions[0].extend
os build, same stack exit 0, artifact written carrying industy exit 1, no artifact
os validate / os build, extend: 'industry' (the control) exit 0 / 0 exit 0 / 0, artifact written

After text from os validate:

  ✗ A picklist reference names a picklist this stack does not declare (1 reference)
  • picklist extension "industy": `extend: 'industy'` names no picklist this stack declares (declared: 'industry'), so the options it adds have no list to join.
      Correct `extend` to the picklist this entry adds options to, or declare that list — `picklists: [{ name: 'industy', label, options }]` in the package that owns it, or a `*.picklist.ts` file the stack imports. Or remove the entry: it adds options to nothing.
      rule: picklist-reference-unknown  at picklistExtensions[0].extend

Tests

  • packages/cli/src/utils/picklist-references.test.ts (unit, 19 now, 10 new): the control (a resolving extend), the refusal naming extension, list, declared lists and path, a stack that declares no picklist, a field and an extension judged in one pass without hiding each other, the outside-dependency notice, sibling-package resolution, the declaring package's own dependencies, and a top-level picklistExtensions beside packages[] not judged.
  • packages/cli/test/picklist-reference-doors.test.ts (integration: it spawns the CLI, so the PR tiers run it; 11 now, 5 new): a resolving extend validates and builds with the artifact carrying it as authored (the control); a dangling extend is refused by os validate (JSON and text face) and by os build, which writes no artifact.
  • Ablations on the committed fix, through scripts/ablation-replace.mjs under the verify lock. Each mutation landed (anchor 1 to 0, blob changed) and each restore was proven (blob equal to HEAD, git diff HEAD empty). The suites read src (vitest imports the module; bin/run-dev.js runs src through tsx), so there is no build leg.
    • New walk disabled (if (false && Array.isArray(extensions))): unit 7 red and 12 green; doors 3 red (JSON, text, os build) and 8 green (both control pins and the six field pins).
    • Package branch disabled (if (false && parsed.packages !== undefined)): the new top-level-not-judged pin goes red, along with the two per-package pins that depend on the branch (5 red, 14 green).
  • At 69f413fb76: os-verify-lock runs of the door file 11/11 and @objectstack/cli typecheck exit 0 (including check:test-typecheck); the unit pin 19/19; pnpm lint exit 0 (full repo, eslint . --no-inline-config). The whole @objectstack/cli unit tier, 240 files / 3411 tests, was green at ec1d965497, one commit earlier; the delta to the head is one comment sentence in picklist-references.ts.
  • Gates: dispatch-gates --commands derived 63 families at 69f413fb76; all 63 were run, each exit code recorded before any pipe, and --ran reconciles 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN. Four of them (check:i18n, check:i18n-coverage, check:i18n-walk-parity, check:dual-build-cjs-loads) first exited 3 (prerequisite not met, nothing measured: no built CLI or workspace dist); after building their prerequisites they were re-run and exited 0 on a final sweep.

Acceptance notes


Generated by Claude Code

claude added 2 commits October 1, 2026 02:27
…y whose extend names no declared picklist

The picklist-reference judge walked a field's `picklist` only. A
`picklistExtensions[].extend` is the same reference one key over, and
`PicklistExtensionSchema` checks only its spelling, so `extend: 'industy'`
beside `picklists: [{ name: 'industry', ... }]` parsed, validated and built.

The judge now walks the second collection on the load path's reading (the
top-level entries of a one-package stack, each `packages[]` body's own
otherwise) and gives an unresolved `extend` the same two verdicts as a
field: refused with `picklist-reference-unknown`, or an info notice
`picklist-reference-unverified` when the declaring package depends on a
package outside the stack. Both doors' refusal heading no longer says
"A field".

Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB
Co-authored-by: Claude <noreply@anthropic.com>
…ut claiming a runtime merge

The judge's header said the options of an extension with a dangling `extend`
were added to a list nobody declared. No runtime reader merges extensions yet,
so nothing was added; the artifact shipped an extension of an undeclared list.

Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/m label Oct 1, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 6 documentable anchor(s).

20 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json d67b94280a2f2b2f1d4a3497142511ede21b8d42.

⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: os validate (command, 53 pages)
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 25 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d67b94280a2f2b2f1d4a3497142511ede21b8d42 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 13e745e72a4aaeec5c8d459d391c6ca6b6badd7a — the merge of head 69f413fb76e2dca9f395ba0116aef4d85d6182c8 into base d67b94280a2f2b2f1d4a3497142511ede21b8d42, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 13e745e72a4aaeec5c8d459d391c6ca6b6badd7a && git checkout 13e745e72a4aaeec5c8d459d391c6ca6b6badd7a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d67b94280a2f2b2f1d4a3497142511ede21b8d42 69f413fb76e2dca9f395ba0116aef4d85d6182c8 && git checkout -B drift-repro d67b94280a2f2b2f1d4a3497142511ede21b8d42 && git merge --no-ff 69f413fb76e2dca9f395ba0116aef4d85d6182c8

node scripts/docs-audit/affected-docs.mjs --json d67b94280a2f2b2f1d4a3497142511ede21b8d42

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d67b94280a2f2b2f1d4a3497142511ede21b8d42 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 69f413fb76e2dca9f395ba0116aef4d85d6182c8
Local-runs: none

Inputs read: card #20825 (body; triage 5908852656; retriage answer 5923151807; the seat's claim 5923350718; the landing note 5923331640; os-dev-report 5924182672); the predecessor PR #21003 (body, landed b84b240b2a) and its record 5922950314; PR #21049 (body, the 6-file list, two commits ec1d965497 and 69f413fb76) and the net diff git diff 2f2fa11d75..refs/review/pr-21049 (+411 / −58); the spec at origin/main (packages/spec/src/data/picklist.zod.ts, stack.zod.ts), the load path (packages/core/src/artifact-packages.ts, packages/runtime/src/artifact-collections.ts, packages/metadata/src/plugin.ts, packages/cli/src/utils/view-container-names.ts), the parity roster, both changeset gates, ADR-0087's launch-window section, and the head's check-runs. Source context was read by sha only (git show, git grep on origin/main and refs/review/pr-21049), never from the working tree.

① Derived judgments

(a) The new walk over picklistExtensions in packages/cli/src/utils/picklist-references.ts.

  • It follows the field walk's load path by construction, not by a parallel branch. bodiesOf is untouched by this diff: it still branches on parsed.packages !== undefined (the resolver's own test, the one view-container-names.ts and resolveArtifactPackageOrder make) and yields the top-level body for a one-package stack or each packages[i].manifest body through artifactPackages. The new code sits inside referencesOf, which runs once per body, so the extension walk takes exactly the bodies the field walk takes and cannot take others. Hence: a one-package stack's top-level picklistExtensions are judged, each packages[] body's own are judged, and a top-level picklistExtensions beside packages[] is not (pinned negatively in the unit file).
  • Is that the loader's reading of a top-level collection beside packages[]? It is the reading every author-time judge in this file family makes, and it is the one the field half shipped under record 5922950314: resolveArtifactPackageOrder returns only the packages[i].manifest bodies when packages is present, and the metadata door registers each body under its own id. Measured bound, stated here rather than hidden: the metadata door also runs a residual sweep over the top level for registry-kind items no body claims (registered under the artifact's manifest id, with a logged warning that the artifact's two halves disagree), and the runtime's resolveArtifactCollections folds a top-level array together with the bodies'. Neither path reaches picklistExtensions today: the loader registers neither picklists nor picklistExtensions (PENDING picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519, the waiver in scripts/check-stack-collection-maps.mjs), and PLURAL_TO_SINGULAR records that extensions merge into their target rather than register as a type. In a composeStacks(…, { manifest: 'preserve' }) artifact the top level is the additive flattening of the same bodies, so what the per-body walk judges is the same set of entries. The negative pin therefore encodes the precedent's reading, identical for a field and an extension, which is what the fold asked for. The residual-sweep edge (a top-level entry no body repeats) is a bound shared with the field walk and with view-container-names.ts, not new here; see ③.
  • Same declared set, same arm. declared is still the union of every body's picklists (sibling packages included, the ADR-0130 co-ownership reading) and known.has(ref.picklist) is the one resolution test for both sites. The outside-dependency arm is judged.outside, computed per declaring body, so an unresolved extend is refused when that body names no dependency outside the artifact and is an info picklist-reference-unverified notice otherwise; a sibling's outside dependency does not soften it (pinned). The coarse trigger is untouched, as triage ruled (5923151807: a recorded line).
  • The schema, read. PicklistExtensionSchema (picklist.zod.ts:114) is a strictObject of extend: SnakeCaseIdentifierSchema and options: z.array(SelectOptionSchema).min(1), with the aliases picklist / target / name / extends folded into extend at the parse, so the parsed stack the judge reads carries extend and nothing else. picklistExtensions: z.array(PicklistExtensionSchema).optional() on ObjectStackDefinitionSchema (stack.zod.ts:360), compose disposition concat (:1035), and MAP_SUPPORTED_FIELDS deliberately excludes it (it keys by extend), so there is no record form and the Array.isArray(extensions) bound is exact. The typeof extend !== 'string' || extend === '' skip is unreachable post-parse and harmless.
  • The walk is where the outside-dependency arm belongs most. An extension is how a package adds to a list it does not own, so the info arm is the honest verdict for an extend aimed at a dependency outside the artifact; the header says so.

(b) The rule id is reused (picklist-reference-unknown / picklist-reference-unverified), not a sibling. Right. What is judged is one fact (a name that resolves to no picklist the stack declares) with one cure class and the same two verdicts; a second id would split one class of verdict for every consumer keyed on errors[].rule and warnings[].rule, and nothing in the repository registers these ids in a ledger (git grep on origin/main finds them only in the judge, its pins and the pending changeset). The finding says which key carried it in all three places a consumer reads: where is picklist extension "X" against field "obj.f", path ends in picklistExtensions[N].extend against .fields.f.picklist (prefixed packages[I].manifest. for a body), and the message opens with extend: 'X' against picklist: 'X'. A consumer that must count by key reads path, which is mechanical. The per-site sentences (wordingOf) differ only in the consequence and the hint, as they should: an extension gets "remove the entry" where a field gets "give the field inline options".

(c) validate.ts / compile.ts touched. Compelled: the heading literal A field names a picklist this stack does not declare (N references) stands over printAuthoringRuleErrors for every refusal in the set, so it would be false for an extend refusal and for a mixed set; A picklist reference names a picklist this stack does not declare is true for both. Grep on origin/main: the old literal appears on the two source lines only; no test, doc or changeset quotes it (names a picklist elsewhere is the liveness ledger's authorHint and a spec pin, both about fields, both untouched). The diff on the two commands is the literal plus the step 2d / 3a-bis comments; the call (judgePicklistReferences(result.data)), the notice capture, printPicklistReferenceNotices, the --json envelope and the exit are unchanged, and the --strict list is not touched. The parity roster scans bare identifiers either command calls: no new identifier is called (judgePicklistReferences is already a SHARED_NON_REGISTRY_GATES row, printError already NOT_A_GATE presentation), so no registration is owed and the dispatch's "only if the call or printing must change" allowance is met by the printing.

(d) Pins and ablations.

  • Unit, 10 new (6 in the one-package describe, 4 in the packages[] describe): the resolving control, the refusal naming extension / list / declared lists / path, a stack declaring no picklist, a field and an extension in one pass, neither hiding the other, the outside-dependency notice, sibling-package resolution, the inside-only refusal, the declaring package's own dependencies, and the top-level-beside-packages[] negative pin. Asserted by rule id, severity, where, path and the names in the message, not by the sentence.
  • Doors, 5 new over a real os process (11 now): a resolving extend validates and builds with the artifact carrying ['industry']; a dangling extend is refused by os validate --json (exactly one error, picklist-reference-unknown, where naming industy, path picklistExtensions[0].extend), by the text face (exit 1, the extension and the path on stdout), and by os build --json (exit 1, same rule, and dist/objectstack.json absent). The fixture's manifest declares no dependencies, so the refusal arm, not the notice, is what the door pins exercise.
  • Would they red without the walk? Yes, and the counts are consistent with the tests as written: disabling the extension walk leaves the controls and the negative pin green (they assert emptiness) and reds the seven unit pins and three door pins that assert a refusal or a notice, which is the dev's 7 / 12 and 3 / 8. Disabling the packages[] branch reds the two old and two new per-package pins that need a body and, by making the top-level branch judge the top-level entry, reds the negative pin too, which is the dev's 5 / 14 and proves that pin is not vacuous. Read off the test file, not run (Local-runs: none); the dev's ablations ran on committed fixes with restore proven.
  • The before / after rows in the report (exit 0 and artifact written at 2f2fa11d75; exit 1 and no artifact at the head, control unchanged) are the dev's measurement; the door file is the standing pin of the same facts.

(e) The changeset .changeset/20825-cli-picklist-extend.md. @objectstack/cli: minor; a feat(cli)!: title carrying (#20825) in the sibling form; Clause-②: no (narrowing — …); the adr-0087: not-required (no-migration-prescription) marker with its reasoning in the comment; the BREAKING banner naming the convention; the FROM state (both commands passed, os build wrote the artifact), the TO state (exit 1, picklist-reference-unknown), and a one-line fix with the three cures the hint offers; the "which extensions are judged" paragraph (load path, sibling resolution); the outside-dependency paragraph (info, in warnings and on the console, never gating, not under --strict). Every sentence is delivered at the head and none overstates: no runtime merge is claimed (commit 69f413fb76 corrected exactly that wording in the header). The disposition is the honest arm: no authorable spelling or type moves, packages/spec is untouched, and which list the author meant is not in the metadata, so objectstack migrate meta has nothing to rewrite; the same arm passed the gate on the predecessor with the same shape of "one-line fix" text.
The pending 20825-cli-picklist-kind.md is byte-identical at the head (correct: it is PR #21003's input to the release and describes what that PR shipped). Re-read for sentences made false by this diff: none. Its title and FROM / TO name a field's picklist, which is still exactly what it judged; its "Which references are judged" paragraph names the two collections the field walk reads and does not say "only"; its rule-id sentence remains true. The two changesets compile into two adjacent entries of the same release, and the new one's "the rule a field's dangling picklist already gets" reads correctly in that order.

② Semver level

Right. @objectstack/cli minor with the BREAKING banner: os validate and os build newly refuse an input they accepted, an accept-set narrowing on two published commands, which AGENTS.md's changeset rule grades BREAKING and the launch-window convention ships as minor (scripts/check-changeset-no-major.mjs refuses major; ADR-0087's ratified section: pre-GA a break ships minor, the banner and the disposition carry the breaking-ness, not the level). Clause-②: no (narrowing) is the well-formed pair from the closed set and is the true one for this diff: nothing is accepted that was refused before, the judge's five exports are the same five names, no spec key, export or config field moves, and the one narrowing is declared with its banner, its one-line fix and its ADR-0087 disposition. @objectstack/lint is correctly absent: packages/lint/** is untouched.

③ Boundary flags

  1. Every deviations entry. (i) Rule id reused: the claim left the choice to the dev on condition it is stated in the PR; it is, with the reasoning, and ①(b) finds it right. (ii) validate.ts / compile.ts touched: the claim allowed it when the printing must change; the refusal heading is the printing, it would be false otherwise, and the two step comments ride the same hunk; nothing else in those files moved (①(c)). (iii) Commit trailers in the Claude-Session + Co-authored-by: Claude form AGENTS.md prescribes, and the PR footer in the session-URL form: a reporting matter with no bearing on the head. (iv) Fresh worktree from 2f2fa11d75 while origin/main moved to d67b94280a: confirmed below, no path overlap and a clean merge-tree. (v) No label writes beyond the assignee; the labeler's four (documentation, size/m, tests, tooling) are the platform's; a changeset exists, so skip-changeset does not apply.
  2. The out-of-scope polish note. quoteNames([]) yields "names no picklist this stack declares (this stack declares no picklist)", redundant wording from PR feat(cli,lint)!: os validate and os build refuse a field picklist that names no picklist, and lint R8 counts picklist as an options source #21003 that now applies to both sites. True, cosmetic, not a contract defect and not an authoring trap; "carrier: none, noted" is the right disposition for a wording nit, and leaving PR feat(cli,lint)!: os validate and os build refuse a field picklist that names no picklist, and lint R8 counts picklist as an options source #21003's sentence alone kept this diff to the fold.
  3. Fixes #20825 closes the card correctly. Triage 5923151807 closed the card's ledger: "picklist kind: the os CLI compile / validate / lint path accepts *.picklist.ts, picklists and picklistExtensions, and os validate refuses a picklist that names no picklist (Scope 6 of #19518) #20825 closes on PR feat(cli,lint)!: os validate and os build refuse a field picklist that names no picklist, and lint R8 counts picklist as an options source #21003, plus the fold below. Nothing else." PR feat(cli,lint)!: os validate and os build refuse a field picklist that names no picklist, and lint R8 counts picklist as an options source #21003 landed (b84b240b2a, items 2 and 3, item 1's ingest half measured true at base); the generator and scaffold half, the Tier H skills line and the collectMetadataStats row are cli + create-objectstack: os generate picklist and the src/picklists scaffold wiring (os init / npm create), after the picklist runtime reader #19519 lands (split from #20825 item 1) #21018 (pm:blocked on picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519); the coarse outside-dependency trigger stays a recorded line by the same answer; the "neither options nor picklist" refusal was never on this card. This PR is the fold, delivered on both doors with the control and the negative pin triage's answer and the claim named. The landing note 5923331640 says the fold's PR closes the card with Fixes, and the head's The card this PR closes must claim this branch check is green against claim 5923350718's branch. Nothing triage left on the card is undelivered.
  4. Bounds carried forward, not closed here, no carrier owed by this card. (i) The top-level-beside-packages[] residual edge in ①(a): the field walk, the view-container judge and now the extension walk all take the resolver's reading; if picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519's reader ever registers a residual top-level extension under the artifact's id, the judge's reading and the loader's diverge on that edge for both keys alike. That is picklist metadata kind — runtime: resolve picklist → options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519's reader to decide, and the right act is one sentence in the judge's header when that reader lands. (ii) The predecessor record asked for a header sentence on the nested plugins[] bound (metadata typed unknown[], walked by no judge); this follow-up did not add it. Not a defect, as that record said; still worth the sentence on the next edit of the header.
  5. Scope and surface. Six files, all inside the claim's named surface (picklist-references.ts, its unit test, the door file, the two commands under the printing allowance, one changeset); packages/spec/**, packages/lint/**, the generator, the scaffold and collectMetadataStats are untouched. No governed path (Governed Surface Queue Guard green). Head repo = base repo. +411 / −58, under the 5,000 threshold. Draft, not armed.

Check-runs on the head (69f413fb76, collapsed latest-per-name, read after convergence at 03:46:49Z): 34 names, 31 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in): path-filtered or opt-in, none owed by a cli-only diff), 0 failures, 0 in progress. All seven required contexts success: Lint & Repo Gates (03:46:08Z, which carries check:adr-0087-registration and so re-validated the not-required (no-migration-prescription) arm against the changeset's own body), TypeScript Type Check (03:44:05Z), Test Core (03:46:44Z; shards 1/6 to 6/6 all success, which carries the cli unit tier, the parity roster and the door file), Dogfood Regression Gate (03:37:45Z; 1/3 to 3/3 all success, so no in-repo example stack is refused by the new walk), Build Core (03:32:39Z), Temporal Conformance (live PG + MySQL) (03:37:34Z), Governed Surface Queue Guard (03:31:40Z). Also success: Check Changeset, The card this PR closes must claim this branch, Part-of PR must not also close its card, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Check PR Size, Type Check · workspace / · source gates / · consumer gates / · debt ledger, Dogfood Verify CLI, Check Documentation Links, Flag docs affected by code changes, Auto Label, filter.
Mergeability: origin/main is d67b94280a; git diff --name-only 2f2fa11d75 origin/main touches none of the PR's six files; git merge-tree --write-tree origin/main refs/review/pr-21049 (a git read, nothing built) exits 0 with tree 103f28662197354b3aa63d49c261ce78c9992f6b and no conflicted path: clean.

Implemented-by: claude/issue-20825-picklist-extend-refusal
Reviewed-by: session_01VvcEokUG1tvVxkceYfR5XB

VERDICT: PASS


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants