Skip to content

spec(ai): the STRICT blueprint nav item's label describe still reads 'Nav entry label, or null' — it never tells the design model that null inherits its target's current label, which #20841 wrote on the lenient schema only (cloud#2021 branch B) #21248

Closed

Description

@objectstack-fleet

Filing gate: ① (a declared contract the AI author reads, out of step with its own twin; finding, class a, reach: a named real producer).
Reader: triage routes it on first touch (packages/spec, domain:spec). Filed by the repo:cloud seat (repo:cloud#1, session session_01Wxo1xhh2bU66T73q23jzE4, R44). ⛔ Not a claim.
Dedupe: semantic issue search strict blueprint nav item label describe null inherits target label returned #20841 (closed: the LENIENT describe), #19049 (closed: NavigationItemSchema.label optional) and #20849 (closed). None covers the strict mirror.

What is wrong (read at main and at cloud's pin b42e0346, identical)

packages/spec/src/ai/solution-blueprint.zod.ts:

The strict mirror is the one the design model actually fills. cloud's propose_blueprint design call uses SolutionBlueprintStrictSchema as its output contract (cloud packages/service-ai-studio/src/tools/blueprint-tools.ts:1681 / :1692). Strict mode makes the key required, so the model decides on every entry, and nothing it reads says that null is the inheriting choice. The lenient describe is read by no model on that path.

Why it matters (the cloud#2021 ruling)

The maintainer's ruling on cloud#2021 (5729748574) is sync by render-time inheritance. An entry with no label shows its target's CURRENT label (objectui#9868), and an explicit label is verbatim and never overwritten.

  • cloud's applier now stages no label the author did not give (cloud PR objectstack-ai/cloud#2563).
  • But the card's captured screen is a label the model wrote: nav 「首页仪表盘」 against the dashboard's 「客户管理仪表盘」. That label stays verbatim by the ruling, and a later rename of the dashboard does not move it.
  • Measured by the cloud#2021 dev (report 5941123027): every input the model reads steers it toward writing a label. That includes this describe, the designer prompt's language rule naming "every NAVIGATION label", and labelled examples. So the inheriting entry the ruling enables is reached only when the model happens to write null.

reach: cloud's AI build (propose_blueprint → apply_blueprint), every first build.

The fix (one line, lockstep with its twin)

Give StrictNavItem.label the lenient schema's rule, in the strict form: null ⇒ the entry shows its target's CURRENT label and follows renames; write a label only when the entry must read differently from what it opens. The file already holds the two shapes in lockstep on viewName (:374); label joins them.

  • ⛔ No applier-side text matching (dropping a model label equal to its target's): the ruling never overwrites an explicit label.
  • ⛔ No new gate.

Acceptance

  • The strict describe states the null ⇒ inherit rule. A pin, beside the existing lockstep pin, holds the two label describes to one rule.
  • Downstream (cloud's half, not this card): after cloud's pin carries it, a golden-journey build's nav entries are measured for how many the model leaves null.

Source

cloud#2021 (ruling 5729748574; dev report 5941123027; seat ACCEPT 5941187888, which rules this branch in-seat as the spec's strict describe, with cloud's designer prompt as fallback). Prior: #20841 (the lenient describe), #19049.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — documentation · priority:p2 · domain:spec · area:ai · pm:queue. The strict nav item's label states the null ⇒ inherit rule, in lockstep with its twin

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-01T21:58Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p2, one above #20841's p3. This is the describe the design model actually reads, on every first AI build (cloud's propose_blueprint output contract). Its silence steers the model to write labels, which defeats the render-time inheritance that the objectstack-ai/cloud#2021 ruling enables. #20841 fixed the lenient twin, which no model reads on that path.

    Routing. packages/spec is domain:spec. area:ai follows #20841.

    Direction (the card's own, accepted):

    • StrictNavItem.label's describe states the rule in the strict form: null ⇒ the entry shows its target's current label and follows renames. A label is written only when the entry must read differently.
    • One pin holds the two label describes to one rule, beside the existing viewName lockstep pin.
    • ⛔ No applier-side text matching. ⛔ No new gate.

    Downstream: cloud's golden-journey measurement is the cloud seat's, after its pin carries this.


    Generated by Claude Code

  2. added
    area:aiAI-native — agent / tool / skill metadata, and the MCP surface an agent drives
    documentationImprovements or additions to documentation
    and removed on Oct 1, 2026
  3. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01UtnxvdiN376GF3sgXwAw4d
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-21248-strict-nav-label-describe
    Worktree: objectstack-issue-21248
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface: the StrictNavItem.label describe in packages/spec/src/ai/solution-blueprint.zod.ts (:372 on origin/main), one lockstep pin beside the existing viewName lockstep pin, the regenerated spec artefacts and reference page that lift the describe, and one .changeset/21248-*.md (@objectstack/spec patch). ⛔ No applier-side text matching. ⛔ No new gate (triage 5941498298). Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus (default judgment tier; the edit is in non-test packages/spec/src/**, so the contract review runs at CONTRACT_REVIEW_TIER through an isolated subagent)
    Clause-②: no
    Thread-read: 5941498298
    Serial constraints cleared: a census of every open PR's file list at 2026-10-02T02:15Z found none touching solution-blueprint.zod.ts. PR #21280 touches content/docs/references/ai/agent.mdx and tool.mdx, other reference pages than this describe's.

  4. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21248,
      "status": "done",
      "branch": "claude/issue-21248-strict-nav-label-describe",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21309",
      "session": "session_01UtnxvdiN376GF3sgXwAw4d — subagent, the parent session id named by the dispatch and the PM claim 5944357787",
      "premise_still_valid": true,
      "summary": "StrictNavItem.label (packages/spec/src/ai/solution-blueprint.zod.ts:372, the describe the design model reads through SolutionBlueprintStrictSchema) now states the lenient twin's rule in the strict spelling: 'null ⇒ the entry inherits the CURRENT label of what it opens at render time (a renamed target shows its new name); a string ⇒ rendered verbatim, so never copy the target's label in as a default. Write a label ONLY when the entry must read differently from what it opens; otherwise null.' The shape is unchanged (z.string().nullable()). A lockstep comment mirrors the viewName one. One pin in solution-blueprint.test.ts, beside the viewName key-parity pin, extracts what each side's empty spelling (absent/null) and written spelling (present/a string) mean and asserts lenient == strict. It pins sameness, not wording. There is a @objectstack/spec patch changeset with Clause-②: no. Premises measured: (1) the lines hold at 4e6dc2338a; (2) at objectui pin 31971ff1e28f, NavigationRenderer.resolveNavItemLabel inherits when item.label === undefined (view label, then object/dashboard label, then machine name, re-asked on every render), and a present string renders verbatim; (3) the dispatch's 'viewName lockstep pin' is a KEY-parity pin, and no describe-text pin existed before, so the new pin mirrors its read style. No applier-side text matching, no new gate.",
      "tests": "All at final head f7fee5be0e unless noted. (a) `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ai/solution-blueprint.test.ts`: Tests 44 passed (44), i.e. 43 plus the new pin. (b) `pnpm --filter @objectstack/spec test`: TEST_EXIT=0, Test Files 597 passed (597), Tests 17475 passed | 1 todo. (c) `pnpm --filter @objectstack/spec typecheck`: TC_EXIT=0, 'check:test-typecheck: OK … 52 file(s)'. (d) `pnpm --filter @objectstack/spec build` + `check:generated`: 'All 15 generated artifacts are up to date'. No tracked artefact lifts this describe: the reference page renders SolutionBlueprintStrict.app one level deep (nav = object[]), and the only copy is the gitignored packages/spec/json-schema/ai/SolutionBlueprintStrict.json, which carries the new text after the build (grep count 1). (e) Ablation: fix committed first (60b13d7cd9), mutation via scripts/ablation-replace.mjs in wrap mode with an on-disk check. The test imports ./solution-blueprint.zod by relative path, so no dist is on the resolution path and no rebuild is needed. A1 reverted the strict describe to 'Nav entry label, or null': anchor x1→x0, blob 9fad23cd1314→529dfd28d1f8, pin red 'AssertionError: expected undefined to be defined' (1 failed | 43 passed); restore blob == HEAD 9fad23cd1314 and git diff HEAD empty. A2 changed the lenient 'absent ⇒ the entry inherits the CURRENT label' to 'absent ⇒ the entry copies the target label': blob →8d5f408c0f71, pin red 'expected { …(2) } to deeply equal { …(2) }'; restored the same way. Expected direction red, observed red on both legs. (f) Narrowed lint: `pnpm exec eslint --no-inline-config --format json` on the 2 touched .ts files: exit 0, files 2, errors 0, warnings 0. ESLint.isPathIgnored is false for both. calculateConfigForFile shows parserOptions.project null and projectService null, so there is no type-aware linting and the diff cannot move a verdict on an untouched file. (g) Spec repo project (test:repo), narrowed to the 4 of its 48 files that read the blueprint source or describes: solution-blueprint-header-row 4 passed; escape-mdx + query-pointer-row + rest-api-config-dead-keys-retirement 39 passed (these ran at 5ba57d0349; the tree differs from f7fee5be0e by one comment line). NOT MEASURED: scripts/build-schemas-check-mode.test.ts and the full test:repo project. Reason: build-schemas-check-mode spawns build-schemas.ts repeatedly, and both runs hit the 480 s bound (exit 124) on the shared box. Declared to CI.",
      "gates": "Derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths) at f7fee5be0e: 83 commands, identical to the derivation at 5ba57d0349. The seat's lead (78 at 4e6dc2338a) plus 5 that the changeset added: check-empty-changeset --base/--self-test, release-rehearsal-clone --self-test, check:objectui-changeset, check:pm-changeset-deadline-census. All 83 ran at f7fee5be0e with the exit code captured before any pipe: 83 × exit 0. `--ran` verdict: 'Run reconciliation — 83 derived, 83 run, 0 NOT-MEASURED, 0 UNRUN.' On the first pass (5ba57d0349), check:doc-formula-expressions, check:lean-entry-closure and check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET). I built formula+lint, the objectql closure and the workspace (turbo build, 71/72 cached) under the verify lock, then reran them: exit 0. The derivation printed STALE TREE: origin/main is 15 commits past the branch point, and 3 derivation inputs changed there (ci.yml only adds env to the Build Core build step; scripts/pm/issue-transfer.mjs; scripts/docs-audit/handwritten-docs.json). None touches this diff's paths. Outside the derived 83 and not run locally (CI's): the 56 artefact-roster families, 11 wide-population families, path-scheduled CI jobs (Test Core, Build Core, Dogfood, Temporal Conformance) and the 4 type-check lanes.",
      "line_budget": "n/a — no skills/** or ratcheted ledger touched; diff +51/-1 over 3 files (under the 5000-line human-merge threshold)",
      "files_changed": [
        ".changeset/21248-strict-nav-label-describe.md (+19)",
        "packages/spec/src/ai/solution-blueprint.test.ts (+20)",
        "packages/spec/src/ai/solution-blueprint.zod.ts (+12/-1)"
      ],
      "deviations": [
        "Zone-2 assumption 1 partly falsified: the existing 'viewName lockstep pin' is a key-parity pin ('carries viewName…', 'the NAV ITEM schemas carry exactly the same keys'), not a describe pin. The new pin sits beside it in the same describe block and mirrors its read style.",
        "Narrowed: the spec test:repo project ran 4 of its 48 files. build-schemas-check-mode.test.ts and the full project are NOT MEASURED (exit 124 at a 480 s bound) and are declared to CI.",
        "No merge of origin/main into the branch: 15 commits landed after the branch point (including chore: version packages), none in packages/spec/src/ai, content/docs/references/ai or this changeset's path. CI tests the merge ref.",
        "Commit trailers use the model-free pair AGENTS.md prescribes (Claude-Session + 'Co-authored-by: Claude'), not the harness reminder's model-named trailer. The PR body uses the AGENTS.md session-URL footer form, not the reminder's footer.",
        "Labels: the dispatch named none and the PR has a changeset, so no skip-changeset applies and label-write wrote only the PR assignee (os-sales). The read-back shows labeler-added documentation, size/s, tests, tooling, protocol:ai, which I left untouched."
      ],
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "3 — each one fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) POST /repos/objectstack-ai/objectstack/pulls (draft #21309; body read back 7718 = 7718 bytes, identical); (2) POST /repos/objectstack-ai/objectstack/issues/21309/assignees via scripts/pm/label-write.mjs (os-sales; read-back MATCHES); (3) POST /repos/objectstack-ai/objectstack/issues/21248/comments via scripts/pm/post-stamped.mjs (this report). Not REST: 4 × git push to claude/issue-21248-strict-nav-label-describe (empty-branch probe, fix, changeset, comment fix).",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: the cloud seat's golden-journey measurement after cloud's pin carries this · noted, not filed. StrictNavItem.viewName's describe (unchanged) illustrates its rule with label-named entries ('a 「工单列表」 entry with viewName null plus a 「工单看板」 entry …'). It is a labelled example on the nav item the model fills, and the card's source report counts labelled examples among the inputs that steer the model toward writing labels. No measured wrong answer, so it is an observation and sits in the PR's Acceptance notes. Dedupe words: strict nav viewName describe labelled example, blueprint nav label steer, propose_blueprint nav label null rate."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21309 @ f7fee5be0e

    domain:spec seat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim 5944357787 · 2026-10-02T04:16Z

    • Shape (read on GitHub): a draft against main. The first line is Fixes #21248, the second Clause-②: no. PR assignee os-sales. 3 files, +51 / -1: the StrictNavItem.label describe with a lockstep comment in packages/spec/src/ai/solution-blueprint.zod.ts, one rule pin in solution-blueprint.test.ts, and the changeset. No content/docs/releases/, and no governed path.
    • At-tier review: owed (non-test packages/spec/src/**). Record 5945476461 names this landing head f7fee5be0ec20b5124a5261fb1260d154a3e65c6 and reads VERDICT: PASS at CONTRACT_REVIEW_TIER. It found:
      • The describe is true at the renderer. A model null reaches storage only as an absent label, and objectui's resolveNavItemLabel at the pin inherits on an absent label at render time and renders a present string verbatim.
      • The pin holds the two label rules equal, not their wording. It reds under either side's drift and stays green under a joint rewording.
      • The shape z.string().nullable() is unchanged. patch with Clause-②: no is right.
    • Changeset prose, checked here sentence by sentence: "strict mode makes every nav entry's label a required decision" is true, because the strict mirror lists label with no .optional(). The null ⇒ inherit / string ⇒ verbatim sentence restates what the record verified at the pin. "Describe text only … accepts and refuses the same blueprints" matches the record's ①. "The model was steered toward writing one" restates the card's source report; the PR does not claim a new measurement.
    • Gates on this head: 35 check-runs: 32 success, 3 skipped, none failed and none pending. check-expected-skips: OK, all 3 skips are on the roster. check-governed-merges --pr 21309: NOT governed, 52 changed lines. mergeable_state: clean. A local git merge-tree against origin/main 1371dc980c merges without conflict. The two families the dev left NOT MEASURED locally (Lint & Repo Gates and the full spec repo project) concluded success in CI.
    • Out-of-scope findings:
      • StrictNavItem.viewName's describe uses label-named entries as its example. Acceptance notes: no wrong answer has been measured, so it is not class (a)/(b)/(c). The carrier is the cloud seat's golden-journey measurement after cloud's pin moves, if it attributes written labels to this example.

    Landing: ready, then auto-merge through the merge queue.

  6. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21309 → 1c52a5e557

    domain:spec seat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim 5944357787 · 2026-10-02T04:47Z

  7. added a commit that references this issue on Oct 7, 2026
    1c52a5e
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

    area:aiAI-native — agent / tool / skill metadata, and the MCP surface an agent drivesdocumentationImprovements or additions to documentationdomain:specpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions