Skip to content

docs(spec): measure what the driver-less merge queue does to the two unsharded ADR-0087 projections - #8359

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8344-shard-unsharded-generated-artifacts
Aug 13, 2026
Merged

docs(spec): measure what the driver-less merge queue does to the two unsharded ADR-0087 projections#8359
os-zhuang merged 1 commit into
mainfrom
claude/issue-8344-shard-unsharded-generated-artifacts

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#8344

The card's step 1 was to measure what the merge queue's driver-less, server-side rebuild actually produces for packages/spec/spec-changes.json and docs/protocol-upgrade-guide.md when two ADR-0087 registrations are in flight — the filer explicitly could not observe this from a PR branch. That measurement exists now, and it changes the direction. No sharding, no CI regenerate-and-diff: the finding and its reproduction are the deliverable.

How the queue's environment was reproduced

.gitattributes routes both files through merge=os-regen, which is registered per clone by scripts/setup-git-hooks.mjs. A fresh clone has no merge.os-regen.driver in its config, and git then falls back to the built-in text merge — which is exactly the queue's situation. So the server-side equivalent is a driver-less clone plus git's own merge:

git clone --shared --no-local . /tmp/driverless
git -C /tmp/driverless config --get merge.os-regen.driver # (absent)
git -C /tmp/driverless merge-tree --write-tree --messages BRANCH_A BRANCH_B

merge-tree --write-tree is merge-ort, the same algorithm git merge runs; the adjacent-id row below was re-confirmed with a real working-tree git merge in that clone, with identical results.

Inputs: the real in-flight case that raised the issue — PR #8325's branch at its pre-merge tip 02694c4, against main at 1b2eb1b, which already carried #8324 and #8327 — plus four synthetic pairs, each a throwaway branch off origin/main registering one semantic entry and running gen:migration-registry + gen:spec-changes + gen:upgrade-guide. The synthetic branches were never pushed and nothing in this PR registers an ADR-0087 entry.

The measurement

two registrations in flight, distance in registry id sort orderdriver-less mergethe un-regenerated result
the real case — #8324 + #8327 on main vs #8325cleanbyte-identical to the regeneration (c985c4a), all three files
ids far apart (aaa-probe-first / zzz-probe-last)cleancheck:spec-changes 0, check:upgrade-guide 0, check:migration-registry 0
exactly one existing entry between themcleancheck:spec-changes 0, check:upgrade-guide 0, check:migration-registry 0
adjacent — nothing between themCONFLICTconflicts in all three files; the PR is ejected
both the first entry of a new majorCONFLICTconflicts in all three files; the PR is ejected

Reverse side of the same test — the same adjacent-id merge, run in a clone with the driver registered:

driverless CONFLICT docs/protocol-upgrade-guide.md + spec-changes.json + registry.ts
driver ON CONFLICT registry.ts (the two projections are deferred)

What it answers

The open question resolves to "never stale-but-clean." Both files are sorted unions, and an ADR-0087 registration is insertion-only. A text merge of two insertion-only diffs therefore either takes both sides — which is the regeneration, byte for byte — or refuses. There is no third outcome. The clean rows are not merely conflict-free: the --check gates were run against the un-regenerated merge result and pass, which is what proves them current.

