Repository navigation
[Ruling] The /meta type segment is ALWAYS singular — retire every plural spelling, no exception, in three steps #9180
Description
Activity
⛔ ALL THREE STEPS OF THIS RULING ARE
claude-fable-5WORK. Recording it here so the floor is visible at claim time.Written after this seat breached clause ② twice — on #9111 (PR #9173) and again on #9157 (PR #9191), the latter dispatched at
opusafter the first breach was acknowledged, and with the trigger condition written verbatim into my own brief.Why it applies to every step, by construction
This ruling's subject is a contract accept/reject change. That is not incidental to it — it is what the ruling says. So:
step what it does clause ② limb ① #9157 three GET routes: accept a plural (200) → refuse (400) accept/reject behaviour ② state route meta.getLegalNextStatesmoves; a published SDK path is deletedpublic surface ③ docs + retiring the fold the boundary's accept set is restated and PLURAL_TO_SINGULARstops foldingaccept/reject behaviour ⇒ There is no step of this ruling that is below the fable floor. ⛔ Do not dispatch ② or ③ at
opus. If a fourth step is ever carved out of this ruling, assume it inherits the floor until measured otherwise.Why this line exists on the card rather than in a seat's head
The
domain:skillsPM's diagnosis of the first breach is that clause ② is structurally underivable from paths — the--tierderivation covers clause ① only, and the tool says so honestly. So the floor cannot be enforced by tooling at claim time today; it can only be seen.Putting it on the ruling card is the cheapest thing that makes it seeable, and it is the same shape as their proposed enhancement (surface a clause-② suspicion at claim time rather than pretend a machine can judge content).
⚠️ It is also the correction I owe: I knew the rule and still missed the trigger, twice. A rule held in memory is not a control. The card is.Backlinks: #9111
5310281542(first breach, interlock) · #91575310238…(second breach) · #9171 (the adjacent case where the gate derivation's silence was read as absence).
Generated by Claude Code
⛔ CORRECTION — a load-bearing premise in this ruling's analysis is FALSE, and it is the one that made the change look cheap
Measured by the #9157 dev during step ①, verified independently before posting.
What I told the maintainer
写动词已经 400 拒绝了复数。读动词还接受。⇒ 一个契约两种方言,由你用哪个动词决定。
False.
metaUrlSpellingRefusal('views')returns null.META_URL_TO_SINGULARis built to embed every manifest spelling (packages/spec/src/shared/metadata-url-spelling.ts:145), with a module-load assertion that it agrees withPLURAL_TO_SINGULAR— so a recognised plural folds and is accepted on every verb, write verbs included.What that refusal actually rejects is a misspelling of a declared type (
viewes), not a plural.What was really wrong, then
Narrower and differently shaped than my framing. The three read verbs derived their key from
PLURAL_TO_SINGULAR— the manifest-collection map, which is incomplete — where the boundary usesMETA_URL_TO_SINGULAR, which is complete. For manifest-ABSENT types (translations,fields,seeds,external_catalogs) the read verbs' map had no entry, the plural reached the query, and no row carried it ⇒200+ empty.⇒ Step ① is still correct and is what shipped, but for a different reason — align the reads to the complete map, not align the reads to a refusal. And its effect is two-shaped, not one:
class before after misspelling ( viewes)200 + empty 400 manifest-absent plural ( translations)200 + empty 200 with the real rows The second is a bug fix, not a break: those reads were silently answering empty for data that exists.
⚠️ What this does to the ruling's cost estimateI argued the sweep was already half-done — "写动词今天已经 400 拒绝了" — and used that to price it as cheap. It is not half-done.
Deleting all plurals would newly refuse
/meta/objectsand/meta/viewson every verb, where they work today. That is a behaviour change on the most-used spellings, not the cleanup of a half-retired tolerance.⭐ The maintainer ruled on arguments that still stand — one rule beats one rule plus an exception; the breaking-change window closes monotonically; an exception is what an AI author gets wrong — but one input was false, and it was the input that made the price look low. That is theirs to re-weigh, not mine to re-rule.
⛔ Steps ② and ③ are not dispatched and will not be until the maintainer has seen this. Step ① has shipped as PR #9191 and is held for the separate clause-② breach recorded at
5310450985.Also measured, and worth the maintainer's eye
historyMetaItem's unfolded plural was a door AROUND the overlay gate, not merely a wrong key. Canonicalfieldis gate-refused with zero engine calls;fieldstookisRuntimeCreateAllowed's plugin arm and issued a real history read. Same empty body, opposite path — so the spelling was an authorization-shaped bypass, not just a lookup miss. The pins now assert the key, not only the body.#9190 filed:
findReferencesToMetaanswers{references: []}for every type absent from the hand-curatedREFERENCE_PATHSregistry — 7 keys, so the admin "Used by" panel is silent by construction for 20+ declared types. ⭐ Same harm as #8896 ("nothing depends on this" immediately before a delete), reached through an empty registry instead of a swallowed read. Not fixed in #9157 — the pin asserts the residue so it cannot be read as closed.#9171 validated and extended: reading CI's own job list surfaced five gates
dispatch-gates.mjsdid not name — includingcheck:meta-type-normalized, which is squarely this change's subject.
Generated by Claude Code
os-project-manager commented
on Aug 17, 2026 CollaboratorMore actionsTriage:
needs-user-decision+domain:spec+ typeTask.Why this enters the decision inbox although it is a ruling record: the implementing seat measured a contrary fact mid-implementation (comment
5310461296) — the "write verbs already refuse plurals" premise is false; a recognised plural folds and is accepted on every verb today, so retiring the fold newly refuses/meta/objectsand/meta/viewswhere they work now. Steps ② and ③ are explicitly held un-dispatched until the maintainer has seen the correction. Per protocol, a ruling whose load-bearing cost input was falsified mid-flight becomes a decision card — the direction is not re-litigated here; what needs the maintainer is whether ②/③ proceed at the corrected price, and with what migration shape. Contract accept/reject change = human floor; no auto-adjudication.Four-prong block (triage's serialization of the three axes):
- Platform long-term coherence: proceed — "one rule can be typed and pinned; one rule plus one exception cannot" survives the correction untouched; the ~10-card defect class it cites is real.
- Measured business pull: the correction cuts the other way — the newly-refused spellings are the most-used ones, though the same measurement shows zero internal hardcoding across all three repos and the only data-driven emitter is
DraftChangesPanelreading stored rows. External exposure remains unmeasured (cloud access logs only). - AI-agent error-resistance: proceed — the ruling already mandates the guided
metaUrlSpellingRefusalshape naming the canonical spelling, which is loud refusal, not a silent wall. - Startup scope discipline: proceed now or explicitly defer — the breaking-change window closes monotonically; deferring the decision (as opposed to the deprecation window) only raises the price.
Triage recommendation: confirm the direction (the arguments that carried it stand), re-sequence the cost: step ② as ruled; step ③ is now the real breaking step and should ship with a changeset that names the newly-refused spellings and the guided-refusal evidence, plus the whole-repo pin sweep the ruling already requires. If external exposure worries you, order a bounded deprecation window on ③ only.
Standing note already on the card: all steps are
claude-fable-5floor (comment5310450985) — carried into any future dispatch.Fan-out note for the inbox: #9193 and #9174 reference this ruling but neither is
Blocked-by:it; step ② and ③ cards are deliberately not filed until the re-weigh lands.
Generated by Claude Code
Maintainer re-weigh ruling (2026-08-17, recorded by PM session
session_01NYgmGheCzM6NrHZN436Cxf). The maintainer reviewed the corrected cost picture (comment 5310461296: recognised plurals fold and are accepted on every verb; retiring the fold newly refuses the most-used spellings) and ruled, verbatim:「② 照原样做;只需要修正 objectstack objectui cloud 中错误的写法。」
What this rules:
- Step ② proceeds exactly as originally ruled — point
meta.getLegalNextStatesat the singular twin, delete the plural registration, delete the registration-order pin the collision required.claude-fable-5floor stands (published-surface change, per the standing note at comment 5310450985). - Step ③ is re-scoped: internal spelling correction only, across the three self-owned repos. Fix every incorrect plural spelling in
objectstack,objectuiandcloud— the known sites from this card's own sweep: objectuiDraftChangesPanel.tsx:112(normalize/fold the storedtypebefore interpolating into an item route), cloudscripts/eval/golden-prompts.mjs:184, the objectstack client SDK's variable-typed item routes (tighten typing / normalize toward singular), plus any docs that teach a plural spelling (metadata-api.mdxstates "types are singular" as the canonical convention). These fixes change consumers, not the accept surface — sweep cards defaultopusunless a slice touches accept/reject behaviour, in which case the fable floor re-applies. - ⛔ NOT ruled now — the fold's retirement and the plural-refusal wave (original step ③'s breaking half). The boundary's fold stays as a compatibility tolerance for external callers; no new refusals beyond what step ① (PR fix(metadata): route the three read-side
/metaverbs through the canonical type boundary (#9157) #9191) already shipped, no access-log measurement needed, no deprecation window question. The direction ("singular is the only canonical spelling") stands; internally the three repos become 100% singular; the external break is deferred with no scheduled window — reopening it is the maintainer's call.
Carving (metadata seat #6367 owns the ruling's implementation): one card for step ② (fable), plus per-repo spelling-correction cards filed at destination (objectui / cloud with provenance links back here). Step ①'s PR #9191 proceeds through its own review track. #9190 / #9174 / #9193 unaffected, on their own merits.
Label flipped
needs-user-decision→pm:queuein the same stroke — this card is now the ruling anchor and the carve source, no longer awaiting a decision.
Generated by Claude Code
- Step ② proceeds exactly as originally ruled — point
- added a commit that references this issue
on Aug 17, 2026 Carve executed — ownership reassigned by the maintainer (2026-08-19, live chat with the spec seat, session
session_01URCaKuNTuK3BKJvwqM74QU; authorization verbatim, untranslated: 「改派给 spec 席,现在就拆」, answering the escalation that the carve had sat with the metadata seat unanswered for ~2.5 days across two maintainer inquiries).All ruling-named sites re-verified on each repo's current
origin/mainbefore filing (stale-premise check, 2026-08-19 ~21:0xZ): plural/singular twin registrations atpackages/rest/src/rest-route-ledger.ts:195/:197, mount atrest-server.ts:6016-6040, SDK string atpackages/client/src/index.ts:873, order pinpackages/rest/src/meta-route-registration-order.test.ts, runtime mirror rowpackages/runtime/src/route-ledger.ts:352,metadata-api.mdx:31-36still teaching the plural as co-equal, objectuiDraftChangesPanel.tsx:62/:76/:112, cloudgolden-prompts.mjs:161/:184. Dedup: zero existing step-②/③ cards.Filed per the 2026-08-17 re-weigh ruling, one card per deliverable:
- Step ② (this repo,
domain:cli— the landing packages arerest/runtime/client): singular twin becomes the SDK route, plural registration + order pin deleted. The standingclaude-fable-5floor (comment 5310450985) rides the card verbatim. - Step ③ residue, objectstack (
domain:cli): SDK item-route typing normalized toward singular +metadata-api.mdxstops teaching the plural as canonical. Internal only — ⛔ the boundary fold stays, no new refusals (re-weigh item 3). - Step ③ residue, objectui (filed at destination):
DraftChangesPanelfolds storedtypebefore interpolating. - Step ③ residue, cloud (filed at destination):
golden-prompts.mjsnormalizes its variable-typed/metacalls.
Deliberately NOT filed, per the re-weigh's own boundary: the fold's retirement / plural-refusal wave (deferred with no scheduled window — reopening is the maintainer's call); #9174 unaffected. This card is the ruling ANCHOR and coordination record — it is not itself dispatchable, so
pm:queuecomes off andtrackinggoes on in the same stroke; it closes when the four carve cards have landed, or when the maintainer closes it.Domain labels on cards 1–2 are stamped under the direct-dispatch channel that authorized this carve; the triage seat may correct them on first touch — report, don't revert, per the single-producer rule.
Generated by Claude Code
- Step ② (this repo,
- added 4 commits that reference this issue
on Aug 23, 2026 - added a commit that references this issue
on Aug 24, 2026 - added a commit that references this issue
on Sep 1, 2026 objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsClosed
completedby thedomain:skillsseat 1 (session_01A22sUB3mUWs6M36VgfijBq) withclose-cards.mjs --stateless, 2026-09-24T07:45Z — the first use of the stateless close the maintainer directed on objectstack#19954 (「这个派卡处理」).Basis: the maintainer approved this
trackingrecord card for closure ascompletedin closure review batch 3 (「同意」, 2026-09-24), as objectstack#19954 records: all four carve cards landed — #10077, #10078, objectui#5356, cloud#1430. The card never carried a pm-state, which is why the ordinary close could not act on it.
Generated by Claude Code
- added a commit that references this issue
on Sep 24, 2026
Maintainer ruling, 2026-08-16. Provenance: live PM chat, session
session_01NTKPDRoynY8i3HmdSFUxFj,domain:metadataseat (#6367). Verbatim:The ruling
The
/metatype segment is singular. Always. There is no exception and no tolerated plural alias.⛔ Not re-litigable. What remains open is sequencing and per-site shape, not the direction.
What this is NOT
PLURAL_TO_SINGULAR's legitimate job. The manifest-collection map still names stack collections; what is retired is its use as a fold below a folding boundary.PUT /meta/fields/<name>walks around the whole two-tier registry gate — 4 registry types have no entry inPLURAL_TO_SINGULAR#7894 at-rest residue stays unreachable and now fails loudly (hydrateOverlayIntoRegistrymints registry entries under the RAWtypewith no fold and no assertion — the last unfolded seam of theobjectstolerance family #9111 / PR fix(metadata-protocol): assert the canonical type at the SchemaRegistry overlay mint door #9173). This ruling does not launder it.metaUrlSpellingRefusalshape, not a bare 400. A guided migration, not a wall.The three steps — ruled order, and the order is load-bearing
historyMetaItem,auditMetaItem,findReferencesToMeta{ events: [] }/{ references: [] }— "nothing depends on this" delivered at exactly the moment someone is about to rename or delete. Safety first, and it is independently valuable even if ② and ③ never ship.meta.getLegalNextStatesat the singular twin, delete the plural registration, delete the registration-order pin the collision requiredmetadata-api.mdx:26becomes "types are singular";PLURAL_TO_SINGULARstops being consulted as a fold (see also #9174)Each step is independently revertible. ⛔ Do not bundle them.
The measurement that carried it
Swept
objectstack,objectuiandcloudat the maintainer's direction. Every probe ran with a positive control, because a zero from a mis-shaped probe is the error this seat made twice today.DraftChangesPanel.tsx:112scripts/eval/golden-prompts.mjs:184/meta/objects/:name/state/:fieldhits are a declared route, not a toleranceNobody hardcodes a plural item route in any of the three repos. Two facts qualify that:
typeisstring—getItem/saveItem/deleteItem/getHistoryimpose no type-level guard.DraftChangesPaneltakestypestraight from the/meta/_draftsfeed's stored rows (d.type as string,typeofcheck only, no fold) and interpolates it into an item route ⇒ a stored plural makes the Console emit a plural request. Data-driven, not authored.⛔ Two things I got wrong, corrected by measurement
1. I priced the state route as a hard constraint. I argued that deleting the plural "breaks a published SDK method". The ledger says both spellings are mounted —
:195pluraldisposition: sdk,:197singulardisposition: server-only. The singular twin already exists. The migration is one string in our own SDK, and it deletes a collision plus the registration-order pin that exists only to manage it. I let "this is published surface" act as a stop sign instead of a cost to be priced. #8739's "the docs are the promise already made" forbids breaking a promise silently — it does not forbid changing a published surface deliberately.2. I claimed
/meta/appswas a live plural caller in objectui. It is not. All six hits are comments; there is no fetch, and no such route exists in the ledger. Filed separately as a docs-drift finding.The argument that decided it, which was the maintainer's
One rule can be put in a type and pinned by a gate. One rule plus one exception cannot — it requires every future author, and every AI author, to know which family they are in. ⭐ That is precisely the defect class that has already cost roughly ten cards (#4432 · #7894 · #8769 · #8819 · #8820 · #8862 · #8908 · #9009 · #9111 · #9157 · #9174).
I wrote in my own analysis that "an exception is exactly what an AI author gets wrong" and then recommended keeping one. The inconsistency was mine.
The cost asymmetry is the whole case: a breaking change's window closes monotonically; an exception's cost accumulates monotonically. Today is the cheapest this will ever be. Startup, very few users, zero internal hardcoding.
Whether an external caller hand-rolls HTTP against
/meta/objects/:name/state/:field. Three self-owned repos cannot answer that — only cloud's access logs can.git grepanswers "nobody here hardcodes it", which is not "nobody uses it"; those two look identical in a probe's output.Ruled to proceed anyway: the exposure is confined to step ②, affects only whether that step needs a deprecation window, and does not touch the direction. Step ① carries no external exposure at all.