Repository navigation
feat(spec): the customizations read's contributions declare a nav kind, with an entry shape per kind - #29401
Conversation
…ith a per-kind entry shape GetPackageCustomizationsResponseSchema.contributions becomes a discriminated union on `kind`: the field/flow entry (on an `object`, accepting exactly what it accepted before) and a nav entry (on an `app`, with an optional `group`, the nav item id as `name`, and `packageId`). The nav arm is declared ahead of its producer and says so with a `[PLANNED — not yet served]` marker, pinned in the test. Claude-Session: https://claude.ai/code/session_01ADHkmQ28WWiNo3u94yGTPb Co-authored-by: Claude <noreply@anthropic.com>
…eset, producer type narrowing The package-lifecycle reference page and the api strictness count follow the per-kind contributions entry. The metadata API page names the contributions list and says the nav kind is not yet served. The runtime producer's local entry type excludes the nav arm so it keeps compiling; it lists the same field and flow entries as before. Claude-Session: https://claude.ai/code/session_01ADHkmQ28WWiNo3u94yGTPb Co-authored-by: Claude <noreply@anthropic.com>
…ledger check:variant-docs names the new discriminated union of the customizations read's contributions entry. It is bound to the API docs page that names the list and its nav kind, as the flow registration verdict (also served, not authored) is bound to its page. Claude-Session: https://claude.ai/code/session_01ADHkmQ28WWiNo3u94yGTPb Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 173 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 05de8c9238d8cf7a43a02a2978ea37f1fda3faa6 && git checkout 05de8c9238d8cf7a43a02a2978ea37f1fda3faa6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a20dcae3f9beb3ade7a99d2d06a8be01c0f842cb 71941530252aa778086e2580e2e105513fe6563f && git checkout -B drift-repro a20dcae3f9beb3ade7a99d2d06a8be01c0f842cb && git merge --no-ff 71941530252aa778086e2580e2e105513fe6563f
node scripts/docs-audit/affected-docs.mjs --json a20dcae3f9beb3ade7a99d2d06a8be01c0f842cb |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract-review-claim: session_01ADHkmSduNGC2SZXszyrNut · head 3bdb413 · 2026-11-02T14:10Z The lane seat reviews this head itself at |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: Inputs read: card #27928 body and all eleven comments (ruling 6566915346 letter E; os-dev-report 6579232649), PR #29401 body, file list and net diff against the merge base ① Derived judgments
② Semver level
③ Boundary flagsDev report 6579232649, four
Deviations (six), each answered:
④ Design judgment
Holds. The union implements the ruling's four bullets exactly (①). The design was tried against: a field/flow body from today's runtime (accepted, preserved, CONTROL pins); a nav entry with and without The Should-fix 1 — an id-less contributed item has no Should-fix 2 — pin the schema-level "(not yet served)" too. The not-yet-served fact is written in three describes (the Out of scope, for the seat. Check-runs at review time. Implemented-by: VERDICT: PASS |
… schema describe's not-yet-served Contract-review should-fixes: a contributed separator may omit its id, so it has no value for the nav entry's required name and is not an entry; the nav arm's describe says so and a test pins it. The schema-level describe's "(not yet served)" is pinned beside the member pin, so the slice that serves the kind cannot leave it behind. Claude-Session: https://claude.ai/code/session_01ADHkmQ28WWiNo3u94yGTPb Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: This is a DELTA record for the push after the PASS record 6579472483 (head Check-runs on Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Head-sha: 维护者速读
席位意见:可落地。契约复审在 3bdb413 判 PASS(6579472483),在现 head 的 delta 判 PASS,CI 全绿。 席位核对 — ACCEPT ·
|
Requested by Steve · project thread
Fixes #27928
Part of #28722 (its Wanted 3, the server slice that serves the
navkind, stays open).Ruling-ref: 6566915346
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — a served response member no author writes, and its
navkind is not served yet, so there is nothing to teach; the API docs page namescontributionsin this diff and the server slice that serves the kind owns the rest维护者速读
GET /packages/:id/customizations的contributions加第三种nav(别的包往本包应用里加的导航项),条目形状按种类分:字段/流程条目照旧带object,导航条目带app、可选group、导航项 id(name)和packageId。nav臂标了 [PLANNED — not yet served],服务端还不发;运行时只改了一处本地类型标注(编译所需,行为不变);回滚就是撤掉这个 PR。packages/spec/src/api/package-lifecycle.zod.ts(contributions 的两臂和排序说明)、packages/spec/src/api/package-lifecycle.test.ts(新测试和 PLANNED 钉)、packages/runtime/src/domains/packages.ts(一行类型收窄)。pnpm --filter @objectstack/spec exec vitest run src/api/package-lifecycle.test.ts,预期 35 个测试全部通过。What changes
The customizations read's
contributionsmember gains thenavkind the ruling binds (letter E): a navigation item another installed package'snavigationContributionsadd to an app the base owns. Declared ahead of its producer (ADR-0049): the runtime lists nonaventry until the server slice of #28722 (Wanted 3) reads the navigation-contribution registry.GetPackageCustomizationsResponseSchema.contributions(packages/spec/src/api/package-lifecycle.zod.ts) is now an array ofz.discriminatedUnion('kind', …)with two strict arms:field/flowarm:kind,object,name,packageId, the same members and describes as before, so it accepts exactly what the member accepted (CONTROL tests); the arm gains a one-line describe;navarm:kind: 'nav',app(the base-owned app), an optionalgroup(the group the contribution targets, as declared),name(the nav item's id) andpackageId. Its describe opens[PLANNED — not yet served]and names ADR-0049; the tracker ids are in an adjacent//comment only.fieldandflowentries first, in their existing order (object, kind, name, package), then thenaventries by app, group (an entry with no group first), name and package.counts.contributionsstill counts items.appNavContributionsregistry the navigation fold reads, never join by name, list no disabled package's contributions, and add no key to any nav item. The schema-level describe names the third kind as not yet served.content/docs/references/api/package-lifecycle.mdx(the union renders as two nested shapes), and the strictness ledger countdocs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md(486 to 487, the new strict arm).content/docs/api/metadata-api.mdxnames thecontributionslist beside overrides, extensions and switched-off items, and says thenavkind is declared and not yet served..changeset/27928-customizations-contributions-nav-kind.md:@objectstack/specminor, with both declaration lines.packages/spec/variant-docs.json: a governed entry for the new union (keykind:nav), bound tocontent/docs/api/metadata-api.mdx, which names the list and itsnavkind. The gate's walker reads only literal arms, so thefield/flowarm'sz.enumis not in the key.packages/runtime/src/domains/packages.tsreadBaseContributionsnow types its local entries as thecontributionsentry type with thenavarm excluded (a TypeScriptExcludeover{ kind: 'nav' }). Without it,pnpm --filter @objectstack/runtime typecheckexits 2 (TS2339objecton the union, twice, and TS7053 on thekindOrderindex, twice). Type-only: the producer lists the samefieldandflowentries, and reading the nav registry stays the server slice's. No runtime changeset: the edit is a type annotation and a comment inside a non-exported function, so no published byte moves.No liveness row. Measured:
packages/spec/liveness/governs metadata types (its README: the source of truth is the metadata-type registry), and no ledger names this API response or its member (git grepoverpackages/spec/livenessfinds neither). The declared-not-served channel for a wire schema is the describe marker, pinned in the test.Why
The ruling (6566915346, letter E, the maintainer's 「同意」) serves the contributed navigation entries as a third kind of the landed
contributionsmember, so Studio can draw them greyed with their package (#27356 row 20) from one served read, never a client-side join on names. The server slice needs this declaration first.Tests
packages/spec/src/api/package-lifecycle.test.ts, new: CONTROL, a field and a flow entry parse and are preserved; CONTROL, a field entry still requiresobject; a nav entry with its group parses and is preserved; a nav entry without a group parses and gains none; a nav entry carryingobjectis refused (unrecognized_keys); a field entry carryingappand a flow entry carryinggroupare refused (unrecognized_keys); a nav entry withoutapp,nameorpackageIdis refused (invalid_typeat that path); an unknown kind is refused (invalid_unionatkind); the nav arm's describe opens the planned marker, names ADR-0049 and carries no tracker id; the member says the kind is not yet served and the field / flow arm carries no marker.Local readings (head `3bdb4133`; the last commit touches only `packages/spec/variant-docs.json`, which no test reads, so the test readings from `b6afae25` stand)
pnpm --filter @objectstack/spec buildunder os-verify-lock, VERDICT command-exit 0.check:generatedthen exit 1 naming two stale artifacts (check:docs,check:strictness-ledger);check:generated --fixVERDICT 0 regenerated exactly those two, committed with the source.check:generatedwithout fix: exit 0 in the gate run below.pnpm --filter @objectstack/spec test: VERDICT command-exit 0, Test Files 791 passed (791), Tests 23401 passed, 1 todo.src/api/package-lifecycle.test.tsalone: 35 passed (25 existing, 10 new).pnpm --filter @objectstack/spec typecheck: VERDICT command-exit 0 (the test layer compiles; 131 pinned signatures held).turbo run build --filter='@objectstack/runtime^...' --filter='@objectstack/client^...', VERDICT 0 (36 tasks); then@objectstack/clientand@objectstack/driver-tursobuilt.@objectstack/runtimetypecheck: exit 2 before the forced edit (TS2339objecttwice and TS7053 twice, all inreadBaseContributions), exit 0 after it (test layer: 68 pinned signatures held). The red-then-green is also the reverse verification that the consumer reads the rebuilt spec declarations, not a cached copy.@objectstack/runtimesrc/domains/packages-customizations.test.ts+src/dispatcher-plugin.package-customizations-mount.test.ts: Test Files 2 passed (2), Tests 19 passed.@objectstack/clienttypecheck: exit 0 (it types the response throughGetPackageCustomizationsResponse).ui/packages/app-shell:turbo run build --filter='@object-ui/app-shell^...'VERDICT 0 (64 tasks), thenpnpm type-check(both tsc programs) VERDICT 0. The four app-shell files that build a customizations answer (customizationMarks.test.ts,BuilderLanding.customizationCounts-28566.test.tsx,metadataAreaHost.customizations-28568.test.tsx,StudioDesignSurface.oneSurface-28572.test.tsx, run fromui/): Test Files 4 passed (4), Tests 34 passed. No non-test ui source readscontributionstoday.pnpm exec eslint --no-inline-configover the three changed .ts files, exit 0, no findings..strict()removed withscripts/ablation-replace.mjs(anchor 1 to 0, blobfd4561e7tobbc05a90) in a side worktree under a trap:package-lifecycle.test.tsexit 1, 1 failed of 35 (a nav entry carryingobjectis refused). Restored: blob equal to HEADfd4561e7,git diff HEADempty, exit 0, 35 passed.check-changeset-no-major.mjs --base origin/main --eventwith this body: exit 0, level axisyes (widening)+added.check-partof-closing-keyword.mjswith this body: exit 0.check:skill-frame-freshnessexit 0;check:dev-prereqsexit 1 because 8 of 106 workspace packages (ui examples and ui CLI among them) have no dist in this container, a machine-state reading and not a finding of this diff (packages/specdist is fresh).2f7977cd, none touching this diff's files; not merged in before opening.!in the title and no(narrowing)arm.Gates (dispatch-gates --commands, 105 commands)
3bdb4133: 105 derived commands, all 105 exit 0 (each with stdin closed, exit captured before any pipe).dispatch-gates --ran: 105 derived, 105 run, 0 NOT-MEASURED (a derived zero), 0 UNRUN.b6afae25: 104 of 105 exit 0;check:variant-docsexit 1 ("undeclared discriminated union" for the new contributions entry), fixed in3bdb4133by a governedpackages/spec/variant-docs.jsonentry bound tocontent/docs/api/metadata-api.mdx.turbo run build --concurrency=2 --filter='./packages/**'under os-verify-lock, VERDICT 0 (78 tasks).Acceptance notes
naventries,groupas declared (named even when the app declares no such group), and one entry per item of the contribution'sitems(a contributed group's children are not entries of their own).skills/objectstack-platform/SKILL.mddescribes the customizations read as listing "overrides, extensions and switched-off items" and has not namedcontributionssince that member landed. A governed surface, not touched here; noted for the seat (carrier: the server slice that serves the kind)..changeset/28722-customizations-contributions.mdsays navigation entries are "not in this list yet", which is still true; it is not edited.Verification
See the readings above. The PR stays a draft; the seat runs the contract review.
Human Minimum
packages/specholds no runtime logic.pnpm lintpasses (scoped to the changed files locally; the repo-wide run is CI's).main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code