The residual hazard is real but is a conflict, not silence — and it is narrow: it needs the two in-flight entries to be adjacent in id sort order. One existing entry between them is already enough (a semantic entry renders as 7 lines in spec-changes.json and 3 in the guide, comfortably past git's 3-line context window).

The card's default direction is measured not to work. Sharding these two files buys back zero ejections, because every conflicting case above also conflicts in packages/spec/src/migrations/registry.ts — generated, committed, unsharded, and deliberately outside the driver (NOT_DRIVER_MANAGED) — which every registration touches by construction. #7297 removed the collision at the source; it does not reach the generated file the sources are concatenated into. Sharding the projections while registry.ts still conflicts would be work that changes no outcome.

The fallback direction (a CI regenerate-and-diff) is also not warranted: it would guard against staleness, and staleness is the one outcome measured not to occur.

What landed

Documentation only — three comment/prose surfaces, no behaviour change:

  • packages/spec/src/migrations/entries/README.md — the table, the reproduction, and why sharding does not reach registry.ts, appended to the existing "What this does not fix" section that already names these two files.
  • .gitattributes — the header block states the durability bound this card is about; it now also records what was measured for the two entries that are still single files, and why they stay that way.
  • packages/spec/scripts/build-spec-changes.ts — corrects the stated reason for staying a single file. It said "two PRs append under different majors"; in-flight registrations land in the same current major, so what separates them is distance in id sort order. The conclusion held, the reason did not.

Verification

check:merge-driver PASS (12 paths reconciled, end-to-end merge proof)
check:nul-bytes PASS (7572 files)
check:spec-changes PASS
check:upgrade-guide PASS
check:migration-registry PASS
check:adr-anchors PASS
check:authz-resolver PASS
check:changeset-gate-self-tests PASS
check:cross-package-test-inputs PASS
check:docs-audit-scope PASS
check:doc-formula-expressions PASS
check:i18n PASS
check:release-body PASS
check:spec-parsed-alias PASS
check:type-source-resolution PASS
@objectstack/spec typecheck PASS
eslint (changed file) PASS
@objectstack/spec test 388 files / 10277 tests passed

Gate families re-derived against the actual changed paths with node scripts/pm/dispatch-gates.mjs; that surfaced three the dispatch list did not name (check:cross-package-test-inputs, check:type-source-resolution, scripts/check-dev-prereqs.mjs) and dropped two that no longer match (check:doc-authoring, check:objectui-changeset). All the surfaced ones were run.

scripts/check-dev-prereqs.mjs exits 1 in this container with "the workspace is not built — 12 of 67 packages declare an entry point under dist/ that is not on disk", naming packages this diff does not touch (client-react, studio, embedder-openai, …). It is a container-state precondition, not a code gate; CI builds the workspace. check:i18n and check:doc-formula-expressions failed the same way until the closures were built, then passed.

No changeset: packages/spec is published, but its files array ships only src/**/*.zod.ts from src/, so neither the migrations README nor .gitattributes nor a scripts/ comment reaches a consumer, and nothing about the package's behaviour changes. skip-changeset applied.

Out of scope, left open: #7297 remains open as the sharding this measurement builds on; #8324, #8327 and #8325 are referenced only as the measurement's real inputs.


Generated by Claude Code

…unsharded ADR-0087 projections (#8344)
#8344 asked an open question it could not answer from a PR branch: `.gitattributes`
routes `packages/spec/spec-changes.json` and `docs/protocol-upgrade-guide.md` through
`merge=os-regen`, a LOCAL git driver, while the merge queue rebuilds each PR
server-side where no custom driver runs. Stale-but-clean, conflict, or correct?
Measured, not reasoned: the real in-flight case (#8325's branch against a `main`
already carrying #8324 and #8327) plus four synthetic two-registrations-in-flight
pairs, each merged in a clone with no `merge.os-regen.driver` configured.
Answer: never stale-but-clean. Both files are sorted unions and a registration is
insertion-only, so the queue's text merge either takes both sides — byte-identical to
the regeneration, all three `check:` gates green on the un-regenerated result — or
conflicts. It conflicts only on ADJACENT registry ids; one existing entry between
them already merges clean and current.
So the card's default direction is measured NOT to work: sharding these two files
buys back zero ejections, because every conflicting case also conflicts in
`packages/spec/src/migrations/registry.ts` — generated, committed, unsharded and
NOT_DRIVER_MANAGED — which every registration touches by construction. No sharding,
no CI regenerate-and-diff; the finding and its reproduction are the deliverable.
This also corrects `build-spec-changes.ts`'s stated reason for staying a single file
("two PRs append under different majors"): in-flight registrations land in the same
current major, so distance in id sort order is what separates them. The conclusion
held; the reason did not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WocN37om5bw81JDoEEMA2e
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 8:27am

Request Review

@os-zhuangos-zhuang added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Aug 13, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/permissions/system-context.mdx(via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/apps.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@os-zhuang@claude