Skip to content

feat(runtime): the per-base customizations read names the package behind each field and flow another package contributes - #28749

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-9q8qlz
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-9q8qlz

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

Requested by Leehom · project thread

Part of #28722

Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — the member is on a REST answer that no published skill teaches, and no metadata author writes it

维护者速读

  • 改了什么:每个已安装基础包的「定制读取」(GET /packages/:id/customizations) 多返回一张 contributions 清单:别的包给这个基础包的对象加的字段,以及别的包挂在这些对象上的流程,每一条都写明是哪个包贡献的。基础包自己的东西和它的 .custom 配对包不在里面。
  • 为什么改:客户在 Studio 打开 HotCRM 时,要能看出哪些字段、流程是 HotCRM 自带的,哪些是别的包加进来的(灰显并写明来源,ADR-0029 D7 / ADR-0146 D3)。以前没有任何读取接口说明来源,前端也不许自己猜。
  • 风险与回滚:只加字段,不改已有清单和数字;Studio 现有的「已定制 N 项」计数不受影响。spec 的应答声明是严格的,所以 ui 里手写的几个测试样例补了这个新成员。回滚 = revert 本 PR。导航项(Wanted 3)不在本 PR,等 The served app navigation names the package that contributed each folded entry (#27356 row 20) #27928 的裁决。
  • 先看:① packages/spec/src/api/package-lifecycle.zod.ts 新增的 contributions 与 counts.contributions(契约);② packages/runtime/src/domains/packages.ts 的 readBaseContributions(排除基础包、配对包和本环境自己写的流程的三处判断);③ packages/runtime/src/domains/packages-customizations.test.ts 新增的 runtime: a served read naming which package contributes to an installed base — extension fields, flows on its objects, navigation entries — so Studio can draw contributed items greyed with their source (ADR-0029 D7, ADR-0146 D3) #28722 一组测试。
  • 两分钟验证:pnpm --filter @objectstack/runtime exec vitest run src/domains/packages-customizations.test.ts,预期 17 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

  • Contract — packages/spec/src/api/package-lifecycle.zod.ts: GetPackageCustomizationsResponseSchema gains contributions, an array of strict entries { kind: 'field' | 'flow', object, name, packageId }, and counts.contributions. Strict like the rest of the answer, and required: the door always serves it.
  • Where it lives: a member of the existing read, not a new route. Measured choice between the N-15 answer and one new read:
    • The N-15 read is already the one per-base read Studio fetches for a base's surface (packageCustomizations-io.ts), with the read gate, the closed query set, the pair-id refusal and the 404 wording this answer also needs. A new GET /packages/:id/contributions would duplicate all four and add a route-ledger row, a dispatcher mount, a typed client method and the dogfood route census pins.
    • The cost of the member is that hand-built answers in tests must carry it. Four fixtures do (one runtime mount test, three ui tests); they are updated here.
    • The existing lists keep their meaning: extensions stays the pair's members only, and Studio's "customized" count (overrides + extensions) is unchanged.
  • Reader — packages/runtime/src/domains/packages.ts#readBaseContributions, called by readPackageCustomizations. Two sources, each the registry that already records the contributor:
    • field: the object registry's extend contributors of each object the base owns (getObjectOwner), the same walk the extensions list makes for the pair. Each contributor's definition is its extension body as declared, so its fields keys are the fields it adds and its packageId is the package that declared them.
    • flow: registry.listItems('flow') (which already drops a disabled package's flows), kept when the start node's config.objectName is a base-owned object and the body is a code package's (isCodeArtifactBody: not the sys_metadata sentinel and not a tenant-authored overlay). The package is the stamp the registration put on the flow.
    • Excluded: the base and its .custom pair (the pair's members stay in extensions). One entry per item and package, so two packages adding a field of one name are two entries. Ordered by object, kind, name and package.
  • Hypotheses in the order, measured:
  • packages/runtime/src/route-ledger.ts: the row's note names the new list (check-route-ledger-census green).
  • Regenerated: content/docs/references/api/package-lifecycle.mdx (gen:docs) and docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md (gen:strictness-ledger), the two artifacts check:generated named stale, and packages/spec/authorable-surface/api.json (the spec build writes it: one line, api/GetPackageCustomizationsResponse:contributions). No export was added or removed (check:api-surface unchanged).
  • .changeset/28722-customizations-contributions.md: minor on @objectstack/spec and @objectstack/runtime. The kind is added: the new member moves the spec's authorable-surface shard (packages/spec/authorable-surface/api.json), and a kind takes at least minor on every package the diff moves (AGENTS.md Post-Task Checklist step 3). The ui fixture edits are test files, which no package ships.

Tests

Test Head Base / ablation
packages/runtime/src/domains/packages-customizations.test.ts (17) 17/17 packages.ts at base 4bf48a8c: 7 red (the 4 new tests and the 3 whole-answer equalities)
ablation: pair exclusion removed 4 red
ablation: base exclusion removed 2 red
ablation: isCodeArtifactBody guard removed (environment flows listed) 1 red
ablation: base-owned trigger object check removed 3 red

The new tests (#28722 describe block) use the card's fixture: a base with its own flow on its object, the pair extending that object and carrying a flow, and a third code package registered through ObjectQL.registerApp (the real manifest path) with an objectExtensions field and two flows, one on the base's object and one on another package's object.

  • The read names the third package for its field and its flow, and names neither the base nor the pair; the answer parses through the declared schema.
  • Two packages adding a field of the same name are two entries, each with its own package.
  • A flow authored in the environment (the sys_metadata sentinel, and a tenant-authored overlay wearing a package id) is not listed.
  • Another base's read lists only what is contributed to its own objects.

Each ablation ran through scripts/ablation-replace.mjs (anchor hit, run, restore), and each restore was proven: blob equal to HEAD and git diff HEAD empty. The runtime test imports packages.ts from src, so no rebuild was needed between legs.

Suites:

Suite Result
pnpm --filter @objectstack/runtime test 504 files, 8203 passed, 19 skipped, exit 0
ui BuilderLanding.customizationCounts-28566, customizationMarks, metadataAreaHost.customizations-28568 3 files, 22/22, exit 0
pnpm --filter @objectstack/runtime typecheck, pnpm --filter @objectstack/spec typecheck exit 0
pnpm build (whole repo) exit 0

Acceptance notes

Verification

All readings at head b4a36765 unless named. Head 7edbf44b changes only the changeset (kind added, minor); check-adr-0087-registration --base origin/main and check-empty-changeset --base origin/main exit 0 there.

pnpm --filter @objectstack/runtime exec vitest run src/domains/packages-customizations.test.ts: 17/17. With packages.ts restored to base 4bf48a8c: 7 red.

Derived gates: 109 derived, 109 run, 0 NOT-MEASURED, 0 UNRUN
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived at b4a36765: the same 109 commands as at 274f09f9, where the loop ran.
  • --ran verdict: "109 derived famil(ies) accounted for — 109 run, 0 NOT-MEASURED", exit 0.
  • First pass at 274f09f9: 105 exit 0, and four that did not:
    • check:doc-authoring exit 1: a real finding. The route-ledger note carried the tracker id. It is removed in b4a36765, and the gate is green there.
    • check-plugin-teardown-shape --self-test exit 3: shallow clone. After fetching its pinned fixture commit, 48 cases pass.
    • check:skill-examples and check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET: no built output). After a whole-repo pnpm build (exit 0), both exit 0.
  • Re-run at b4a36765 after the note edit: check:doc-authoring, check:route-envelope, check:issue-citations, check:nul-bytes, check:published-files, check:type-check-coverage, check:durability-log-level and the four above, all exit 0.
  • node scripts/check-route-ledger-census.mjs: exit 0 (115 rows).
  • pnpm --filter @objectstack/spec check:generated --fix: two artifacts were stale (docs, strictness ledger). Both were regenerated and re-checked green.
  • eslint --no-inline-config over the 5 changed packages/ TS files: exit 0. ui eslint over the 3 changed test files: exit 0.
ui fixtures were needed

BuilderLanding.customizationCounts-28566.test.tsx restored to base: 3 of 6 red against the new schema, because its served answer lacks the now-required contributions. With the fixture carrying it: 6/6 green.


Generated by Claude Code

…d to an installed base

GET /packages/:id/customizations gains a contributions list: every field another
package's object extension adds to one of the base's objects and every flow such
a package ships whose trigger object the base owns, each stamped with its
package. The base and its .custom pair are never listed.

Co-Authored-By: Claude <noreply@anthropic.com>
…answers

Regenerate the spec docs and strictness ledger for the new member, and add the
changeset.

Co-Authored-By: Claude <noreply@anthropic.com>
…ibutions list

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/runtime, @objectstack/spec, touching 10 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/api.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

28 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 8afaf12d7c302072871f7e975e9dff5be5541d90.

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

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/api.json) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 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; 101 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 — 173 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 8afaf12d7c302072871f7e975e9dff5be5541d90 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from ede30e99a53cb36bfc452fb1a7d24430c89a6af5 — the merge of head fb7fafd3d3e4b4c49b15955ce00c5f17323bdcb5 into base 8afaf12d7c302072871f7e975e9dff5be5541d90, 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 ede30e99a53cb36bfc452fb1a7d24430c89a6af5 && git checkout ede30e99a53cb36bfc452fb1a7d24430c89a6af5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8afaf12d7c302072871f7e975e9dff5be5541d90 fb7fafd3d3e4b4c49b15955ce00c5f17323bdcb5 && git checkout -B drift-repro 8afaf12d7c302072871f7e975e9dff5be5541d90 && git merge --no-ff fb7fafd3d3e4b4c49b15955ce00c5f17323bdcb5

node scripts/docs-audit/affected-docs.mjs --json 8afaf12d7c302072871f7e975e9dff5be5541d90

⚠️ 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 8afaf12d7c302072871f7e975e9dff5be5541d90 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ol added, minor

The new member moves the spec's authorable-surface shard, so the kind is added
and spec and runtime take minor.

Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7edbf44b9d7d6cd13699f7c65e03230b1a4f9ec3
Local-runs: none
Scope: packages/spec/src/api/package-lifecycle.zod.ts + packages/runtime/src/domains/packages.ts (readPackageCustomizations, readBaseContributions), packages/runtime/src/domains/packages-customizations.test.ts (the pin), packages/objectql/src/registry.ts (getAllObjects, getObjectContributors, getObjectOwner, registerObject extend path, listItems, getPackagedObjectBase), packages/objectql/src/engine.ts (objectExtensions registration at 9021, stampPackage), packages/metadata-core/src/code-artifact-provenance.ts (isCodeArtifactBody), packages/services/service-automation/src/engine.ts (the start node's config.objectName, 3715 / 6046), ui/packages/app-shell/src/views/studio-design/packageCustomizations-io.ts (first consumer), .changeset/28722-customizations-contributions.md, packages/spec/authorable-surface/api.json, packages/runtime/src/route-ledger.ts

Head 7edbf44b is one commit on b4a36765 (the brief's head) and touches only the changeset; the net diff against origin/main at merge-base 4bf48a8c is 12 files, +254/−17 code plus the 3-line changeset correction. Inputs: card #28722 (body, triage 6560179406, claim 6560204697, os-dev-report 6561138282), PR #28749 body (as patched by the seat on 7edbf44b), file list, git diff 4bf48a8c 7edbf44b, and the check-runs on 7edbf44b.

The remainder of the diff — content/docs/references/api/package-lifecycle.mdx and docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md (regenerated), packages/runtime/src/dispatcher-plugin.package-customizations-mount.test.ts and the three ui fixtures (the required member added to hand-built answers) — is the lane seat's ACCEPT at TIER_DEFAULT; it was read to judge the hits file, not reviewed.

① Derived judgments

  • Public surface, widened. GetPackageCustomizationsResponseSchema gains contributions (array of strict { kind: 'field' | 'flow', object, name, packageId }) and counts.contributions, both REQUIRED, the object staying .strict(). Right: the answer's rule since N-15 (runtime: per-package customization read — for an installed base, its .custom overrides, its object extensions and its switched-off items, served rows only, with counts #28411) is "strict at every level, counts derived from the lists in the same answer"; an optional member would let counts.contributions and contributions disagree by absence. Consequence named right by the changeset: a client parsing through this spec version refuses an older server's answer — in this monorepo the ui reader (packageCustomizations-io.ts) and the runtime handler version together, and every hand-built answer in the tree that the strict parse reaches is updated (1 runtime mount test, 2 runtime handler equalities, 3 ui fixtures; git grep on the head finds no other).
  • Accept-set, unchanged. The route's request side is untouched: GetPackageCustomizationsRequestSchema, the closed query set, the pair-id 400 and the 404 wording are as on main. Right — the PR claims no request change and makes none.
  • Authorable-surface shard packages/spec/authorable-surface/api.json: +1 row api/GetPackageCustomizationsResponse:contributions. Right as generated output; it is the ratchet's record that this is a metadata-protocol added (② below). api-surface/api.json, declaration-map, export-origins, json-schema.manifest are unmoved: no export added or removed — right, the member sits inside an existing export.
  • Strictness ledger 474 → 475: one new .strict() site (the contributions entry object; counts was already strict). Right.
  • Route ledger note for GET /packages/:id/customizations names the fourth list; row, disposition, client method and responseSchema unchanged. Right — no new route, no new mount, no client change (packages/client is not in the diff and needs none: customizations() returns the spec type by re-export).
  • Served semantics implied by the schema descriptions and checked against the reader (④): field = an extend contributor's declared fields keys on a base-OWNED object, from any package but base and pair; flow = a registered flow whose start node config.objectName is base-owned, whose stamp is a code package's, from any package but base and pair; order object, kind, name, package. Each description matches what readBaseContributions does.
  • Nothing stamped at fold time (mergeObjectDefinitions untouched, objectql: an object extension's display patch is carried and applied by mergeObjectDefinitions; a patch naming a missing field or option is refused at install and validate #28565's surface): right — the contributor list already holds the fact, and the write door and every object read are left alone.

② Semver level

Changeset .changeset/28722-customizations-contributions.md at 7edbf44b: @objectstack/spec: minor, @objectstack/runtime: minor, Clause-②: yes (widening), Metadata-protocol: added. PR body (patched): the same three lines, plus Skills-docs: none.

③ Boundary flags

From the os-dev-report 6561138282 (a report on b4a36765; the seat's later message supersedes its declaration):

④ Design judgment

packages/spec/src/api/package-lifecycle.zod.ts (GetPackageCustomizationsResponseSchema), judged against the ruling it implements (ADR-0029 D7 as amended by ADR-0146 D3: a contributed item, when its base is open in Studio, is drawn greyed with its source package named — ADR-0146 line 320: "flows watching its objects, fields extending its objects, ADR-0029 D7 navigation contributions"), the card's Wanted 1–2 and its fixture, and its first consumer (packageCustomizations-io.ts, strict parse; N-19 as the drawer). Reader read in the live tree.

Holds — tried and not broken:

  1. The source is named on the item, not joined by name. Two packages adding acme_sync_id are two entries with their own packageId (pinned by test 2). The schema's object + name + packageId is the full identity of a contributed item; nothing on the client has to invent a source — the card's one hard rule.
  2. Field entries come from the one owner of the fact. registry.getObjectContributors(name) holds every extend contributor with its packageId and its declared body (registerObject, extend branch: the contributor's definition is the body as given, applyProtection stamps provenance; engine.ts 9021 is the manifest door that builds extDef from objectExtensions). The reader walks getAllObjects(baseId) filtered to getObjectOwner(name)?.packageId === baseId — the same walk N-15 makes for the pair — so "base-owned object" is ownership: 'own', never an overlay (getObjectOwner excludes overlays by its own docblock). Objects the base merely extends are not its surface: right under D7.
  3. Flow entries come from the registry's stamp and the spec's own trigger slot. _packageId is registerItem's stamp (engine.ts 4834–4927, connector materializer: an installed package's package-relative providerConfig file ref is read from the HOST's package root, so an install-local app can bind the host's own file (measured: it materialized ready with the host's OpenAPI document) #28534 guards). The trigger object is the start node's config.objectName — the one place the flow spec declares it (flow.zod.ts 1034–1044) and the one place the automation engine reads it (service-automation/src/engine.ts 3715, 6046) and the runtime authoring gate reads it (runtime-authoring-gate.ts 253). No second notion of "a flow on an object" is invented.
  4. Exclusions. Base and pair are excluded by id on both kinds (isContributor); the pair's members stay in extensions and the "customized" count (counts.overrides + counts.extensions) the landing draws is unchanged — the first consumer's numbers do not move (its fixture test proves the fixture edit was the only edit needed).
  5. Disable. listItems('flow') drops a disabled package's flows; the fold (foldObjectExtendersOnto, resolveObject) does NOT drop a disabled package's extend fields. The contributions list follows each served read exactly: a disabled package's fields are listed because the served object still carries them, its flows are not because the served flow list does not. Consistent with what Studio will draw beside; the schema says so for flows.
  6. Strictness and counts. contributions entry .strict(), counts.contributions an int ≥ 0 derived from the list; the whole answer parses through the declared schema in the pinning test and in the mount test. The ui's strict parse (packageCustomizations-io.ts 73) is the consumer the required member is right for.
  7. Ordering object, kind (field before flow), name, package — declared and implemented identically; deterministic for a drawer.
  8. Member of the N-15 read, not a new route. The measurement in the PR body holds against the tree: the read gate, closed query set, pair refusal and 404 wording live in one handler; a new route would duplicate four things and add a ledger row, a mount, a client method and census pins. The cost (hand-built answers must carry the member) is paid in the diff.
  9. Wanted 3 left open without a hole. kind is a closed enum field | flow; a nav kind later is a widening, and the entry shape (object, name, packageId) already fits a navigation entry on an app if The served app navigation names the package that contributed each folded entry (#27356 row 20) #27928 rules for a served read.

Findings:

  • should-fix (④, packages.ts readBaseContributions vs the schema's packageId description): the two kinds apply two different tests of "is this stamp a package's". The flow branch asks isCodeArtifactBody(flow) (drops the sys_metadata sentinel AND any _provenance: 'org' body); the field branch asks only isContributor(packageId) (non-empty, not base, not pair). An extend contributor's definition carries _packageId and _provenance too (registry.ts 543–546 and 3859–3863), and the registry's own packaged-base read already applies isCodeArtifactBody(c.definition) to extend contributors (registry.ts 5545). In the live tree every extend contributor enters through the manifest door (engine.ts 9021), so no witnessed case diverges today; but a tenant-built app (not the pair) whose rehydrated manifest extends a base object and ships a flow on it would have its field listed and its flow not. One rule for both kinds — isCodeArtifactBody(contributor.definition) on the field branch, mirroring the flow branch — keeps "a package's contribution" one predicate. Not a must-fix: no served case contradicts the schema's text today, and the pin (test 3) covers the flow side only.
  • should-fix (④, schema docblock, hits file): the GetPackageCustomizationsResponseSchema comment says "X's own items and the pair's (listed above) are never contributions". The pair's extension members are listed above; the pair's own flows on X's objects are listed nowhere in this answer (N-15's overrides holds the pair's overrides of X's items, not the pair's new flows). The exclusion is the card's ruling and is right; the parenthesis over-claims what is served. One clause — "the pair's extension members are listed above; its other items are the pair's own" — makes the docblock exact. Prose only; no served value changes.
  • should-fix (③/record hygiene, not the tree): the os-dev-report 6561138282 still states Metadata-protocol: none / patch and a wrong deviations reading; the seat's correction supersedes it. A one-line follow-up on the card naming 7edbf44b and the corrected kind keeps the card's ledger from carrying a refuted measurement as the last word.

No must-fix. The schema change holds against ADR-0029 D7 / ADR-0146 D3, the card's fixture is the test's fixture, the reader reads two registries that already record the contributor, and the first consumer parses the answer it is given.

Implemented-by: claude/project-thread-9q8qlz
Reviewed-by: session_01FEkQ5kE5BrPUPNMMyv6oCH

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

席位核对 · ACCEPT · PR #28749 · 2026-11-01T17:58Z

Head-sha: 7edbf44b9d7d6cd13699f7c65e03230b1a4f9ec3

维护者速读(席位终稿)

核对六项

  1. 形态:draft → main;Part of #28722,合并不关卡(第 3 项留给 The served app navigation names the package that contributed each folded entry (#27356 row 20) #27928),正确。文件 12 个,在认领 6560204697 申报面内(spec、runtime、ui 夹具、生成文件与 changeset)。
  2. 前提实测:dev 按卡片夹具(基础包 + 配对包 + 第三个包扩展一个字段、加一个流程)先红后绿,读取点名第三个包、不点名基础包与配对包;消融后红,恢复。
  3. 门禁:本 head check-runs 59 success、9 skipped(路径未命中)、0 待跑;唯一 failure 是 17:12Z 读旧正文的 Check Changeset,17:13Z 重读新正文 success。Test UI (8/16) 首跑红于 ui/packages/fields 的 FilterConditionField.betweenBlankBound-9914(期望数字 5、收到字符串 "5"),本 PR 未碰 ui/,判为无关抖动;重跑失败 job 一次(run 41330100857 第 2 次)全绿。dev 本地 109/109 派生门禁 exit 0,0 NOT-MEASURED;ui app-shell typecheck 交 CI。
  4. 接受的偏差:dev 原报 Metadata-protocol: none / patch,被 Check Changeset 以 authorable-surface 分片移动拒绝;已改为 added、spec 与 runtime minor(7edbf44b),dev 已在卡上发更正 6561290103。复核两条 should-fix 记为后续,不挡落地:(1)readBaseContributions 字段分支应与流程分支同用 isCodeArtifactBody 判定(树内无实际分歧);(2)spec 注释"配对包的…从不计为 contributions"措辞过宽,仅文字。
  5. 范围外去向:第 3 项(导航)等 The served app navigation names the package that contributed each folded entry (#27356 row 20) #27928;ui 车道注意:N-19 进行中的手写应答在本 PR 合入后须带新成员。Skills-docs: none(已核:无已发布 skill 讲授此应答)。
  6. 落地条款:Clause-②: yes (widening) + packages/spec 路径 ⇒ CONTRACT_REVIEW_TIER 完整复核 PASS(6561256710,Head-sha 与现 head 一致)。Metadata-protocol: added ⇒ spec、runtime minor,与 changeset 一致(Check Changeset 本 head 最新一次 success;17:12Z 那次 failure 读的是旧正文)。check-governed-merges --pr 28749:NOT governed,271 行。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 18:00
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 18:00
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8952f7ef8a023b69b595ed3f716c21490420756a
Local-runs: none
Scope: packages/spec/src/api/package-lifecycle.zod.ts + packages/spec/authorable-surface/api.json, docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md (the two regenerated files), and the merge commit bad518ce read for what it resolved

Re-confirm of the record posted as comment 6561256710 (verdict PASS on head 7edbf44b9d7d6cd13699f7c65e03230b1a4f9ec3). The head moved by two commits, no force-push: bad518ce merges origin/main 706ac52d into 7edbf44b (wip-28722-merge), and 8952f7ef regenerates the strictness ledger and the authorable-surface shard on the merged tree. origin/main is 706ac52d and the merge-base is now 706ac52d.

① The net diff, read, not taken on trust

  • git diff --stat origin/main 8952f7ef: the same 12 files, +254/−17.
  • Substance: git diff 4bf48a8c 7edbf44b and git diff origin/main 8952f7ef, both with the two generated files excluded, are byte-identical after dropping index lines (the record's hits file, the runtime reader, the pin, the route-ledger note, the four fixtures, the docs page and the changeset are unchanged). Nothing of what 6561256710 judged in ①–④ has moved; its findings (no must-fix; three should-fixes) stand as written.
  • .changeset/28722-customizations-contributions.md is still @objectstack/spec: minor, @objectstack/runtime: minor, Clause-②: yes (widening), Metadata-protocol: added (② unchanged).

② The two regenerated files: main's entries kept, only this PR's additions on top

  • packages/spec/authorable-surface/api.json: git diff origin/main 8952f7ef on it is exactly one added row, api/GetPackageCustomizationsResponse:contributions (1934 api/ keys on main, 1935 on the head). The merge commit bad518ce had resolved the conflict to the PR side (vs main: 1 insertion, 12 deletions — 11 of main's newer rows dropped); 8952f7ef regenerated with the generator and restored all 11, so the head carries main's shard plus this PR's one key. Right, and by the generator, not by hand.
  • docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md: main is api/ | 477 (up from 474 at the old base — three strict sites landed on main since); the head is 478, which is main plus this PR's one new .strict() site (the contributions entry object). Right.
  • Nothing else in the diff vs main beyond the 12 files, so the merge brought in no stray resolution elsewhere.

③ Check-runs on 8952f7ef

Read at the time of this re-confirm, no red. success: Check Changeset (job 124937265140 — the gate that was red on an earlier head against the stale PR body is green here on the corrected declaration), Plan Test Core shards, Governed Surface Queue Guard, PR-body guards, Spec property liveness, Check PR Size, Check Documentation Links, Flag docs affected by code changes, Dogfood Regression Gate, Auto Label, filter. Still in_progress: Test Core (1/8) through (8/8), Build Core, Build Docs, Build UI Deps, Lint · * (all eight), Type Check · workspace, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Live E2E UI. Their conclusions are the gate verdicts; the lane seat reads them before landing.

④ Design judgment

Unchanged from 6561256710: the hits file and every file it cites are byte-identical in the net diff. No must-fix. Should-fixes carried forward: one predicate for "a package's" stamp on both kinds in readBaseContributions; the schema docblock's "(listed above)" over-claim about the pair's flows; a card follow-up superseding the os-dev-report's stale none/patch reading.

Implemented-by: claude/project-thread-9q8qlz
Reviewed-by: session_01FEkQ5kE5BrPUPNMMyv6oCH

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

席位核对 · ACCEPT(head 续认)· PR #28749 · 2026-11-01T18:28Z

Head-sha: 8952f7ef8a023b69b595ed3f716c21490420756a

续认 6561730559。head 从 7edbf44b 前进到 8952f7ef,没有 force-push:

  • bad518ce 合入 origin/main 706ac52d,解决生成账本 docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md 的冲突。
  • 8952f7ef 用生成器重生成该账本(main 477 + 本 PR 1 = 478)和 packages/spec/authorable-surface/api.json(main + 本 PR 1 行)。

对 main 的净差异仍是同样 12 个文件、+254/−17;两个生成文件以外的部分与原 head 逐字节相同。CONTRACT_REVIEW_TIER head 续认 PASS(6562066799)。auto-merge 仍开着,本 head CI 全绿后进队列。其余六项同 6561730559。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 1, 2026
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 41338791986 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test UI (9/16) — 失败步骤: Run the ui tests (shard 9/16)

    × marks the served override "customized" and the pair’s own item "added", and nothing else 8423ms
    × "only customizations" lists exactly the customized and the added item 8258ms
    × an added item opens writable: its edit saves at the pair, declaring no override 8256ms
    × New on the read-only base asks the ensure door, writes into the pair it names, and links no nav entry 8323ms
    × marks the pair’s object "added"; the base’s objects carry no mark; opening it names the pair 8282ms
    × the rails ask no list of another package, and no row names one 8272ms
    FAIL   dom  packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx > the Interfaces rail lists a base and its pair as one list (objectstack#28572) > marks the served 
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    FAIL   dom  packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx > the Interfaces rail lists a base and its pair as one list (objectstack#28572) > "only customizati
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    FAIL   dom  packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx > the Interfaces rail lists a base and its pair as one list (objectstack#28572) > an added item ope
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    FAIL   dom  packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx > the Interfaces rail lists a base and its pair as one list (objectstack#28572) > New on the read-o
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    FAIL   dom  packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx > nothing a third package contributes is drawn (objectstack#28722) > the rails ask no list of anoth
      ↳ 失败原因: AssertionError: expected null to be 'added' // Object.is equality
    FAIL   dom  packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx > the Data rail lists the pair’s objects with the base’s (objectstack#28572) > marks the pair’s obj
      ↳ 失败原因: AssertionError: expected null to be 'added' // Object.is equality
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 3 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

…stomization answers

Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fb7fafd3d3e4b4c49b15955ce00c5f17323bdcb5
Local-runs: none
Scope: packages/spec/src/api/package-lifecycle.zod.ts + packages/spec/authorable-surface/api.json, docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md (the generated files), ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.oneSurface-28572.test.tsx (the fixture the queue run refused), and the merge commit 2074ba21 read for what it brought in

Second re-confirm — of the record posted as comment 6562066799 (re-confirm of 6561256710, verdict PASS, head 8952f7ef8a023b69b595ed3f716c21490420756a). The merge queue pulled the PR out (run 41338791986, Test UI 9/16): StudioDesignSurface.oneSurface-28572.test.tsx, landed on main by #28747 after this PR's fixtures were written, hand-builds two customization answers without contributions / counts.contributions, and the strict GetPackageCustomizationsResponseSchema refuses them. The head moved by two commits, no force-push: 2074ba21 merges origin/main 8afaf12d into 8952f7ef; fb7fafd3 adds the member to those two fixtures. origin/main and the merge-base are both 8afaf12d.

① The delta vs the last re-confirm, read

  • git diff --stat origin/main fb7fafd3: 13 files, +257/−19 — the 12 files of 6562066799 plus StudioDesignSurface.oneSurface-28572.test.tsx (+3/−2).
  • Substance: git diff origin/main fb7fafd3 with the two generated files and the new fixture excluded is byte-identical (after dropping index lines) to the same diff at 8952f7ef against its main. The hits file, the runtime reader, the pin, the route-ledger note, the docs page, the changeset (minor/minor, Clause-②: yes (widening), Metadata-protocol: added) and the earlier four fixtures are unchanged.
  • The fixture commit vs main is exactly: contributions: [] and counts.contributions: 0 on the full read (counts: { overrides: 1, extensions: 1, switchedOff: 0 } → + contributions: 0) and on EMPTY_READ. Nothing else in the file moves; it is the same edit the PR made to its three sibling ui fixtures, for the same reason (the required strict member, ① of 6561256710).
  • The merge 2074ba21 vs main touches only this PR's own files (its generated-file footprint vs main is the docs page, the ledger and the shard, each at this PR's values); nothing of feat(studio): a read-only base and its customization pair as one surface #28747 or the rest of main was altered by the merge.

② Generated files on the new merged tree

  • packages/spec/authorable-surface/api.json vs main: exactly one added row, api/GetPackageCustomizationsResponse:contributions (1934 api/ keys on main, 1935 on the head). main moved again between the two re-confirms and the shard still reads main plus this one row.
  • docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md: main is api/ | 477, head is 478 — main plus this PR's one .strict() site. No other generated artifact (api-surface, declaration-map, export-origins, json-schema.manifest) differs from main.

③ Every hand-written customizations answer on main

git grep -lE "switchedOff: \[|counts: \{ ?overrides" origin/main -- ui/ packages/ finds seven files: the runtime handler packages.ts (the server, not a fixture) and six test files — dispatcher-plugin.package-customizations-mount.test.ts, packages-customizations.test.ts, metadataAreaHost.customizations-28568.test.tsx, BuilderLanding.customizationCounts-28566.test.tsx, customizationMarks.test.ts, and StudioDesignSurface.oneSurface-28572.test.tsx. On main none carries contributions (as expected: the member is this PR's). On fb7fafd3 all six fixtures carry it (2, 12, 3, 2, 3 and 3 occurrences respectively). No hand-written answer on main is left without the member; the one #28747 added is the one fb7fafd3 fixes. The queue failure was the race between #28747 landing and this PR's fixture sweep, not a defect in the contract; the required-member consequence named in ① of 6561256710 is exactly this, and the cost is paid.

④ Check-runs on fb7fafd3

Read at the time of this re-confirm, no red. success: Governed Surface Queue Guard, PR-body guards, Spec property liveness, Check PR Size, Check Documentation Links, Flag docs affected by code changes, Dogfood Regression Gate, Auto Label, filter. Still in_progress: Check Changeset, Plan Test Core shards (so no Test Core shard and no Test UI shard has reported yet — Test UI 9/16 is the one the queue run reddened), Build Core, Build Docs, Build UI Deps, Lint · * (all eight), Type Check · workspace, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Live E2E UI. Their conclusions are the gate verdicts; the lane seat reads them — Test UI above all — before re-arming the queue.

Design judgment

Unchanged from 6561256710: the hits file and every file it cites are byte-identical in the net diff. No must-fix. Should-fixes carried forward: one predicate for "a package's" stamp on both kinds in readBaseContributions; the schema docblock's "(listed above)" over-claim about the pair's flows; a card follow-up superseding the os-dev-report's stale none/patch reading.

Implemented-by: claude/project-thread-9q8qlz
Reviewed-by: session_01FEkQ5kE5BrPUPNMMyv6oCH

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

席位核对 · ACCEPT(第二次 head 续认)· PR #28749 · 2026-11-01T19:48Z

Head-sha: fb7fafd3d3e4b4c49b15955ce00c5f17323bdcb5

本条续认 6562075578。

为何出队:队列构建 41338791986 的 Test UI (9/16) 红了。#28747(72533449)在本 PR 之后合入 main,它的 StudioDesignSurface.oneSurface-28572.test.tsx 手写的 customizations 应答缺 contributions,被本 PR 新加的严格应答 schema 拒绝。这是两个 PR 落地时间交错造成的,不是契约缺陷。

修法:head 前进两个提交,没有 force-push。

  • 2074ba21:合入 origin/main 8afaf12d,无冲突,生成物都是最新。
  • fb7fafd3:给该文件的两处夹具补上 contributions: [] 和 counts.contributions: 0,其余不动。

核对:

  • 对 main 的净差异现为 13 个文件、+257/−19。
  • 复核用 git grep 查过 main 上全部手写应答,没有其他遗漏。
  • CONTRACT_REVIEW_TIER 第二次续认 PASS(6562785424)。
  • 本 head check-runs 58 success、7 skipped、0 失败;Test UI 16 个分片全绿。

落地:重新开启 auto-merge 进队列。其余各项同 6561730559。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 8196881 Nov 1, 2026
65 checks passed
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

Development

Successfully merging this pull request may close these issues.

1 participant