Repository navigation
Description
Maintainer ruling, 2026-08-16. Provenance: live PM chat, session session_01NTKPDRoynY8i3HmdSFUxFj, domain:metadata seat (#6367). Verbatim:
「这是创业项目,目前用户很少,三个自有仓库里没人硬编码, 一刀切「删除所有复数」是不是从长远看损失更小」
「把这个裁决(一刀切 + 三步)立成卡、并起 ① 的实施卡」
The ruling
The /meta type 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
- ⛔ Not a change to
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. - ⛔ Not a claim that any stored plural row becomes reachable. Pre-meta-plural-url-bypass:
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. - ⛔ Not a licence to blank a diagnostic or soften a refusal. Every refusal added under this ruling must name the canonical spelling — the
metaUrlSpellingRefusalshape, not a bare 400. A guided migration, not a wall.
The three steps — ruled order, and the order is load-bearing
| # | step | card | why this position |
|---|---|---|---|
| ① | Read verbs refuse plurals, like the write verbs already do — historyMetaItem, auditMetaItem, findReferencesToMeta |
#9157 | ⭐ The only step with a demonstrated harm. Today a plural read answers { 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. |
| ② | State route: point meta.getLegalNextStates at the singular twin, delete the plural registration, delete the registration-order pin the collision required |
to file | Depends on nothing; deferrable; the only step with any external-break exposure |
| ③ | Docs + the fold's retirement: metadata-api.mdx:26 becomes "types are singular"; PLURAL_TO_SINGULAR stops being consulted as a fold (see also #9174) |
to file | Last, because it should describe a world that already exists |
Each step is independently revertible. ⛔ Do not bundle them.
The measurement that carried it
Swept objectstack, objectui and cloud at 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.
| repo | literal plural item routes | variable-typed item routes | control |
|---|---|---|---|
| objectui | 0 | 1 — DraftChangesPanel.tsx:112 |
29 files with singular item routes ✅ |
| cloud | 0 | 1 — scripts/eval/golden-prompts.mjs:184 |
2 ✅ |
| objectstack | 0 generic; the /meta/objects/:name/state/:field hits are a declared route, not a tolerance |
4 — the client SDK | 41 ✅ |
Nobody hardcodes a plural item route in any of the three repos. Two facts qualify that:
- The SDK's
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 — :195 plural disposition: sdk, :197 singular disposition: 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/apps was 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.
⚠️ The one thing still unmeasured, and why it did not block
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 grep answers "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.
Activity
os-zhuang commentedon Aug 17, 2026
⛔ 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:
meta.getLegalNextStatesmoves; a published SDK path is deletedPLURAL_TO_SINGULARstops folding⇒ 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).
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
os-zhuang commentedon Aug 17, 2026
⛔ 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
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:
viewes)translations)The second is a bug fix, not a break: those reads were silently answering empty for data that exists.
I 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 commentedon Aug 17, 2026
Triage:
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):
DraftChangesPanelreading stored rows. External exposure remains unmeasured (cloud access logs only).metaUrlSpellingRefusalshape naming the canonical spelling, which is loud refusal, not a silent wall.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
os-zhuang commentedon Aug 17, 2026
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:What this rules:
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).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./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
fix(metadata): route the three read-side `/meta` verbs through the ca…
os-zhuang commentedon Aug 20, 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:
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.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).DraftChangesPanelfolds storedtypebefore interpolating.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
fix(rest,client): the /meta FSM state route is singular — retire the …
docs(api): teach the singular `/meta/:type` spelling as the canonical…
docs(runtime,rest): record the dispatcher /meta plural tolerance as a…
docs(skills,rest): teach the canonical `/meta/object/:name/state/:fie…
feat(scripts): corpus-wide route-spelling detector for published prose (
feat(scripts): corpus-wide route-spelling detector for published pros…
objectstack-fleet commentedon Sep 24, 2026
Closed
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
feat(pm): close-cards --stateless acts on a card with no pm-state, an…