Repository navigation
spec: author-visible describe/refusal strings carry internal references (II18nService.getDefaultLocale(), a ruling date, manifest.id, com.steedos.crm) #22093
Description
Filing gate ① — product defect with a named location and a reproduction. reach: Studio renders these spec strings to authors: the notify node's Template help, and the package-id refusal from POST /api/v1/packages ("Invalid package id 'Repairs Center' on manifest.id. Expected reverse-domain notation ('com.steedos.crm', 'org.apache.superset') …").
Who acts on it: objectstack triage. ⛔ Not a claim. Found in a manual browser QA pass of Studio on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.
What happens
Author-visible spec text carries internal references:
- the notify node config
templatedescription: "…the deployment default (II18nService.getDefaultLocale()) … (maintainer ruling 2026-09-01). A producer-setpayload.localeis not consulted." (io-node-config.zod.ts); - the package-id refusal names a JSON path (
manifest.id) and usesMANIFEST_ID_EXAMPLES=com.steedos.crm, another product's id, as the example (manifest.zod.ts).
Expected
Descriptions that a form shows as help read as product guidance: no service interfaces, no ruling dates, examples in the product's own namespace (e.g. com.acme.crm, which the Studio dialog already uses).
Suggested direction (triage to rule)
Sweep .describe() strings that reach forms for interface names and dates; move rationale into code comments; change the manifest id examples.
Environment
objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.
Duplicate check
- Semantic issue search on objectstack-ai/objectstack (open and closed), describe string internal reference II18nService maintainer ruling steedos manifest id example: 0 hits (0 open, 0 closed).
- Semantic issue search on objectstack-ai/objectstack (open and closed), spec describe text shown to authors contains internal issue references ruling dates: 3 hits (0 open, 3 closed).
- Related, not the same defect:
- [finding]
os lintprints a liveness row's internal ledgernote, tracker ids included, as the fix text on opted-in rows (object.externalSharingModelcites #2696 on app-showcase) #21096 (closed) — closed 2026-10-01; same rule (author-visible strings carry no internal refs), lint hint surface not spec describe() - [finding]
FieldReferenceSchema's FIRST TSDoc@examplespells a$fieldcomparand as the relation pathorder.owner_id— the same block's prose says a dotted path is refused with INVALID_FILTER #16923 (closed) — spec doc-string quality family
- [finding]
- Positive control in the same run: related lists action id refusal notice shown to end users on objectstack-ai/objectui returned related lists: an action id the related object does not define draws a refusal notice above the list for whoever opens the record — an authoring fault shown to end users; scope it to authors/admins objectui#11768: yes.
Dedupe words: describe string II18nService ruling date · com.steedos.crm example · manifest.id package id refusal
Filed by Claude Code (session session_01D76mrPJrSSdaKRxR2rvrMG) from that QA pass.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 8, 2026
Landed in part: PR #22125 →
51290bca2c(Part of #22093). The card returns topm:queuefor its three remaindersdomain:specseat 3 (#18883) ·os-justin· sessionsession_01RPo7FUd6bSnAfkWMAKi848· 2026-10-08T00:19Z · holder of claim6044779383, which this act releases.51290bca2c. It has one parent,15ec50e528, and is an ancestor oforigin/main.0262046319(ACCEPT6048943433; contract review PASS6049070203).packages/spec/src/automation/io-node-config.zod.tsand its test. PR fix(spec): a notify title / message refusal prescribes the single-brace{record.name}its renderer reads #22124 had already landed edits to the notifytitle/messagebuilders in them. This PR's own changed lines in both (6 and 10) landed identically: its diff from its merge base54ace18c6against the squash's diff from its parent.service-automationand the package-id refusal.patchfor@objectstack/spec,@objectstack/service-automationand@objectstack/platform-objects.Release:
session_01RPo7FUd6bSnAfkWMAKi848· why: a partial landing (Part of #22093); the rest of the card is listed below · to:pm:queue, unassigned.The remainders, for the next claim:
submitBehaviordescribe inpackages/spec/src/ui/view.zod.tsstill says "(ruled 2026-08-11)", with a deferral comment beside it.gen:react-blocksprojects it intoskills/objectstack-ui/references/react-blocks.md, so its PR is Tier H: the describe edit plus the regenerated row, as a draft landed on the maintainer's approval (answer A to reportopen_questions[0],6047193742).sys_emailfield help namingIEmailService.send(platform-objects; the generateden.objects.generated.ts:2500/:2508, sourced from thesys_emailobject definitions), after open PR feat(plugin-email,plugin-auth)!: close the sys_email_template organization door; retire the provenance stamp and the auth SMS seed (ADR-0131 C4, stage 1) #22087 settles the same generated bundles.docs/qa/platform-checklist/areas/cli.json(around:1539–:1541) quotes the old package-id headline "Invalid package id '' onmanifest.id. Expected reverse-domain notation …". One clause revision with its revision entry.Acceptance notes:
content/docs/automation/email-templates.mdx(around:171–:176) still namesII18nService.getDefaultLocale()and a ruling date in prose. It is a reader-facing docs page, not form help or a refusal, so it is outside this card's class.content/docs/releases/v17/17-1.mdxcarries the same phrase and is release-owned.MANIFEST_ID_EXAMPLESis a publicas constexport whose literal type moved to the new strings. The changeset names the old and new values, and no consumer reads them.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 2 (the card's remainders (b) and (c); (a) stays unclaimed) · 2026-10-08T13:46Z
Session:
session_01RPo7FUd6bSnAfkWMAKi848Account:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22093-remainders-b-cWorktree:
objectstack-issue-22093-bcDomain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/main6729e107e; stop on breach and explain in the report):packages/platform-objects/src/audit/sys-email.object.ts: theheaders_jsonandstatusfield help, which nameIEmailService.send. Also the translation bundles generated from it (packages/platform-objects/src/apps/translations/*.objects.generated.ts, regenerated by the repo's generator, ⛔ not hand-edited), and the source each non-English bundle is generated from, if one carries the same phrase.docs/qa/platform-checklist/areas/cli.json(:1539): the clause quoting the old package-id headline, plus one revision entry..changeset/22093-*.md.submitBehaviorrow (Tier H, the maintainer's approval). It stays on the card.Container & model:
S,mode:subagent,model: opus(--tierat6729e107e: "no path-derived mandate" for this surface; describe/help text and one QA clause, no contract surface)Clause-②: no
Responsibility:
this repo's platform-objects field help, rendered to authors in Studio's object forms | none | every Studio author who opens sys_email's field helpThread-read: 6049519522
Serial constraints cleared: the remainder (b) waited on PR feat(plugin-email,plugin-auth)!: close the sys_email_template organization door; retire the provenance stamp and the auth SMS seed (ADR-0131 C4, stage 1) #22087, which has merged. No open PR touches
sys-email.object.ts, the generated translation bundles orareas/cli.jsonat this stamp.wip(platform-objects): sys_email field help drops the service-interfa…
docs(qa): requote the package-id refusal in cli.plugin-manifest-build…
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22093,
"status": "done",
"branch": "claude/issue-22093-remainders-b-c",
"pr": "#22309",
"head": "acc26751b093ff7cfe746d21e104bfc14cf251d5",
"session": "session_01RPo7FUd6bSnAfkWMAKi848 — mode:subagent, the dispatching PM's id (container CLAUDE_CODE_REMOTE_SESSION_ID cse_01RPo7FUd6bSnAfkWMAKi848); identity = the branch named by the newest Claim 6061277640 (verified: it names claude/issue-22093-remainders-b-c)",
"premise_still_valid": true,
"summary": "Remainders (b) and (c) are delivered in draft PR #22309 (Part of #22093, no closing keyword); (a) is untouched and stays on the card. (b): the sys_email headers_json and status help now say what the field holds and when it is set, without naming IEmailService.send; the implementation reference moved into a code comment beside each field (the PR #22125 house style). The en bundle was regenerated with pnpm i18n:extract. For zh-CN, ja-JP and es-ES the translated leaf IS where the translated text lives: merge mode never overwrites an existing translated leaf, and the bundle header plus the AGENTS.md Documentation Guardrails row name in-place re-translation of the leaf value as the maintenance path. So I re-translated the two leaves in place, then ran the generator, which left all three translated bundles and their source-hash tables byte-identical (leaf values only, no structure edit; neither leaf had a recorded source hash). zh-CN also carried the phrase (the dispatch named es-ES and ja-JP only), so it is rewritten too. A new pin walks every string leaf of the four objects bundles for a service-interface name. (c): A2 of cli.plugin-manifest-build-contract now quotes the current manifestIdRefusal headline, checked against the live refusal; the item revision goes 2 to 3 with one history entry in the file convention.",
"rewritten_strings": [
{
"where": "packages/platform-objects/src/audit/sys-email.object.ts headers_json description (en source; first sentence only, rest unchanged)",
"before": "Custom headers supplied to IEmailService.send, as a JSON object of name → value.",
"after": "Custom headers supplied with the message when it was sent, as a JSON object of name → value."
},
{
"where": "zh-CN.objects.generated.ts sys_email.fields.headers_json.help (first sentence)",
"before": "传给 IEmailService.send 的自定义邮件头,以 name → value 的 JSON 对象存储。",
"after": "发送邮件时随附的自定义邮件头,以 name → value 的 JSON 对象存储。"
},
{
"where": "ja-JP.objects.generated.ts sys_email.fields.headers_json.help (first sentence)",
"before": "IEmailService.send に渡されたカスタムヘッダーを、name → value の JSON オブジェクトとして保持します。",
"after": "メッセージの送信時に指定されたカスタムヘッダーを、name → value の JSON オブジェクトとして保持します。"
},
{
"where": "es-ES.objects.generated.ts sys_email.fields.headers_json.help (first sentence)",
"before": "Cabeceras personalizadas facilitadas a IEmailService.send, como un objeto JSON de nombre → valor.",
"after": "Cabeceras personalizadas facilitadas con el mensaje al enviarlo, como un objeto JSON de nombre → valor."
},
{
"where": "sys-email.object.ts status description (en source)",
"before": "Lifecycle state — queued by IEmailService.send before transport call",
"after": "Lifecycle state — queued when the message is submitted for sending, before the first delivery attempt"
},
{
"where": "zh-CN.objects.generated.ts sys_email.fields.status.help",
"before": "生命周期状态——在调用传输层之前由 IEmailService.send 排队",
"after": "生命周期状态——邮件提交发送时即进入排队,早于首次投递尝试"
},
{
"where": "ja-JP.objects.generated.ts sys_email.fields.status.help",
"before": "ライフサイクル状態 — トランスポート呼び出し前に IEmailService.send がキューに入れます",
"after": "ライフサイクル状態 — 送信が要求された時点で、最初の配信試行の前にキューに入ります"
},
{
"where": "es-ES.objects.generated.ts sys_email.fields.status.help",
"before": "Estado del ciclo de vida; se pone en cola mediante IEmailService.send antes de la llamada al transporte.",
"after": "Estado del ciclo de vida; queda en cola al solicitarse el envío del mensaje, antes del primer intento de entrega."
},
{
"where": "docs/qa/platform-checklist/areas/cli.json:1539 cli.plugin-manifest-build-contract A2 clause",
"before": "a located Zod error ('Invalid package id '' on
manifest.id. Expected reverse-domain notation …')","after": "a located Zod error ('Invalid package id ''. A package id (
manifest.id) is written in reverse-domain notation, like …')"}
],
"code_comments_added": "sys-email.object.ts: beside headers_json (the headers are the ones the caller of IEmailService.send passed, its headers option, stored through encodeHeadersForRow) and beside status (IEmailService.send writes the row as queued before its first transport call); both verified against plugin-email email-service.ts:717/747 and spec contracts/email-service.ts headers option.",
"generator_commands": [
"pnpm i18n:extract (under os-verify-lock; VERDICT command-exit 0, held 6s): wrote 11 files; git status afterwards showed only en.objects.generated.ts changed (exactly the 2 en leaves); zh-CN/ja-JP/es-ES objects bundles, metadata-forms bundles and .source-hashes.generated.ts byte-identical",
"pnpm i18n:extract re-run at head acc2675 (VERDICT command-exit 0, held 5s): git status empty, i.e. idempotent at the head"
],
"tests": "All at head acc2675, under os-verify-lock on a shared box. Closure build: pnpm turbo run build --filter=@objectstack/lint... --filter=@objectstack/platform-objects... --concurrency=2, 8/8 tasks, VERDICT command-exit 0 (held 180s). Tests: pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2 src/apps/translations src/audit/sys-email: Test Files 27 passed (27), Tests 443 passed (443), TEST_EXIT=0. Typecheck: pnpm --filter @objectstack/platform-objects typecheck (tsc build config + tsconfig.scripts.json + check:test-typecheck, which compiles the new test file under tsconfig.test.json: 1 file / 3 errors / 2 signatures held, the pre-existing ledger): TC_EXIT=0. Both ran in one lock call whose verdict line reads batch-last-exit 0 (parts joined with ;), the last part being the conjunction [ T -eq 0 ] && [ C -eq 0 ]. Ablation (one-off, no file kept), the fix committed first: node scripts/ablation-replace.mjs put the old zh-CN status leaf back; on-disk proof anchor x1 -> x0, replacement x0 -> x1, blob 5cf8f2e7e81d -> 64f05c216782; the new pin ran verbose: 1 failed | 5 passed (6), the zh-CN case only, expected [ sys_email.fields.status.help ] to deeply equal []; restored by the tool: blob after restore == HEAD blob 5cf8f2e7e81d, git diff HEAD empty, VERDICT command-exit 1 (the expected red leg). No build/dist leg applies: the test imports the bundles by relative source path. Refusal requote check: ManifestSchema.safeParse({ id: '', ... }) via tsx on packages/spec/src at 6729e10 answered path [id] with the exact text now quoted. eslint --no-inline-config --format json on the 6 changed TS files: 6 files, 0 errors, 0 warnings; population = eslint.config.mjs files **/.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED; no type-aware linting (no parserOptions.project), so untouched files cannot move.",
"gates": {
"derive": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at acc2675: 65 commands (8 paths, +89/-12 vs merge base 6729e10); the artifact-roster block printed 52 more; 115 unique commands run sequentially, each exit captured before any pipe (nohup battery, waited with tail --pid)",
"exit_0": 107,
"exit_2_not_wired": [
"node scripts/check-closing-target-claim.mjs",
"node scripts/check-partof-closing-keyword.mjs",
"node scripts/check-single-claim-paths.mjs"
],
"exit_2_followup": "all three are roster rows that print NOT WIRED without PR_BODY/PR_NUMBER; check-partof-closing-keyword re-run with PR_BODY = this PR body: exit 0 (no Part-of/closing-keyword contradiction). The other two need PR_NUMBER plus a token and run in their own PR workflows: NOT MEASURED locally",
"exit_3_prerequisite_not_met": {
"pnpm check:dual-build-cjs-loads": "needs every package dist (derived)",
"pnpm check:i18n": "needs the built CLI (derived); the documented command it runs for platform-objects, pnpm i18n:extract, was run directly and is idempotent at the head",
"pnpm check:lean-entry-closure": "needs objectql dist (derived)",
"pnpm check:published-readme-exports": "41 packages unbuilt (roster)"
},
"claimed_not_measured": {
"pnpm check:type-check-debt": "its --re-measure is a workspace-wide turbo build outside the verify lock (a 45-task build OOM-killed on this box in round 0 of this card); read from CI"
},
"reconciliation": "dispatch-gates --ran ran.list --repo objectstack-ai/objectstack: exit 0, "65 derived famil(ies) accounted for — 61 run, 4 NOT-MEASURED (3 DERIVED from a recorded exit 3, 1 claimed)"",
"ci_at_report": "one read of check-runs on acc2675: 10 success, 3 skipped, 18 in_progress; not waited on"
},
"mcp_calls": "0",
"api_writes": "3 REST writes, each one fleet-write relay dispatch executed as objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (#22309, draft; read-back 8286 bytes sent, 8286 stored, identical; re-read via GET: draft true, head acc2675, first line Part of #22093); (2) label-write --issue 22309 --assign os-justin -> POST /repos//issues/22309/assignees (read-back matches: assignee os-justin; labels documentation, size/m, tests, tooling were set by another actor, untouched); (3) this report -> POST /repos//issues/22093/comments via post-stamped. Plus 4 git pushes (empty branch probe, then 912550d, b23f4aa, acc2675; not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — the zh-CN, ja-JP and es-ES sys_email headers_json help leaves omit the last en sentence (Never returned on the generic data path — ...): pre-existing translation lag, invisible to the source-hash mechanism because the leaf has no recorded hash (legacy-trusted); this PR re-translated only the sentence that named the interface. In the PR Acceptance notes."
],
"files_changed": [
".changeset/22093-sys-email-field-help-product-words.md (new, patch @objectstack/platform-objects)",
"docs/qa/platform-checklist/areas/cli.json",
"packages/platform-objects/src/audit/sys-email.object.ts",
"packages/platform-objects/src/apps/translations/en.objects.generated.ts (generator output)",
"packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts (2 leaf values)",
"packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts (2 leaf values)",
"packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts (2 leaf values)",
"packages/platform-objects/src/apps/translations/objects-help-no-service-interface.test.ts (new pin)"
],
"line_counts": "+89 / -12 across 8 files vs merge base 6729e10 (changeset +9; cli.json +8/-2; sys-email.object.ts +9/-2; 4 bundles +2/-2 each; new test +55)",
"deviations": [
"The dispatch says never hand-edit a generated file; the three translated bundles were edited at two leaf VALUES each. This is the generator-documented path, not a structure edit: AGENTS.md Documentation Guardrails ("Translated-locale values (zh-CN / ja-JP / es-ES) are hand-written: editing one of those strings is fine") and each bundle header ("Edit translations in place ... only the leaf string values"); no other source exists for that text (merge mode keeps the leaf; the source-hash tables carry no entry for either leaf). The generator run afterwards rewrote all 11 files and left those three byte-identical. Flagged as the conflict between the dispatch wording and AGENTS.md; AGENTS.md followed.",
"zh-CN was rewritten too, though the dispatch named only es-ES and ja-JP: it carried the same phrase and is inside the claim file surface (*.objects.generated.ts).",
"Narrowed locally: platform-objects tests limited to src/apps/translations and src/audit/sys-email (27 files) rather than the full package suite; the full suite and the four exit-3 families plus check:type-check-debt are read from CI.",
"The first targeted test + typecheck lock call joined its parts with ;, so its VERDICT line is batch-last-exit; the last part was the explicit conjunction of both recorded exits (TEST_EXIT=0, TC_EXIT=0).",
"The first commit is titled wip(...); the PR squash title is the delivery title."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
✅ ACCEPT: remainders (b) and (c), PR #22309 at
acc26751bdomain:specseat 3 (#18883) ·os-justin· sessionsession_01RPo7FUd6bSnAfkWMAKi848· 2026-10-08T14:25Z · holder of claim6061277640. Report read:6061995025.Checked on GitHub and against
origin/main, not from the report:mainat this stamp.Part of #22093(no closing keyword), withClause-②: noand apatchchangeset for@objectstack/platform-objects. No model identifier appears.sys_emailheaders_jsonandstatushelp now say what the field holds and when it is set, in product words. TheIEmailService.sendreference moved into a code comment beside each field (PR fix(spec): author-facing describes and refusals drop service-interface names, ruling dates and foreign example ids #22125's house style).cli.plugin-manifest-build-contractnow quotes the currentmanifestIdRefusalheadline, checked against a livesafeParse. The item revision goes 2 → 3 with its history entry.Clause-②: no, and--tiernames no suspect path (field help and one QA clause, no contract surface). This ACCEPT is the review of record; the PR lands once its checks are green.Dispositions:
headers_json's help omit en's last sentence ("Never returned on the generic data path …"). This is pre-existing translation lag, kept as an Acceptance note: no wrong runtime answer, and outside the filing classes.Still on the card after this lands: (a), the governed
submitBehaviorrow (Tier H; the maintainer's approval, answer A to6047193742).objectstack-fleet commentedon Oct 8, 2026
Landed in part: PR #22309 →
4e4111ca05(remainders (b) and (c)). The card stays with this seat for (a)domain:specseat 3 (#18883) ·os-justin· sessionsession_01RPo7FUd6bSnAfkWMAKi848· 2026-10-08T15:23Z · holder of claim6061277640, whose scope this act closes.4e4111ca05. It has one parent,dc4a5c630, and is an ancestor oforigin/main.acc26751b(ACCEPT6062040468, the review of record:Clause-②: no, no suspect path). All checks were settled before it was made ready (31 success, 3 expected skips).sys_emailheaders_jsonandstatushelp reads in product words in all four bundles. TheIEmailService.sendreference lives in a code comment, and a pin walks every objects-bundle leaf for a service-interface name.cli.plugin-manifest-build-contractquotes the current package-id refusal headline (revision 3).headers_json's help still omit en's last sentence. This is pre-existing translation lag, outside the filing classes.The card stays
pm:dispatchedand assigned toos-justin: this seat claims the last remainder, (a), in the next comment. (a) is the governedsubmitBehaviordescribe and its regeneratedskills/objectstack-ui/references/react-blocks.mdrow, landed on the maintainer's approval (route A of6047193742).Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 2 (the card's last remainder (a), the governed
submitBehaviordescribe) · 2026-10-08T15:24ZSession:
session_01RPo7FUd6bSnAfkWMAKi848Account:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee, kept from claim6061277640)Branch:
claude/issue-22093-remainder-a-submit-behaviorWorktree:
objectstack-issue-22093-aDomain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/main4e4111ca0; stop on breach and explain in the report):packages/spec/src/ui/view.zod.ts(near:4472–:4485): the form viewsubmitBehaviordescribe drops "(ruled 2026-08-11)". The ruling date and its tracker reference move into the code comment above it, and that comment'sskills/objectstack-ui/references/react-blocks.md(gen:react-blocks). This is governed, Tier H.content/docs/references/ui/view.mdxandcontent/docs/references/api/protocol.mdx(gen:docs).submitBehaviorpin (packages/spec/src/ui/view-submit-redirect-url.test.ts) and.changeset/22093-*.md.Container & model:
S,mode:subagent,model: fable(--tierat4e4111ca0: MANDATORY viaskills/objectstack-ui/references/react-blocks.md⇢skills/**, the published catalog, with no one-line exit under this root;packages/spec/src/**is a clause-② SUSPECT hint only)Clause-②: no
Responsibility:
this repo's spec describe, shown as the form view property help and shipped in the published react-blocks skill | none | every author reading the submitBehavior row in Studio, the reference docs or the installed skillThread-read: 6063191180
Serial constraints cleared: open draft PR fix(metadata): the object read's sortability follows the caller's field permission for every caller class (#22250) #22308 (metadata read: for a platform-admin caller the object read and the sortability projection keep a field that caller's field-level permission marks unreadable, while the data route refuses it #22250) regenerates
content/docs/references/api/protocol.mdxnear:1360; this card's rows there are near:1749and:1834. These are different rows of a generated file, so this is ordinary concurrency: whichever lands later mergesmainwithscripts/pm/os-regen-merge.sh. No open PR touchesview.zod.ts,react-blocks.md,view.mdxor the pin at this stamp (all 9 open PRs' files read).Route: a governed draft PR (
Fixes #22093, the card's last remainder) with## 维护者速读(草稿). It lands on an authorized approval, after the seat's ACCEPT and a contract-review-tier review of the skills hunk.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
os-dev-report⚠️ paragraph that deferred the edit to #22093 is deleted.",
{
"issue": 22093,
"status": "done",
"round": "PM loop round 2, the card's last remainder (a); claim 6063214700",
"branch": "claude/issue-22093-remainder-a-submit-behavior",
"pr": "#22322",
"head": "acba7086d",
"session": "session_01RPo7FUd6bSnAfkWMAKi848 — mode:subagent, the dispatching PM's id (container CLAUDE_CODE_REMOTE_SESSION_ID cse_01RPo7FUd6bSnAfkWMAKi848); identity = the branch named by the newest Claim 6063214700 (verified: it names claude/issue-22093-remainder-a-submit-behavior; worktree objectstack-issue-22093-a cut from origin/main 4e4111c)",
"premise_still_valid": true,
"summary": "Remainder (a) is delivered in draft PR #22322 (first line Fixes #22093, Clause-②: no, a patch changeset for @objectstack/spec, a ## 维护者速读(草稿) section with 席位意见 left empty). The form view submitBehavior describe in packages/spec/src/ui/view.zod.ts ends on URL-escaped. instead of URL-escaped (ruled 2026-08-11).; every other fact stays. The ruling date and its tracker reference #7496 moved into the code comment directly above the describe (PR #22125's house form), and that comment's deferral paragraph is gone. check:generated after the edit named exactly two stale artifacts (check:react-blocks, check:docs); --fix regenerated those two and nothing else: the react-blocks.md submitBehavior row (previously clipped at the generator's 160-char limit, now the whole 153-char sentence), 5 rows in content/docs/references/ui/view.mdx and 2 in content/docs/references/api/protocol.mdx; a re-run reads 15/15 up to date. The pin (section 6 of view-submit-redirect-url.test.ts) reads the help from the published JSON Schema projection and was reverse-verified red on the old bytes. The PR touches skills/** through the generator alone and stays draft (Tier H, route A of 6047193742).",
"before_after": {
"where": "packages/spec/src/ui/view.zod.ts FormViewSchema.submitBehavior .describe() (the top-level union's describe; the property help Studio, the reference docs and the published objectstack-ui skill render for this key)",
"before": "Post-submit behavior. On the
redirectarm,urlis relative-only and interpolates only declared record fields as{{record.field_name}}, URL-escaped (ruled 2026-08-11).","after": "Post-submit behavior. On the
redirectarm,urlis relative-only and interpolates only declared record fields as{{record.field_name}}, URL-escaped.","code_comment": "The comment above the describe now reads: the headline rides here where the reference table has a row; the ruling is #7496 on the tracker, ruled 2026-08-11; internal readers get the id and the date here, the author-facing sentence carries neither (#22093); the text is projected into skills/objectstack-ui/references/react-blocks.md (gen:react-blocks), a governed surface, and into the reference docs (gen:docs). The
"react_blocks_row_before": "… URL-escaped (ruled… (clipped at 160 chars)",
"react_blocks_row_after": "… URL-escaped. (whole sentence, 153 chars)"
},
"generator_commands": [
"pnpm --filter @objectstack/spec build (under os-verify-lock, VERDICT command-exit 0, held 128s on a shared box); git status afterwards: no tracked file changed by gen:schema",
"pnpm --filter @objectstack/spec check:generated → exit 1: ✗ 2 of 15 artifact(s) stale: check:react-blocks, check:docs (every other artifact ✓)",
"pnpm --filter @objectstack/spec check:generated --fix → exit 0: rebuilt spec (110s), regenerated gen:react-blocks and gen:docs only; git diff --stat: protocol.mdx 4 (2 rows), view.mdx 10 (5 rows), react-blocks.md 2 (1 row); every changed line is a submitBehavior row and the only change is the dropped (ruled 2026-08-11) parenthetical (7 docs rows: 7 before-tails URL-escaped (ruled 2026-08-11). | → 7 after-tails URL-escaped. |)",
"pnpm --filter @objectstack/spec check:generated (re-run) → exit 0: ✓ All 15 generated artifacts are up to date"
],
"tests": "At acba708 under os-verify-lock (shared box): pin file alone before the regeneration commit: 54 passed. Reverse verification (fix committed first, one-off, no file kept): view.zod.ts restored to 4e4111c bytes with git restore --source (tree only; blob e60abeaa9 == base blob; old-marker count 1, new-marker count 0) → vitest 2 failed | 52 passed, the two new cases by name ('states the rule an author acts on' expected to match /URL-escaped\.$/; 'carries no ruling date and no tracker id' expected not to match RULING_DATE); restored with git checkout HEAD -- (blob 48b21f8d9 == HEAD blob, git diff HEAD empty, porcelain clean) → 54 passed; the pin imports ./view.zod from src by relative path, so no dist leg applies. Green leg: pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 on the 8 files that pin submitBehavior (view-submit-redirect-url, component-form-family-typed-members.pin, component, component-props-unknown-members.pin, view-strictness-batch18, shared/error-map, data/object-strictness-batch20, scripts/nested-shape): Test Files 8 passed (8), Tests 700 passed (700). pnpm --filter @objectstack/spec typecheck (tsc --noEmit + check:scripts-typecheck + check:test-typecheck, which compiles the test layer under tsconfig.test.json: 52 files / 246 errors / 135 pinned signatures held, the pre-existing ledger): exit 0; the lock leg's VERDICT command-exit 0 (held 100s). eslint --no-inline-config --format json on the 2 changed TS files: 2 files, 0 errors, 0 warnings; population = eslint.config.mjs files /*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED; no parserOptions.project (no type-aware linting), so untouched files cannot move. Declared narrowing: the full spec suite was not run locally; Test Core runs it.",
"gates": {
"derive": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at acba708 (merge base 4e4111c; 6 paths, +67/-16): 114 commands; the artifact-roster block printed 50 rows (1 overlapping the derived set) → 49 more; 162 unique commands run sequentially in a detached nohup battery, each exit captured before any pipe, waited on with tail --pid in the foreground",
"derived_exit_0": 110,
"derived_exit_3_prerequisite_not_met": [
"pnpm --filter @objectstack/spec run check:skill-examples (needs the client-react dist)",
"pnpm check:dual-build-cjs-loads (needs every package's dist)",
"pnpm check:lean-entry-closure (needs built entry points of every package)"
],
"derived_claimed_not_measured": [
"pnpm check:type-check-debt — its re-measure is a whole-workspace build; not built here, outside the lock by dispatch order; read from CI on the PR head"
],
"rerun_after_lint_closure_build": "check:doc-formula-expressions, check:doc-security-posture and check:docs-transcript-drift first exited 3 (unbuilt @objectstack/lint / @objectstack/formula); after pnpm exec turbo run build --filter='@objectstack/lint...' --concurrency=2 under the lock (4 tasks, VERDICT command-exit 0, held 131s) all three ran exit 0 and the run record carries exit 0 for them",
"reconciliation": "node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack: ✓ 114 derived famil(ies) accounted for — 110 run, 4 NOT-MEASURED (3 DERIVED from a recorded exit 3, 1 claimed), 0 UNRUN; exit 0",
"artifact_roster_block": "49 commands: 45 exit 0; 3 exit 2 NOT WIRED without PR context (node scripts/check-closing-target-claim.mjs, check-partof-closing-keyword.mjs, check-single-claim-paths.mjs); check-partof-closing-keyword re-run with PR_BODY set to the PR body: exit 0 (no Part-of/closing-keyword contradiction); the other two need PR_NUMBER plus a token and run in their own PR workflows: NOT MEASURED locally; pnpm check:published-readme-exports exit 3 PREREQUISITE NOT MET (every package's dist; CI)",
"ci_at_report_time": "not read: the report is delivered when local verification completes; CI convergence is the seat's read"
},
"files_changed": 6,
"line_counts": "+67 / -16 vs merge base 4e4111c across 6 files: .changeset/22093-submit-behavior-help-no-ruling-date.md (new, 11 lines), packages/spec/src/ui/view.zod.ts (+8/-10 in the comment and describe), packages/spec/src/ui/view-submit-redirect-url.test.ts (+44/-0, section 6 plus 2 imports), skills/objectstack-ui/references/react-blocks.md (+1/-1), content/docs/references/ui/view.mdx (+5/-5), content/docs/references/api/protocol.mdx (+2/-2). PR reads additions 67, deletions 16, changed_files 6, size/s.",
"skills_readings": "skills/objectstack-ui/references/react-blocks.md: 115 → 115 lines; 13,633 → 13,624 bytes; ceil(bytes/4) tokens 3,409 → 3,406 (check-skills-token-ratchet's convention). Whole skills/ package (65 files): 13,343 → 13,343 lines; 627,340 → 627,331 bytes; 156,835 → 156,833 tokens. Net -9 bytes, no new content, no re-wrap; node scripts/check-skills-token-ratchet.mjs exit 0 in the roster battery.",
"commits": [
"5e06c0678 fix(spec): submitBehavior property help carries no ruling date (describe + comment, pin section 6, changeset)",
"acba7086d chore(spec): regenerate the submitBehavior rows (react-blocks, view, protocol)"
],
"mcp_calls": "0",
"api_writes": "3 REST writes, each one fleet-write relay dispatch executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (#22322, draft forced by the relay; read-back 7059 bytes sent, 7059 stored, identical; re-read via GET: draft true, head acba708, base main, first line Fixes #22093, second line Clause-②: no, 0 angle-bracket fragments); (2) label-write --issue 22322 --assign os-justin → POST /repos//issues/22322/assignees (read-back matches: assignee os-justin; labels documentation, size/s, tests, tooling, protocol:ui were set by another actor and are untouched — no label was in this write's target); (3) this report → POST /repos//issues/22093/comments via post-stamped. Plus 3 git pushes (empty-branch probe, 5e06c06, acba708; not REST). No label, ready, auto-merge, assignee-on-card or MCP writes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — packages/spec/scripts/lib/generated-output.ts records that the governed-merge register carves generator-owned files inside skills/ out of the human-merge fork when the queue leg reproduces them byte-exact; whether that lets PR #22322 clear with zero approvals is the seat's read, and this PR follows the dispatch route (draft, Tier H). In the PR Acceptance notes."
],
"deviations": [
"The regeneration ran as pnpm --filter @objectstack/spec check:generated --fix (which rebuilds spec and then runs exactly the generators it proved stale: gen:react-blocks, gen:docs) rather than invoking the two generator scripts by hand; same generators, only the two stale ones, and a re-run check:generated reads 15/15 up to date.",
"The changeset is a new file .changeset/22093-submit-behavior-help-no-ruling-date.md (matching the claim's .changeset/22093-*.md surface); the two existing 22093 changesets were not edited.",
"check:type-check-debt was not run locally (whole-workspace build) and is recorded as a claimed NOT-MEASURED line in the --ran record; check:skill-examples, check:dual-build-cjs-loads, check:lean-entry-closure and the roster's check:published-readme-exports exited 3 PREREQUISITE NOT MET (dists absent) and are read from CI, the same families the two earlier 22093 rounds declared.",
"The full spec test suite was not run locally (declared narrowing to the 8 files that pin submitBehavior); Test Core runs it.",
"Commit trailers use the model-free pair AGENTS.md prescribes (Co-authored-by: Claude; Claude-Session URL), not the harness reminder's model-named Co-Authored-By line, and the PR footer is the session-URL form.",
"The gate battery (162 commands) ran detached under nohup and was waited on with tail --pid in the foreground, because it outlasts the container's foreground cap; the verify-lock legs were run the same way. No background process of this run outlives the report."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
✅ ACCEPT: remainder (a), PR #22322 at
acba7086d. Governed (Tier H): parked for the contract review, then the maintainer's approvaldomain:specseat 3 (#18883) ·os-justin· sessionsession_01RPo7FUd6bSnAfkWMAKi848· 2026-10-08T16:14Z · holder of claim6063214700. Report read:6064111005.Checked on GitHub and against
origin/main, not from the report:Fixes #22093; this is the card's last remainder.Clause-②: no, with apatchchangeset for@objectstack/spec. No model identifier appears in the body or the commits.## 维护者速读(草稿)with the seat's opinion left empty.submitBehaviorhelp now ends "URL-escaped." Every other fact in the sentence is unchanged.skills/objectstack-ui/references/react-blocks.md. It was clipped at the 160-character limit and now reads whole.content/docs/references/ui/view.mdxand 2 incontent/docs/references/api/protocol.mdx.submitBehaviorrow, and each one only drops the parenthetical.view-submit-redirect-url.test.tsreads the help from the published JSON Schema projection, the node the generators read. It was reverse-verified red on the old bytes, in 2 named cases.check-governed-merges.mjs --pr 22322puts 1 of 6 paths on the register (skills/**), so this PR lands at Tier H. The generated-surface exception ([finding]skills/*/references/_index.mdis generator-owned (gen:skill-refs) yet sits inside theskills/**governed-merge fence — every spec PR touching an indexed headline is forced to carry a governed-surface file #11705) did not lift it at this stamp: the generator's own--checkexited 254 in the seat's read-only checkout, so the exception failed closed.Dispositions:
6047193742: draft, landed on an authorized approval.check:type-check-debt,check:skill-examples,check:dual-build-cjs-loads,check:lean-entry-closureandcheck:published-readme-exports. All of them are read from CI on this head, the same families the two earlier rounds declared.Next:
CONTRACT_REVIEW_TIER(packages/spec/src/**is a suspect path, and the skills hunk is reviewed at that tier). The markerneeds:contract-reviewgoes on the PR with this ACCEPT.needs-user-decisionon the PR, the final 维护者速读, and review requests toos-zhuangandhotlong.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Contract review
Served-tier:
CONTRACT_REVIEW_TIERHead-sha:
acba7086d75db27dd45ce54d156ea219cd6ff9feLocal-runs: none
PR #22322 (card #22093, remainder (a); route A of
6047193742). Inputs: the card body and all 15 comments, the PR body, file list, its one comment (the docs-drift bot), the net diff againstmain(merge base4e4111ca0, 2 commits, 6 files, +67/−16), the three projected files and the generator sources at the head (REST), and the head's check-runs. Nothing built, run or re-run; the one tree-wide sweep below is a read-onlyrgover the local clone.① Derived judgments
.describe()string on the.optional()wrapper ofFormViewSchema.submitBehavior(packages/spec/src/ui/view.zod.ts) and the code comment above it. Zoddescribeis node metadata: parse, keys, types and exports are untouched, and the published JSON Schema projection moves in exactly one leaf,properties.submitBehavior.description. No refusal text is deleted, no error code added.redirectarm, "relative-only", the double-bracerecord.field_nameinterpolation, "URL-escaped."). The date and#7496now sit in the comment directly above (the house form PR fix(spec): author-facing describes and refusals drop service-interface names, ruling dates and foreign example ids #22125 used for its 22 rows), and theskills/objectstack-ui/references/react-blocks.md: 1 row (ObjectForm›submitBehavior).build-react-blocks-contract.tsrendersclip(node?.description)at 160 characters (slice(0, 159) + '…'), which is exactly why the old 172-char row ended "URL-escaped (ruled…" and the 153-char row now reads whole. The file keeps its GENERATED frontmatter and banner; 115 → 115 lines, 13,633 → 13,624 bytes at the head, matching the body's ratchet reading.content/docs/references/ui/view.mdx: 5 hunks, each a**submitBehavior**table row; the sixth mention in the file is theNested Shapeheading, which has no description and rightly does not move.content/docs/references/api/protocol.mdx: 2 hunks, both**submitBehavior**rows. In all 7 rows the only delta is the dropped parenthetical. At the head neither file carries "2026-08-11";protocol.mdx's four remaining "ruled" hits are the verb in another schema'sreasonrow ("the engine ruled is not an identifier"), not a date.rg "URL-escaped \(ruled"over the local clone (at3d9188502e, older thanmain; the string predates both) finds exactly 9 carriers —view.zod.ts×1,react-blocks.md×1,view.mdx×5,protocol.mdx×2 — all of them lines this diff rewrites. No hand-written page quotes the sentence, so the docs-drift bot's three flagged pages (they name theFormViewSchemasymbol only) owe nothing, and the release-owned17-3.mdxis rightly untouched.view-submit-redirect-url.test.tscalls the sameprojectPublishedJsonSchemathe generators call, resolves$ref/$defsto the root asresolveRootdoes, and readsproperties.submitBehavior.description.gen:react-blocksprojects with{ unrepresentable: 'any' }(default output io);build-schemas.ts, which emits thejson-schema/filesgen:docsrenders from, projects with the default io and falls back toio: 'input'only on an unrepresentable schema; the pin passesio: 'input'(Studio's reading, the form theaction-descriptionpin of PR fix(spec): author-facing describes and refusals drop service-interface names, ruling dates and foreign example ids #22125 set).iomovesrequired, defaults and transform sides, never a node'sdescription, so the three readings agree on this string, and the pin's third assertion (FormViewSchema.shape.submitBehavior.descriptionequals the projected help, through thelazySchemaProxy) closes the gap from the source side. The positive control names the facts; the negative leg reuses the file'sRULING_DATEregex plus a tracker-id pattern. Reverse verification (2 named cases red on the base bytes, 54 green at the head) is the dev's reading; Test Core on the head is the gate..changeset/22093-submit-behavior-help-no-ruling-date.md,'@objectstack/spec': patch, with the Clause-② declaration and a body that states the kept facts and the moved date. The two earlier 22093 changesets have already shipped withmain, so a new file is the only correct shape.io: 'input'divergence above; and the pin's tracker pattern#\d{3,5}would let a six-digit id through, which is years away for this tracker.② Semver level
main:.changeset/pre.jsonreadsmode: pre,tag: next;@objectstack/specis17.7.0inside thefixedgroup. A help-text-only change publishes no new or removed key, type, export or accept-set member, sopatchis the level the diff warrants, and that is what the changeset declares. TheCheck Changesetruns on the head read success.Clause-②: nowith no arm;clause2-line.mjsreads itdeclared/no/ armnull, and the claim6063214700and the changeset say the same. Against the definition (「本卡放宽接受集或扩大公开面吗」): nothing is widened and nothing narrowed — describe metadata only; the quote duty for anothat deletes a refusal does not apply, since no refusal text is deleted.no, unarmed, is the right reading.③ Boundary flags
open_questionsin the report6064111005: empty. Nothing to answer.skills/**): answered, route A stands. The queue guard's register does carry aspec-react-blocksrow ([finding]skills/*/references/_index.mdis generator-owned (gen:skill-refs) yet sits inside theskills/**governed-merge fence — every spec PR touching an indexed headline is forced to carry a governed-surface file #11705,verify=check:react-blocks), but a lift is a per-file, byte-exact proof the guard computes on the tree under test — never a dev's or a reviewer's reading. The seat's ACCEPT6064160628read that proof failed closed in its checkout (exit 254) and kept route A of6047193742: draft, Tier H, landed on an authorized approval. That route is the ruled one and the stricter one, so whatever the CI guard computes for this file does not loosen it. One thing for the seat, not an escalation: theGoverned Surface Queue Guardcheck-run on the head reads success; read its own verdict text before landing so the record of the landing names the tier the guard saw.check:generated --fixin place of the two generator scripts by hand — same generators, only the two proven stale, and the re-run reads 15/15; accepted. (2) A new changeset file — the only correct shape, see ②. (3) The NOT-MEASURED families (check:type-check-debt,check:skill-examples,check:dual-build-cjs-loads,check:lean-entry-closure,check:published-readme-exports) are read from CI:Build Core,Type Check · source gates,Type Check · consumer gatesandType Check · debt ledgerread success at my second read;Lint & Repo GatesandType Check · workspacewere still running (below). (4) The spec suite narrowed locally to 8 files —Test Core (1–6/6)carries the full suite. (5) Model-free commit trailers per AGENTS.md — no model identifier appears in the body, the commits or the changeset. (6) The detached gate battery — process hygiene, no bearing on the diff.6064067917); answered in ① (no hand-written page carries the sentence; the release-owned page is read-only and untouched). No review comments, no reviews.acba7086d, read twice. First read, 2026-10-08T16:16Z: 36 runs — 15 success, 5 skipped (expected opt-in and auto-label skips), 16 in_progress, 0 failure. Second read, 2026-10-08T16:23Z: 37 runs — 24 success, 5 skipped, 8 in_progress (Lint & Repo Gates,Test Core (1/6)–(6/6),Type Check · workspace), 0 failure. Every settled gate is green; the seat reads the remaining eight green itself before landing, per the brief.Implemented-by:
claude/issue-22093-remainder-a-submit-behaviorReviewed-by:
session_01RPo7FUd6bSnAfkWMAKi848VERDICT: PASS
objectstack-fleet commentedon Oct 9, 2026
Landed: PR #22322 →
ad381fd943(Fixes #22093, the card's last remainder). The card is closeddomain:specseat 3 (#18883) ·os-justin· sessionsession_01RPo7FUd6bSnAfkWMAKi848· 2026-10-09T00:47Z · holder of claim6063214700, closed by this act.os-zhuangapproved PR fix(spec): the submitBehavior property help carries no ruling date #22322 atacba7086d(2026-10-08T23:58Z) and enqueued it. It merged through the merge queue at 2026-10-09T00:35Z asad381fd943, which has one parent,16096e8d7b, and is an ancestor oforigin/main. GitHub closed this card on the merge.acba7086d(ACCEPT6064160628; contract review PASS6064355753).content/docs/references/api/protocol.mdx, whichmainalso moved (fix(metadata): the object read's sortability follows the caller's field permission for every caller class (#22250) #22308, other rows). This PR's own 4 changed lines there landed identically: its diff from the merge base matches the squash's diff from its parent.submitBehaviorhelp reads "… URL-escaped." in Studio, the reference docs and the publishedobjectstack-uiskill. The ruling date and spec: rule the submitBehavior.url semantics — objectui#4190 dead-ends on it #7496 live in the code comment.This act removes
pm:dispatched; the domain, area and type labels stay.Generated by Claude Code
fix(spec): author-facing describes and refusals drop service-interfac…
fix(platform-objects): sys_email field help names no service interfac…
fix(spec): the submitBehavior property help carries no ruling date (o…
test(spec): the class (e) remainder's titles state each cited decisio…