Skip to content

os validate: a field naming a sibling package's picklist without a declared dependency passes the author-time door, then is refused at install (ADR-0150 D3) #31832

Closed

Description

@objectstack-fleet

domain:server#2 · os-steve · session_01ADHkmQKZzp4DLtqiCkZZRM

Filing gate: ③ door drift with a named seam (finding F1 of the CONTRACT_REVIEW_TIER record on PR #31731, E6 #31678 of epic #31527). ⛔ Not a claim.
reach: read on PR #31731's head bf7e3295 against main. After E6, a packaged field whose picklist names a list a SIBLING package in the same artifact owns, without a declared dependency on it, warns at boot, serves no options, and is refused at the post-seal install door. This follows the maintainer ruling 6729820123 (declared dependency closure only). The author-time door still accepts it: packages/cli/src/utils/picklist-references.ts judgePicklistReferences resolves against every picklist the stack declares, and its module header says "a list a SIBLING package in the same artifact owns resolves here exactly as one the declaring package owns" (ADR-0130). So os validate / os build no longer foretell the install door for this case. The ruling gives the ADR-0130 note to child A #31685, but names no carrier for the door's code.
Business reason: an author is told the package is valid, then the install refuses it and the field shows no options. The fix at author time is one line (declare the dependency), but nothing says so until install.
Reader: the next domain:server seat, from pm:queue, after PR #31731 lands.
Dedupe (open AND closed): judgePicklistReferences sibling dependency, picklist-references sibling ADR-0150 → nothing on point. #31685 (child A) carries the ADR text only.
class: c · type: Bug
Grade: p3. An authoring door that disagrees with the install door. No wrong data.

维护者速读

ADR-0150 落地后(E6),一个字段引用同一发布包里兄弟包的选项列表、却没有声明依赖时,安装会被拒,字段也拿不到选项。可是 os validate 仍然放行,作者直到安装才发现问题。这张卡让 os validate 当场指出缺少依赖,提示和安装时的拒绝一致。

What to build

  1. judgePicklistReferences resolves a packaged field's picklist the way the runtime does after E6: the field's own package, then its declared dependency closure, then sys. Where it can, it reuses the same resolver (resolvePackageScopedHolders in @objectstack/core and packageResolutionClosure in @objectstack/metadata-core). ⛔ No second closure walk. A sibling's list without a declared dependency is refused with the runtime's sentence, which names the candidate and the one-line fix (declare the dependency).
  2. Update the module header's ADR-0130 paragraph to the ruled rule, citing ADR-0150 D3 and ruling 6729820123. Coordinate the wording with child A docs/adr: ADR-0048 addendum (§3.4 and §5 lint text) and ADR-0129 cross-reference for ADR-0150 (ADR-0150 child A) #31685, which writes the ADR note.
  3. Tests: a sibling list without a declared dependency → refused, RED on main (accepted). CONTROL: with the dependency declared → accepted; the declaring package's own list → accepted; the boot-parity test stays green.

Surface

  • Expected file surface: packages/cli/src/utils/picklist-references.ts and its tests, root .changeset/.
  • Clause-②: no (narrowing). The door now refuses what the runtime already refuses. The seat owes a contract review at CONTRACT_REVIEW_TIER.

Related: #31678 (E6), PR #31731, #31685, epic #31527. Blocked-by: #31678's PR #31731.

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 9, 2026
  2. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX

    Claim: PM loop round 10 · #31832 · 2026-11-09T06:57Z
    Session: session_01ADHkmP8JJo4zF2sjTBRwnX (seat); dev = card thread session_01ADHkmQYiGGwc3V7yGZ4iTo
    Account: os-steve
    Branch: claude/project-thread-xj3m25
    Worktree: the card thread's checkout of objectstack-ai/objectstack
    Domain: domain:server
    Seat: domain:server#4
    File surface: packages/cli/src/utils/picklist-references.ts, its tests, root .changeset/31832-*.md (stop on breach)
    Container & model: S, mode:thread, model: default tier, effort: medium
    Clause-②: no (narrowing, as the card declares: the author-time door refuses what the runtime already refuses since E6); the seat owes a contract review at CONTRACT_REVIEW_TIER (narrowing face) before landing
    Metadata-protocol: none
    Responsibility: os validate / os build refuse a packaged field whose picklist names a sibling package's list with no declared dependency, with the runtime's sentence and the one-line fix · today the author door accepts it and the install door refuses it, so the field serves no options · reached by every author of a multi-package artifact
    Thread-read: none
    Premise: holds. PR #31731 (E6 #31678) is merged; judgePicklistReferences still resolves against every picklist the stack declares. Step 0 re-measures.
    Serial constraints: a follow-up of E6 of epic #31527; the epic seat is told before landing. ADR wording aligns with child A #31685 (read only).

  3. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: confirmed · domain:server · p3 · 2026-11-09T06:59Z

    ⛔ Not a claim.

    • Lane: domain:server, os validate for a sibling package's picklist without a declared dependency (ADR-0150 D3). p3.

    Generated by Claude Code

  4. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-steve · session_01ADHkmQYiGGwc3V7yGZ4iTo
    os-dev-report

    {
      "issue": 31832,
      "status": "done",
      "branch": "claude/project-thread-xj3m25",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31864",
      "session": "session_01ADHkmQYiGGwc3V7yGZ4iTo",
      "premise_still_valid": true,
      "summary": "judgePicklistReferences now resolves each reference with the runtime's own rule: resolvePackageScopedHolders (@objectstack/core) over the artifact's holders, with the closure from packageResolutionClosure (@objectstack/metadata-core) read through a registry view of the packages[] bodies. No second closure walk; both packages were already direct cli dependencies. A field's picklist resolves from the declaring package (own, declared closure plus sys); a sibling's list with no declared dependency is refused as picklist-reference-unknown with R's message verbatim and a hint naming the exact dependencies entry. An extension's extend resolves with no caller (ruling X), unchanged in effect. With an outside dependency the closure refusal is an info notice, as before. Module header's ADR-0130 paragraph rewritten to ADR-0150 D3 + ruling 6729820123, worded after child A's ADR-0130 note (PR #31841).",
      "tests": "head 8f820082: picklist-references.test.ts + boot-parity: Test Files 2 passed, Tests 47 passed (8 new unit cases, 2 new boot-parity cases on a real kernel: door message === findOutsideClosurePicklistReferences() refusal). RED on main: implementation swapped to base cc16008a blob 931bc8f2 -> 5 failed / 42 passed (sibling refusal, two-holder, outside-dependency notice, boot-parity sentence, forced heading); every CONTROL green on main; restored, git diff HEAD empty, blob 582ba0e4 == HEAD. Ablation (scripts/ablation-replace.mjs, closureOf -> every artifact package): 3 failed / 44 passed (sibling refusal, notice, boot-parity sentence); two-holder pin stays green as an ambiguity refusal naming both; tool restore blob == HEAD, git diff HEAD empty. Tests import src, so no dist rebuild was in the path. cli unit tier: 406 files / 5801 tests passed; integration tier declared to CI.",
      "gates": "local, after the last commit 8f820082: dispatch-gates --commands derived 63 families; all 63 exit 0; --ran reconciliation 63 derived / 63 run / 0 NOT-MEASURED / 0 UNRUN. First pass on fcf3a4b9 had check-adr-0087-registration exit 1 (changeset lacked the disposition marker), fixed in 8f820082. Full turbo build of packages/** exit 0 (VERDICT command-exit 0). pnpm --filter @objectstack/cli typecheck exit 0. eslint --no-inline-config over the 3 changed source files exit 0 (cli has no lint script). CI check-runs: not yet read, PR just opened.",
      "dogfood_battery": "8f820082 code (built at fcf3a4b9, same source) · os-verify-lock VERDICT command-exit 0 · Test Files 441 passed, 3 skipped (444) · Tests 3662 passed, 42 skipped (3704) · no failures",
      "mcp_calls": "5 — all reads: mcp__github__issue_read get #31832, get_comments #31832 (x2), issue_read get #31685, pull_request_read get_diff #31841",
      "api_writes": "2 — relay pr_create (objectstack-ai/objectstack, PR #31864, body read back 9112 bytes identical); this os-dev-report comment via post-stamped.mjs",
      "files_changed": [
        "packages/cli/src/utils/picklist-references.ts",
        "packages/cli/src/utils/picklist-references.test.ts",
        "packages/cli/src/utils/picklist-references.boot-parity.test.ts",
        ".changeset/31832-picklist-sibling-dependency.md"
      ],
      "line_budget": "+315 / -29 over 4 files (dispatch-gates reading); all inside the claimed file surface",
      "deviations": [
        "PR opened with no assignee: the standard order forbids MCP writes and the relay pr_create read-back lists assignees: none.",
        "Not subscribed to PR activity: the dispatch contract says not to wait on CI; FAIL rounds come back through the seat.",
        "Changeset carries the ADR-0087 marker not-required (no-migration-prescription), the same category E6 used; the gate requires one on a (narrowing) changeset."
      ],
      "open_questions": [
        {
          "question": "Rule id for the closure refusal: this PR reuses picklist-reference-unknown (no new public name). Keep it, or should the seat name a separate id?",
          "options": ["A keep picklist-reference-unknown: one fact (the name resolves to no list from where it is written), R's sentence says why", "B new id such as picklist-reference-outside-closure, plus an os explain entry in packages/lint (outside this surface)"],
          "recommendation": "A, because the delta forbids new public names and the message already carries the runtime's reason"
        },
        {
          "question": "os explain picklist-reference-unverified (packages/lint/src/rule-explanations.ts) still says the door resolves against every picklist the stack declares. Outside this card's surface: widen the surface for a one-paragraph edit, or file it?",
          "options": ["A seat widens the surface; one paragraph in rule-explanations.ts on this PR", "B a follow-up docs card", "C leave it: the notice arm itself is unchanged"],
          "recommendation": "A, because it is one paragraph and keeps os explain true on the day the door changes"
        },
        {
          "question": "PR #31841 (child A, Tier H) adds a dated note to this same module header saying the code is not yet wired. Order?",
          "options": ["A land this first; #31841 merges main and drops its note in picklist-references.ts", "B land #31841 first; this PR merges main and its rewrite replaces the note"],
          "recommendation": "A or B both work; the note becomes stale once this lands, so whichever lands second drops it. The epic seat should know."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: seat 4 · noted, not filed: the first boot-parity describe's CONTROL stack (a body field naming a sibling's list, no dependency) is now also refused by this door as picklist-reference-unknown; the CONTROL only asserts the residual rule, so it stays green and still tests what it was written for."
      ]
    }

    Stamped 2026-11-09T08:09Z.

  5. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX

    Claim amendment · #31832 · 2026-11-09T08:12Z · amends claim 6734213007 · File surface only (Branch, Clause-②, Metadata-protocol unchanged)

    • File surface adds packages/lint/src/rule-explanations.ts, only the picklist-reference-unverified entry's text (one paragraph), plus its test if a pinned string moves. After this card, that os explain entry says a sibling package's list resolves without a declared dependency, which is now false. No rule id, severity or export moves.
  6. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-steve · session_01ADHkmP8JJo4zF2sjTBRwnX

    Landed · #31832 · 2026-11-09T12:31Z

    Probe Reading
    PR #31864 merged through the queue 2026-11-09T12:31Z as 80c4c9a286. Head 661fbd90d0: ACCEPT 6738444696; contract review r1 6736073411 → delta PASS 6737277130; epic red-line PASS 6737971511. The first merge group was ejected by an unrelated plugin-security timeout (6739499314), and the PR was re-queued unchanged
    #31832 closed by Fixes #31832

    State change: pm:queue removed in this stroke. Three cases are now refused by os validate / os build, each with the runtime's sentence and the one dependencies entry to add:

    • a packaged field whose picklist only a sibling package holds, when no dependency is declared on it;
    • a field whose list two declared siblings hold;
    • an extension target that two packages hold.

    The door reads the runtime's own resolver: resolvePackageScopedHolders over packageResolutionClosure, so there is no second walk. Boot parity is pinned on a real kernel. #31841's "not yet wired" note is dropped.

  7. added a commit that references this issue on Nov 10, 2026
    80c4c9a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions