You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec: maxLength is authorable on every field type and validated as no more than a number — maxLength: 0 and maxLength: 12.5 parse cleanly #11566
Found while implementing #11431. Filed unassigned. Cannot be fixed from the driver lane — packages/spec is a different seat.
What
packages/spec/src/data/field.zod.ts declares:
maxLength: z.number().optional().describe("Max character length"),
on the base field schema. Two consequences:
No shape validation..number() alone accepts 0, -5, 12.5 and Infinity. None of those is a character length, and each parses without complaint.
Authorable on every type. Being on the base schema, maxLength is legal on boolean, date, lookup, autonumber, formula — types where it describes nothing that is stored.
Evidence that this is not theoretical
schema-drift.ts read a malformed bound as authoritative and planned DDL no server accepts — maxLength: 0 took the narrowing arm (0 > 255 is false) and asked for varchar(0), at severity error / category destructive, i.e. as work os migrate apply --allow-destructive should go do. Fixed on the driver side in #11431 by refusing anything that is not a positive integer, but that is a consumer defending itself against a producer that should not have emitted the value (PD #12).
The authoring forms already disagree about where the key applies, which suggests the base-schema placement was never deliberate:
field.form.ts shows maxLength for text / textarea / email only
object.form.ts shows it for text,textarea,email,url,phone,password,markdown,html,richtext
Neither list matches the other, and neither matches the base schema's "every type".
The write-time validator (packages/objectql/src/validation/record-validator.ts) enforces maxLength for text | textarea | email | url | phone | password | markdown | html | richtext | code — a third list, and the closest thing to a real answer for which types the key means something.
Applicability. Whether maxLength should be refused on types that do not store a bounded string is the larger question, and touches ADR-0078 declared=enforced. The validator's ten-type list is the natural candidate for the allowed set.
Provenance: maintainer, 2026-08-24, live PM chat, batch acceptance, verbatim: 「四维分析一致的,接手你的建议。」 The four-facet block above is aligned, so the recommendation is adopted.
Ruled: shape tightens to the house pattern (z.number().int().min(1), per the precision precedent one field below), and applicability converges to the write-time validator's ten-type list (text | textarea | email | url | phone | password | markdown | html | richtext | code) — maxLength refused on types that store no bounded string. The two authoring-form lists (field.form.ts, object.form.ts) are aligned to the same ten in the same stroke. Clause-②: yes (narrows the accept set) — contract-review tier applies at dispatch.
State: needs-user-decision → pm:queue (spec lane). Downstream: #11431's Blocked-by: #11566 unlocks when this closes; the engine seat should re-verify that card's surface on the post-landing ref per the unlock discipline.
Claim: PM loop round R5 wave 1
Session: session_01K93P8PbH7kVjsAXkqzH1zY
Branch: claude/issue-11566-maxlength-shape-applicability
Worktree: objectstack-issue-11566
Domain: domain:spec
File surface: packages/spec/src/data/field.zod.ts (base-schema maxLength at :856 + the length/size alias map at :738), packages/spec/src/data/field.form.ts, packages/spec/src/data/object.form.ts, new rejection pins + changeset (stop on breach; explain in the report). ⛔ packages/spec/src/migrations/registry.ts is HELD by the #10724/#11169 family dispatch this wave — if this tightening's conventions force a touch there, stop and report rather than editing it.
Container & model: M, mode:subagent, model: claude-fable-5 — dispatch-gates --tier packages/spec/src/data/field.zod.ts (this dispatch): "Clause ② SUSPECT surface — packages/spec/src/** — the contract surface … the normal landing zone of a clause-② card"; content reading is the verdict: the 2026-08-24 ruling narrows the accept set ⇒ fable-mandatory.
Clause-②: yes
Serial constraints cleared: same-file predecessor PR #11903 (deleteBehavior, the .overwrite() region) merged 2026-08-25 — same-day churn clause applies, work from current merged main. H17 hit named per protocol: hold #8345 declares field.zod.ts as a Restart-touch trigger file — notification only, no rider work taken from it. In-flight overlap: none (lane had 0 dispatched cards at fire); lane sibling #11408 (editMode, may touch object.form.ts) is deliberately held to wave 2 behind this card — cross-wave serial, not a fold (different defect shape, fails family gate ①). Cross-lane: #11739 (services, feat(spec,auth)) touches spec on the auth-posture surface, disjoint from this file set.
{
"issue": 11566,
"status": "done",
"branch": "claude/issue-11566-maxlength-shape-applicability",
"pr": "https://github.com/objectstack-ai/objectstack/pull/11989",
"premise_still_valid": true,
"summary": "Ruled tightening implemented on post-#11903 main (base ac59744): maxLength is z.number().int().min(1); a new superRefine refuses it outside the write-time validator's ten types, exported as BOUNDED_STRING_FIELD_TYPES; both authoring-form maxLength rows converge on the same ten; 15 new pins assert specific zod issue code+path+message. Alias-map assumption measured false-but-safe: strictObject aliases are unknown-key rejection guidance only — length:/size: never flow a value into maxLength, so no bypass exists. Repo sweep found zero spec-parsed authors of now-rejected values; driver-sql's #11431 consumer-defense fixtures bypass FieldSchema.parse and are preserved verbatim.",
"tests": "All at head e36b89a (re-affirmed after the lint census commit; initial readings at 9ca3a81, tree byte-identical for spec surfaces). spec field.test.ts: 'Tests 184 passed (184)'. Full spec suite: 'Test Files 424 passed (424)' / 'Tests 11288 passed (11288)'; spec typecheck exit 0 incl 'check:test-typecheck: OK'. Reverse verification from committed state (git restore --source=origin/main, mutation grep-confirmed: BOUNDED_STRING_FIELD_TYPES count 0; no dist in the loop — spec tests import ./field.zod source, so no rebuild leg applies; restore leg grep count 4 + git status clean vs HEAD): exactly the predicted 12 reds ('Failed Tests 12' = 3 shape + 9 applicability pins), direction red-on-revert as expected. Consumers: objectql 'Test Files 232 passed (232)' / 'Tests 4113 passed (4113)'; driver-sql 'Test Files 128 passed | 8 skipped (136)' / 'Tests 1981 passed | 114 skipped (2095)' (live PG/MySQL matrix skips, no DB); lint 'Test Files 81 passed (81)' / 'Tests 2294 passed (2294)' after the census re-derivation.",
"gates": [
"check:generated --fix: proved exactly 3 stale (api-surface, export-origins, docs) and regenerated them; 'check:authorable-surface ... ✓', 'check:skill-refs ... ✓' (no skills/** touch)",
"check-adr-0087-registration: '✓ this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).'",
"check-changeset-no-major: '✓ This diff introduces no major bump.'",
"check-empty-changeset: '✓ No empty-frontmatter changeset introduced by this diff'",
"check:i18n: 'check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys).' (after building the CLI; first run refused loudly with nothing measured)",
"check:cross-package-test-inputs: 'OK: 16 package(s) read outside themselves, all declared'",
"check:where-matcher / check:query-options-erasure / check:engine-double-contract: 'baseline key set verified against ac59744: no files added.'",
"check:spec-parsed-alias: 'ADR-0122 type-alias convention ... OK'",
"check-nul-bytes: 'OK (scanned 6651 text file(s) ... no raw ASCII control bytes).'",
"dispatch-gates re-derived from actual diff, derivation stamped 'at commit 9ca3a81' then re-run at e36b89a (37 matched families placed); declared narrowing: repo-wide pnpm lint and check:type-check-debt --re-measure left to CI — spec's own typecheck+test-typecheck accounting ran green locally",
"CI at 9ca3a81: Test Core red on lint's RHS-rule corpus census (expected 47, got 44) — fixed in e36b89a by re-deriving the census (the pin's own anchor note places in-list literals outside the rule); full lint suite green locally at that head"
],
"deviations": [
"ADR-0087 ledger entry deferred to #11950: registry.ts is HELD by the #10724/#11169 family dispatch per the card constraint; the changeset carries no **BREAKING** marker (matching the #9689 accept-set-narrowing precedent), so the disposition gate does not fire — confirmed by its own verdict line above. The convention did NOT mechanically force a registry touch, so I did not stop-and-block; the debt is tracked openly instead of landing silently. PM may re-judge this fork.",
"record-validator NOT rewired to import BOUNDED_STRING_FIELD_TYPES (outside the claimed file surface; membership identical and pinned on both sides) — possible follow-up refactor, noted in PR body.",
"field.form minLength row left at 3 types / object.form minLength at 9 (ruled scope covered maxLength only); the minLength defect pair is filed as #11949.",
"Report fields are the union of the dispatch's machine-checkable list and the standard template (the two specs differ; conflict noted per protocol).",
"Second commit e36b89a (lint census) landed after the PR opened, in response to the PM's CI notification — same card scope, no widening."
],
"files_changed": [
".changeset/maxlength-bounded-string-only.md",
"content/docs/references/data/field.mdx",
"packages/spec/api-surface/data.json",
"packages/spec/export-origins/data.json",
"packages/spec/src/data/field.form.ts",
"packages/spec/src/data/field.test.ts",
"packages/spec/src/data/field.zod.ts",
"packages/spec/src/data/object.form.ts",
"packages/lint/src/validate-predicate-path-refs.test.ts"
],
"open_questions": [
{
"question": "Should the #11950 ledger entry land as soon as the registry hold lifts (this wave) or ride the next spec seat round?",
"options": [
"A: next round, PM-scheduled",
"B: rider on the #10724/#11169 family PR"
],
"recommendation": "A — a rider on the family PR widens their scope; the entry is inert until protocol major 18 per step18's own text, so there is no urgency beyond tracking."
}
],
"out_of_scope_findings": [
"filed as #11949: minLength carries the identical pre-#11566 defect pair (shape + base-schema applicability + stale form lists); lower bound (0 vs 1) is a judgment call",
"filed as #11950: deferred ADR-0087 ledger entry for this narrowing (registry.ts held this wave)"
]
}
ACCEPT + Contract review: PASS — PR #11989 at head e36b89a (spec seat R5, reviewer of record session_01K93P8PbH7kVjsAXkqzH1zY).
Checklist verdict, verified against GitHub and my own reads:
Shape: draft, base main, first line Fixes #11566 (full ruled scope delivered). Changed files (9) = claim surface + the PM-directed patch-round addition (packages/lint/src/validate-predicate-path-refs.test.ts — deviation explained in the report, directed by this seat's CI-failure round). Changeset on the published package; no content/docs/releases/; no governed surface.
Ruling implemented literally: z.number().int().min(1) + BOUNDED_STRING_FIELD_TYPES ten-type superRefine refusal + both form rows converged to the ten. 15 pins assert specific zod issue code+path+message — real rejection tests, not bare toThrow.
Mechanism assumptions honestly measured: the alias-map hazard I dispatched was falsified-but-safe (strictObject aliases feed unknown-key guidance only; no value path exists) — reported, not silently absorbed. deleteBehavior's .overwrite() machinery correctly identified as default-materialization-only; superRefine is the right house pattern and absence-stays-absence is pinned.
Reverse verification: disk-confirmed mutation → exactly the predicted 12 reds, restore leg proven; no dist in the loop (source-relative import), correctly stating why no rebuild leg applies.
Contract-review tier: this seat's serving model re-verified this shift (get_session → claude-fable-5 = CONTRACT_REVIEW_TIER); standing authorization on seat post [PM seat] domain:spec — ⏳ vacant #6017 (2026-08-24, 「你自己就是fabke,为什么还要等 review」).
Disposition: enqueue follows the standing rule — every check on the PR green at head e36b89a; the seat flips ready + queues on the all-green reading and tracks to MERGED. Card stays pm:dispatched until landing.
Activity
os-zhuang commentedon Aug 24, 2026
分诊(Routine 席,小时轮):入决策箱,
needs-user-decision+domain:spec,type Bug(校验缺口本体是缺陷,但两个修法都收窄接受集 = 契约变化,人工地板)。下游注记:#11431(engine,pm:blocked)的 Blocked-by 指向本卡,裁决落地即解锁它。标准四棱块:os-decision-facets
① 实际业务需求:
maxLength: 0已实测把 schema-drift 推到「计划一个没有服务器接受的 varchar(0) DDL、且标记为 destructive」——这是真实的错误放大链,不是理论洞。写侧校验器的十类型清单就是今天真实在读这个键的消费者。② 项目长远合理性:形状收紧(
z.number().int().min(1))与同文件precision的既有 house pattern 完全同构,是把已声明意图兑现;适用面收窄(哪些类型允许 maxLength)触 ADR-0078 declared=enforced,且三份清单(field.form / object.form / record-validator)互不一致——收敛到校验器的十类型清单是唯一有实测读者背书的答案。③ 防 AI 犯错:这是四轴里最强的一条:
maxLength现在「每个类型都可声明、什么形状都能过」,AI 作者在 boolean/lookup 上写 maxLength 得不到任何响声。publish 时响亮拒绝优于驱动侧逐个防御(#11431 已被迫在消费端防了一次)。④ 创业阶段不扩散需求:两个修法都是收窄不是扩张,成本一次性;不裁的成本是每个驱动/工具各自防御一遍。
推荐:形状收紧(int ≥1)+ 适用面收敛到 record-validator 的十类型清单,一次裁掉两问;实施走条款②契约复审档位,并同笔对齐 field.form/object.form 两份表单清单。 本分析看不见的:存量已发布 app 里是否有踩线声明(如小数/0/挂在非字符串类型上)——若维护者知道存在,裁决需附迁移/宽限条款。
Generated by Claude Code
os-zhuang commentedon Aug 24, 2026
Maintainer ruling recorded — tighten both halves
Provenance: maintainer, 2026-08-24, live PM chat, batch acceptance, verbatim: 「四维分析一致的,接手你的建议。」 The four-facet block above is aligned, so the recommendation is adopted.
Ruled: shape tightens to the house pattern (
z.number().int().min(1), per theprecisionprecedent one field below), and applicability converges to the write-time validator's ten-type list (text | textarea | email | url | phone | password | markdown | html | richtext | code) —maxLengthrefused on types that store no bounded string. The two authoring-form lists (field.form.ts,object.form.ts) are aligned to the same ten in the same stroke. Clause-②: yes (narrows the accept set) — contract-review tier applies at dispatch.State:
needs-user-decision→pm:queue(spec lane). Downstream: #11431'sBlocked-by: #11566unlocks when this closes; the engine seat should re-verify that card's surface on the post-landing ref per the unlock discipline.Generated by Claude Code
os-support-ai commentedon Aug 25, 2026
Claim: PM loop round R5 wave 1
Session:
session_01K93P8PbH7kVjsAXkqzH1zYBranch:
claude/issue-11566-maxlength-shape-applicabilityWorktree:
objectstack-issue-11566Domain:
domain:specFile surface:
packages/spec/src/data/field.zod.ts(base-schemamaxLengthat :856 + thelength/sizealias map at :738),packages/spec/src/data/field.form.ts,packages/spec/src/data/object.form.ts, new rejection pins + changeset (stop on breach; explain in the report). ⛔packages/spec/src/migrations/registry.tsis HELD by the #10724/#11169 family dispatch this wave — if this tightening's conventions force a touch there, stop and report rather than editing it.Container & model:
M,mode:subagent,model: claude-fable-5— dispatch-gates--tier packages/spec/src/data/field.zod.ts(this dispatch): "Clause ② SUSPECT surface —packages/spec/src/**— the contract surface … the normal landing zone of a clause-② card"; content reading is the verdict: the 2026-08-24 ruling narrows the accept set ⇒ fable-mandatory.Clause-②: yes
Serial constraints cleared: same-file predecessor PR #11903 (deleteBehavior, the
.overwrite()region) merged 2026-08-25 — same-day churn clause applies, work from current merged main. H17 hit named per protocol: hold #8345 declaresfield.zod.tsas aRestart-touchtrigger file — notification only, no rider work taken from it. In-flight overlap: none (lane had 0 dispatched cards at fire); lane sibling #11408 (editMode, may touchobject.form.ts) is deliberately held to wave 2 behind this card — cross-wave serial, not a fold (different defect shape, fails family gate ①). Cross-lane: #11739 (services,feat(spec,auth)) touches spec on the auth-posture surface, disjoint from this file set.Generated by Claude Code
fix(spec): maxLength is a positive integer, authorable only on bounde…
claude commentedon Aug 25, 2026
os-dev-report
{ "issue": 11566, "status": "done", "branch": "claude/issue-11566-maxlength-shape-applicability", "pr": "https://github.com/objectstack-ai/objectstack/pull/11989", "premise_still_valid": true, "summary": "Ruled tightening implemented on post-#11903 main (base ac59744): maxLength is z.number().int().min(1); a new superRefine refuses it outside the write-time validator's ten types, exported as BOUNDED_STRING_FIELD_TYPES; both authoring-form maxLength rows converge on the same ten; 15 new pins assert specific zod issue code+path+message. Alias-map assumption measured false-but-safe: strictObject aliases are unknown-key rejection guidance only — length:/size: never flow a value into maxLength, so no bypass exists. Repo sweep found zero spec-parsed authors of now-rejected values; driver-sql's #11431 consumer-defense fixtures bypass FieldSchema.parse and are preserved verbatim.", "tests": "All at head e36b89a (re-affirmed after the lint census commit; initial readings at 9ca3a81, tree byte-identical for spec surfaces). spec field.test.ts: 'Tests 184 passed (184)'. Full spec suite: 'Test Files 424 passed (424)' / 'Tests 11288 passed (11288)'; spec typecheck exit 0 incl 'check:test-typecheck: OK'. Reverse verification from committed state (git restore --source=origin/main, mutation grep-confirmed: BOUNDED_STRING_FIELD_TYPES count 0; no dist in the loop — spec tests import ./field.zod source, so no rebuild leg applies; restore leg grep count 4 + git status clean vs HEAD): exactly the predicted 12 reds ('Failed Tests 12' = 3 shape + 9 applicability pins), direction red-on-revert as expected. Consumers: objectql 'Test Files 232 passed (232)' / 'Tests 4113 passed (4113)'; driver-sql 'Test Files 128 passed | 8 skipped (136)' / 'Tests 1981 passed | 114 skipped (2095)' (live PG/MySQL matrix skips, no DB); lint 'Test Files 81 passed (81)' / 'Tests 2294 passed (2294)' after the census re-derivation.", "gates": [ "check:generated --fix: proved exactly 3 stale (api-surface, export-origins, docs) and regenerated them; 'check:authorable-surface ... ✓', 'check:skill-refs ... ✓' (no skills/** touch)", "check-adr-0087-registration: '✓ this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).'", "check-changeset-no-major: '✓ This diff introduces no major bump.'", "check-empty-changeset: '✓ No empty-frontmatter changeset introduced by this diff'", "check:i18n: 'check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys).' (after building the CLI; first run refused loudly with nothing measured)", "check:cross-package-test-inputs: 'OK: 16 package(s) read outside themselves, all declared'", "check:where-matcher / check:query-options-erasure / check:engine-double-contract: 'baseline key set verified against ac59744: no files added.'", "check:spec-parsed-alias: 'ADR-0122 type-alias convention ... OK'", "check-nul-bytes: 'OK (scanned 6651 text file(s) ... no raw ASCII control bytes).'", "dispatch-gates re-derived from actual diff, derivation stamped 'at commit 9ca3a81' then re-run at e36b89a (37 matched families placed); declared narrowing: repo-wide pnpm lint and check:type-check-debt --re-measure left to CI — spec's own typecheck+test-typecheck accounting ran green locally", "CI at 9ca3a81: Test Core red on lint's RHS-rule corpus census (expected 47, got 44) — fixed in e36b89a by re-deriving the census (the pin's own anchor note places in-list literals outside the rule); full lint suite green locally at that head" ], "deviations": [ "ADR-0087 ledger entry deferred to #11950: registry.ts is HELD by the #10724/#11169 family dispatch per the card constraint; the changeset carries no **BREAKING** marker (matching the #9689 accept-set-narrowing precedent), so the disposition gate does not fire — confirmed by its own verdict line above. The convention did NOT mechanically force a registry touch, so I did not stop-and-block; the debt is tracked openly instead of landing silently. PM may re-judge this fork.", "record-validator NOT rewired to import BOUNDED_STRING_FIELD_TYPES (outside the claimed file surface; membership identical and pinned on both sides) — possible follow-up refactor, noted in PR body.", "field.form minLength row left at 3 types / object.form minLength at 9 (ruled scope covered maxLength only); the minLength defect pair is filed as #11949.", "Report fields are the union of the dispatch's machine-checkable list and the standard template (the two specs differ; conflict noted per protocol).", "Second commit e36b89a (lint census) landed after the PR opened, in response to the PM's CI notification — same card scope, no widening." ], "files_changed": [ ".changeset/maxlength-bounded-string-only.md", "content/docs/references/data/field.mdx", "packages/spec/api-surface/data.json", "packages/spec/export-origins/data.json", "packages/spec/src/data/field.form.ts", "packages/spec/src/data/field.test.ts", "packages/spec/src/data/field.zod.ts", "packages/spec/src/data/object.form.ts", "packages/lint/src/validate-predicate-path-refs.test.ts" ], "open_questions": [ { "question": "Should the #11950 ledger entry land as soon as the registry hold lifts (this wave) or ride the next spec seat round?", "options": [ "A: next round, PM-scheduled", "B: rider on the #10724/#11169 family PR" ], "recommendation": "A — a rider on the family PR widens their scope; the entry is inert until protocol major 18 per step18's own text, so there is no urgency beyond tracking." } ], "out_of_scope_findings": [ "filed as #11949: minLength carries the identical pre-#11566 defect pair (shape + base-schema applicability + stale form lists); lower bound (0 vs 1) is a judgment call", "filed as #11950: deferred ADR-0087 ledger entry for this narrowing (registry.ts held this wave)" ] }Generated by Claude Code
os-support-ai commentedon Aug 25, 2026
ACCEPT + Contract review: PASS — PR #11989 at head
e36b89a(spec seat R5, reviewer of recordsession_01K93P8PbH7kVjsAXkqzH1zY).Checklist verdict, verified against GitHub and my own reads:
main, first lineFixes #11566(full ruled scope delivered). Changed files (9) = claim surface + the PM-directed patch-round addition (packages/lint/src/validate-predicate-path-refs.test.ts— deviation explained in the report, directed by this seat's CI-failure round). Changeset on the published package; nocontent/docs/releases/; no governed surface.z.number().int().min(1)+BOUNDED_STRING_FIELD_TYPESten-typesuperRefinerefusal + both form rows converged to the ten. 15 pins assert specific zod issuecode+path+message — real rejection tests, not baretoThrow.9ca3a81(lint's Publish-time validation of predicate path references — a spec-delivered predicate naming a nonexistent path should fail at authoring, not evaluate to a guess at render (#6936 companion) #7010 corpus census, 47→44) was this PR's own doing (the==-chain →in-list respell removed 3 literal comparisons); the fix ate36b89are-derives the census per the pin's own anchor note (in-list literals are outside the RHS rule) with the derivation recorded in a comment — I read the hunk myself; it is a reasoned re-derivation, not a blind count bump. Full lint suite green locally (2294 passed)..overwrite()machinery correctly identified as default-materialization-only;superRefineis the right house pattern and absence-stays-absence is pinned.maxLengthtoo — a declared 1024 becomes varchar(255) and refuses legitimate writes #11431 consumer-defense fixtures preserved verbatim per the dispatch constraint (they bypassFieldSchema.parseby design).**BREAKING**marker, matching the FieldSchema acceptsdeleteBehavior: 'set_null'on amaster_detail, and the engine silently resolves it tocascade#9689 accept-set-narrowing precedent, so the disposition gate does not fire; the ledger-entry debt is tracked openly in spec: register the #11566 maxLength narrowing in the ADR-0087 migration ledger (deferred from the enforcement PR — registry.ts was held) #11950 instead of landing silently in the HELDmigrations/registry.ts. This is lane precedent, and the annotation-gap itself is already tracked (No gate requires a breaking-behavior changeset to carry the breaking annotation at all — the ADR-0087 disposition gate only fires once one already does #8607) — flagged to the maintainer in the round report, not re-ruled here.contributesblock (events/menus/themes/translations/actions/drivers/fieldTypes/functions/commands) — tombstone route,kindssurvives #10724/contributes.kinds.globsis declared-but-unenforced: nothing reads it, and the file-type discovery it advertises globsfilePatternsoff a registrycontributes.kindsdoes not extend #11169 fold releases the registry hold; a rider would widen the family PR. The entry is inert until protocol major 18 per step18's text; tracking is the urgency, and it is tracked.minLengthstill has the pre-#11566 defect pair — validated as no more than a number, authorable on every field type #11949 (minLength twin defect, unassigned, judgment call flagged) and spec: register the #11566 maxLength narrowing in the ADR-0087 migration ledger (deferred from the enforcement PR — registry.ts was held) #11950 — both exist, both back-linked.get_session→claude-fable-5=CONTRACT_REVIEW_TIER); standing authorization on seat post [PM seat] domain:spec — ⏳ vacant #6017 (2026-08-24, 「你自己就是fabke,为什么还要等 review」).Disposition: enqueue follows the standing rule — every check on the PR green at head
e36b89a; the seat flips ready + queues on the all-green reading and tracks to MERGED. Card stayspm:dispatcheduntil landing.Generated by Claude Code
fix(spec): minLength is a positive integer, authorable only on bounde…
feat(spec): register the deferred #11566 maxLength narrowing in the A…
feat(spec): declare rows (multiline editors) and options[].descriptio…
fix(spec): maxLength is a positive integer, authorable only on bounde…
fix(spec): minLength is a positive integer, authorable only on bounde…
feat(spec): declare rows (multiline editors) and options[].descriptio…
fix(spec): os migrate meta guidance for the ui-* and plugin-* migrati…
test(spec): the second data/ file group's test titles state each cite…