Repository navigation
feat(spec, metadata-protocol): the draft list and the commit timeline tell two languages of one email template apart — each entry carries member (#23073) - #23099
Conversation
… carry each bundle entry's member (#23073) ListDraftsResponseSchema's draft entry and ListPackageCommitsResponseSchema's commit item gain an optional member: present on every entry of a type whose identity is a pair (email_template), the canonical member spelled out, and absent for a name-only type. listDrafts and listCommits serve it from the repository header's stored member and the stored revert plan item. Claude-Session: https://claude.ai/code/session_01GZSPSQ5ArPjSJdxxuqwGoZ Co-authored-by: Claude <noreply@anthropic.com>
…23073) Claude-Session: https://claude.ai/code/session_01GZSPSQ5ArPjSJdxxuqwGoZ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 141 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 333e75620cfac37a8de0f7de0c12c1c6b87eb92f && git checkout 333e75620cfac37a8de0f7de0c12c1c6b87eb92f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 55828c5145ce1ca8cb597baf3396614adf9c0583 2f2c6feea3975d5d90e448c2427ba5c19f8117db && git checkout -B drift-repro 55828c5145ce1ca8cb597baf3396614adf9c0583 && git merge --no-ff 2f2c6feea3975d5d90e448c2427ba5c19f8117db
node scripts/docs-audit/affected-docs.mjs --json 55828c5145ce1ca8cb597baf3396614adf9c0583
|
… two lists serve member (#23073) The bundle member selector entry (unreleased) said the protocol's draft list does not include the member; #23073 makes the draft list and the commit timeline serve it. One sentence rewritten; this card's own changeset drops its now-redundant supersession note. Claude-Session: https://claude.ai/code/session_01GZSPSQ5ArPjSJdxxuqwGoZ Co-authored-by: Claude <noreply@anthropic.com>
|
Check Changeset is red here by design: DELIBERATE CORRECTION ·
|
Contract reviewServed-tier: Inputs: card #23073 (body and its 5 comments: triage 6129686435, claim 6131409879, dev report 6132899471, seat ACCEPT 6132937336, patch round 6133092518), the ruling they cite on #22877 (6116165299) and the stage-2 records 6128093700 / 6128724141; PR #23099 (body, its 2 comments, the 13-file list, the net diff Check-runs on the head: 40. 30 ① Derived judgments
② Semver level
③ Boundary flagsDev's deviations (report 6132899471):
Open question (the stale stage-2 sentence; options A / B / C) — answered A by the seat, executed on this head, judged under ②. Out-of-scope finding (the package-batch result arrays The stage-2 records' carried escalations (the runtime dispatcher's item GET without Implemented-by: VERDICT: PASS |
Step 4 of os-regen-merge: the two os-regen mdx files regenerated from the merge of origin/main 55828c5, carrying #23098's and #23075's rows and this card's member rows. Claude-Session: https://claude.ai/code/session_01GZSPSQ5ArPjSJdxxuqwGoZ Co-authored-by: Claude <noreply@anthropic.com>
|
Regen-provenance: 6133279014 ·
|
Fixes #23073
Clause-②: yes (widening: a new declared response key)
Completes ruling 6116165299 (letter A) on #22877 for the two list doors: each bundle member is addressable by name, and now each list entry says which member it is. Stage 2 (PR #23070,
038d4c958) gave the by-name/metadoors thememberselector; the lists still answered two languages of oneemail_templateas two entries with the sametypeandname, so a reader could not tell them apart or address the second one.What changed
Contract (
@objectstack/spec,domain:spec)ListDraftsResponseSchema(GET /meta/_drafts): each draft entry gainsmember: z.string().optional().ListPackageCommitsResponseSchema(GET /packages/:id/commits): each commit item gainsmember: z.string().optional().email_template,(name, locale)); its discriminator value (zh-CN), with the canonical member spelled out (en-US); present on every entry of such a type; absent for a type whose identity is its name alone; it is the by-name doors'memberselector (?member=) for that entry.content/docs/references/api/protocol.mdxandcontent/docs/references/api/package-lifecycle.mdx(check:generatedproved onlycheck:docsstale;--fixregenerated that one and re-checked it green). Noauthorable-surface,api-surfaceorjson-schemaartifact moved: these are response shapes, and no export was added.Serving (
@objectstack/metadata-protocol,domain:engine, cross-domain exception per triage 6129686435)listDrafts: serves the repository header's storedmember(SysMetadataRepository.listDrafts, thebundle_membercolumn throughstoredRowMemberHeader), which it used to strip.listCommits: serves the stored revert-plan item'smember(CommitPlanItem, the memberrevertCommitacts on), which it used to strip.bundleMemberDiscriminator(type, storedMember), the stage-2 function every by-name door already uses to turn a selector into a discriminator value. It is keyed onITEM_KEY_DISCRIMINATORS, the one declaration of which types are discriminated. There is no second list of types, and nothing is read off the body.en-USrather than omitting it. The reasons:memberfor a discriminated type, and nothing for a name-only type".?member=en-USis the same request as no selector (stage 2'sresolveBundleMemberSelector), so the spelled-out value round-trips through every by-name door.protocol.tschanged, plus one comment-only docblock (CommitPlanItem, near L2093) whose sentence "the commit timeline serves the declared four keys only" this PR makes false. The three open PRs editingprotocol.ts(fix(metadata-protocol): a migrate-stored pass records the user who ran it, or nobody — never a label #23085 landed since; feat(metadata-protocol): an unmanaged install copies no protection lock, and names each one it drops #23075, feat(plugin-security)!: no seeder or materializer writes a sys_permission_set row; the boot seeds no row for a declared set (#15204 stage 6b-2) #22900) are hunk-disjoint from these.Premises measured on
origin/main(a22e67b3c)memberwhen it is non-canonical (storedRowMemberHeader,sys-metadata-repository.ts), and the protocol stripped it:drafts.map(({ member: _member, ...header }) => header). Holds.memberwhen it is non-canonical (CommitPlanItem), andlistCommitsstripped it the same way. Holds.packages/**/src(excludingdist):@objectstack/client:meta.listDraftsandpackages.listCommitsreturnListDraftsResponse/ListPackageCommitsResponseimported from@objectstack/spec/api. This is derived, not a re-declaration, so it widens with the schema. Itsreturn-type-precision.test.tspinstoEqualTypeOfagainst those same imported types.@objectstack/rest:GET /meta/_draftsanswersres.json(result), a pass-through. The route ledger names the schema by string.@objectstack/runtime: the/metadata/_draftsand/packages/:id/commitsdispatcher arms pass the protocol answer through (success({ commits })). The route ledger names the schemas by string.@objectstack/cli: zero reads of either list.resolveBundleMemberSelectorandbundleMemberDiscriminatorboth readITEM_KEY_DISCRIMINATORS(@objectstack/metadata-core). This PR reusesbundleMemberDiscriminator.Pins
packages/objectql/src/environment-overlay-email-locale-restart.test.ts(stage 1/2's real-engine harness, realObjectQLand protocol):en-USandzh-CNdrafts of one template, bound to a package, read fromlistDraftsas two entries[email_template, acme.welcome, en-US]and[…, zh-CN]. Each listedmemberis handed back to the by-name door (getMetaItem({ state: 'draft', member })) and reads its own draft's wording. AfterpublishPackageDrafts,listCommitsreads the commit's two items with the same two members, and each item'smemberreads its own published wording throughgetMetaItem.memberkey now assertszh-CNon both the publish commit and its revert. The stage-2 comment "the public draft list keeps its declared header (nomember)" is removed, and its assertion (draftsempty after a discard) is kept.packages/metadata-protocol/src/sys-metadata-repository-14938-list-drafts-updated-at.test.ts§D: twoemail_templatedraft rows (bundle_memberNULL andzh-CN) whose bodies BOTH sayen-US, so the served member is shown to come from the stored column, not the body. They read asen-US/zh-CN. Anobjectdraft's keys are exactly the six declared ones, with nomemberkey. The response parses asListDraftsResponseSchemawith every member preserved.packages/metadata-protocol/src/protocol-14038-list-commits-created-at-iso.test.ts: a stored plan of anemail_templateitem withoutmember, one withmember: 'zh-CN', and anobjectitem servesen-US,zh-CN, and no key. It parses asListPackageCommitsResponseSchemawith every member preserved.packages/spec/src/api/protocol.test.tsandpackage-lifecycle.test.ts: two members of one template plus a name-only entry parse, the parse keepsmember, and the name-only entry has nomemberproperty.Reverse verification (ablation)
One-off, from the committed fix at
fe5c3a5e3, throughscripts/ablation-replace.mjs. In each leg the anchor hit exactly once and the blob changed. Each restore was proven byblob == HEADand an emptygit diff HEAD, and the final tree diff was 0 lines. Predicted direction for each leg: red, exactly the new case.listDraftsservesheader(no member)sys-metadata-repository-14938…:1 failed | 18 passed (19): the #23073 caselistCommitsservesitem(no member)protocol-14038…:1 failed | 4 passed (5): the #23073 caseListDraftsResponseSchemakey renamed away frommemberprotocol.test.ts:1 failed | 184 passed (185): the #23073 casememberpackage-lifecycle.test.ts:1 failed | 19 passed (20): the #23073 caseThe objectql harness leg was not ablated. It reads
@objectstack/metadata-protocolfromdist(KNOWN_UNALIASED_TEST_IMPORTS), so an ablation there needs a rebuild per leg. The serving it exercises is the code that A1 and A2 ablate at source level.Tests and gates (each reading names the tree it was taken on)
Targeted pins and the ablation ran at
fe5c3a5e3. The merge that followed, tof735cffa6, brought onlypackages/rest,packages/runtimeand two changesets, none of which these packages depend on.protocol.test.tsandpackage-lifecycle.test.ts:Test Files 2 passed (2),Tests 205 passed (205). metadata-protocol's two edited files:2 passed,24 passed. The objectql harness:1 passed,20 passed.@objectstack/spec:typecheck(tsc, scripts and the test program) exit 0, atfe5c3a5e3.check:generatedproved onlycheck:docsstale.--fixregenerated it and re-checked it green, and the other 13 artifacts were green.@objectstack/metadata-protocol:typecheckexit 0. Itstscprogram includes both edited test files (--listFiles: 2 of 2). The fulltestran atfe5c3a5e3:Test Files 228 passed | 3 skipped (231),Tests 28207 passed | 19 skipped (28226).@objectstack/objectql:typecheckexit 0,check:test-typecheckledger held, atfe5c3a5e3. The fulltestran atf735cffa6:Test Files 408 passed (408),Tests 7975 passed (7975).@objectstack/client(consumer: its two list methods return the spec types), atf735cffa6:tsc --noEmit(build program): exit 0.return-type-precision.test.ts,unwrap-misfire.pin.test.tsandmeta-automation-descriptors.test.ts:3 passed,27 passed.tsconfig.test.json) is a narrowed reading. 0 errors fall in those three files. All 35 errors elsewhere areTS2307cannot-find-module for unbuilt devDependencydist(@objectstack/runtime,objectql,plugin-hono-server, …). So the whole-program verdict is NOT MEASURED here and is left to CI.@objectstack/rest,@objectstack/runtime: pass-throughs with no re-declaration (see the premises section). No test owed: the public byte surface they touch is the spec type, already read above.@objectstack/cli: zero reads of either list, so nounitrun is owed.eslint --no-inline-config --format jsonover the 9 changed.tsfiles:files_linted=9 errors=0 warnings=0.**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}object; the json lists all 9, so none is ignored.parserOptions.project, stated ineslint.config.mjs), and its plugins are per-file rules. A diff cannot move an untouched file's verdict.f735cffa6:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 114, and each ran with its exit code recorded before any pipe.--ranprinted:114 derived famil(ies) accounted for — 113 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).check-engine-split-ratio: shallow clone. Re-run aftergit fetch --shallow-since=2026-07-07 origin main.check:skill-examples: needs the client and client-reactdist. Re-run after building them.check:lean-entry-closure: needs the objectqldist. Re-run after building it.check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET. It needs every package'sdist, with 59+ unbuilt here, and a whole-monorepo build is CI's.Acceptance notes
publishPackageDrafts'published[]/failed[],discardPackageDrafts'discarded[]/failed[], andrevertCommit'sreverted[]/failed[]carry{ type, name }only. That is the same shape this card closes for the two lists. Carrier: none..changeset/22877-member-selector.md(stage 2, unreleased) says "The protocol's draft list (GET /meta/_drafts) does not include it." That file is outside this claim's file surface, so it is not edited here. This PR's changeset says the earlier sentence is superseded. The one-line amendment belongs to the seat: delete that sentence, or replace it with "The protocol's draft list serves it too."CommitPlanIteminprotocol.tsand thememberheader doc ofSysMetadataRepository.listDrafts.DraftChangesPaneladdressing each entry with?member=) is Studio's draft panel and commit timeline address a bundle member: readmemberoff_drafts/ package commits and pass?member=(the objectui half of objectstack#23073) objectui#12271.Generated by Claude Code