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
service-settings: lock localization.currency once a sys_exchange_rate row or a homeAmount declaration exists; Setup shows what uses it (multi-currency M1-3, epic #8345) #27198
Filing gate ③ (maintainer-directed). Ruling 6465431966 on #27119 (batch #421 item 2, Q1 = 乙-现在, maintainer 「同意立专题卡」 2026-10-28): the multi-currency epic resumes on #8345 in shape C′. Part of #8345. Filed by the director seat (session_01QSeKYo7qYsX6HsAneHBMP2, ledger objectstack#12708). ⛔ Not a claim. Workload row and precedents: 6464936321; design: 6464438764.
Ruling-ref: 6465431966
维护者速读
改了什么: 有汇率行或影子列声明后,组织默认币种不可再改;Setup 明示「已被 N 个对象 / M 条汇率使用」。
A write-side predicate for the manifest key, reading the engine (any rate row; any object whose metadata declares homeAmount).
Setup copy naming the users.
Tests: before / after the first rate row.
Lane: server · Size: M · Review: default. Clause-②: no · Metadata-protocol: none. New mechanism: name it in the PR body.
Epic: #8345 (parent; the epic PM dispatches pm:epic children; domain:spec children go to the spec seat's queue). Dedupe: #8345 thread, #27119 (ruled), the workload list 6464936321; no open card carries this slice.
Claim: PM loop round 1 · 2026-10-31T02:56Z
Session: session_01Utp6CbTZrFqRYNgjSaSbN5 (epic PM, epic:#8345)
Account: os-bill
Branch: claude/project-thread-1bmr1f (card thread session_01Utp6CYUAEJV7QpzuJm62Te; the platform assigns the thread's one push branch — the recorded platform exception), cut from PR #28096's head cf13692214 (#27191 not yet on main; Merge-after: #28096, main merged in once it lands, never rebased)
Worktree: objectstack-issue-27198
Domain: domain:server
Seat: epic:#8345
File surface: packages/services/service-settings/src/** (the write-side predicate on localization.currency, its Setup copy and translations, tests), .changeset/*; ⛔ no governed path, ⛔ no packages/spec/src/**, ⛔ no packages/objectql write path (stop on breach; explain in the report)
Container & model: M, mode:thread, model: opus (TIER_DEFAULT; dispatch-gates --tier: no path-derived mandate), effort: default (the platform's; only the maintainer sets thread effort)
Clause-②: no — to confirm against the tree (a new refusal on a settings save is a narrowing; say which)
Metadata-protocol: none — to confirm against the tree
Responsibility: n/a — not a defect card
Thread-read: none
Premise: holds
Serial constraints cleared: #27185 landed (PR #27246, 194e66c0); #27191 approved and in auto-merge (PR #28096), built on its head with Merge-after: #28096.
Release pairing: R2 — ⛔ not released alone; R2's release waits for the customer's terms (ruling 6522614678).
Maintainer authority: Bill, project thread, 2026-10-28 「epic的卡你统一派」, 「并发可以5」, 「你自主完成后续阶段」; ruling 6522614678 (letter A, 「A 现在启动 R2 开发」).
{
"issue": 27198,
"status": "done",
"branch": "claude/project-thread-1bmr1f",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28297",
"head": "5e49fa73b3b40666dfcd0bd22116cdea5ccc1a3e",
"session": "session_01Utp6CYUAEJV7QpzuJm62Te",
"premise_still_valid": true,
"summary": "Real lock, not the warning-first step: the settings write path hosts a predicate cleanly. New mechanism, named in the PR: the settings write predicate, SettingsService.registerWritePredicate(namespace, key, predicate). setMany asks it only when the write changes the key (submitted vs the value the caller's cascade resolves), after validatePatch and before the crypto pass and every write; refusals refuse the whole batch as SettingsValidationError, invalid_value at the key, with constraint. The lock (localization-currency-lock.ts), registered by SettingsServicePlugin right after bindEngine: used = the organization's sys_exchange_rate rows (system count, explicit organization filter) or any registered object declaring currencyConfig.homeAmount on a field. A change to a SET currency (other code or clear) is refused while used; the message names N objects (labels) and M exchange rates; constraint { lockedBy: 'in_use', objects, exchangeRates }. Unset -> first value passes (ADR-0144 Open item 4, this card's half). Setup copy = the Localization page's live-status banner (locked: info; unset but needed: warning; nothing while unused), in en / zh-CN / ja-JP / es-ES, picked from the request's Accept-Language (else ?locale) and passed as SettingsDisplayOptions beside the context, never inside it (the door pins hold context identity). Base: #28096's head cf136922 (not on main); PR says Merge-after: #28096; main merges in once it lands, no rebase. Declarations: Clause-②: yes (widening) for the new service-settings exports; Metadata-protocol: none; changeset service-settings patch. Corrected from the card's Clause-② no: the new exports widen the public face; the refusal narrows nothing released today (sys_exchange_rate and homeAmount are in no published version).",
"tests": "head 5e49fa73: @objectstack/service-settings 67 files / 1107 tests passed; tsc --noEmit exit 0; eslint on touched files exit 0. New: settings-write-predicate.test.ts 7/7, localization-currency-lock.test.ts 24/24 (save succeeds before the first rate row and before any homeAmount; refused after each, message names the users; clear and namespace reset refused; re-save passes; unset -> set passes; lock lifts; refusal and banner in all four locales; PUT 400 SETTINGS_VALIDATION). Ablations, each restored to HEAD: setMany call removed 16/31 red; isUsed -> false 13 red; homeAmount detector blinded 8 red; organization filter dropped 2 red; change gate dropped 2 red.",
"gates": "dispatch-gates --commands on 5e49fa73: 63 commands (9 paths vs #28096 head). All ran with exit captured before any pipe: 62 exit 0, 1 exit 3 (check:dual-build-cjs-loads, PREREQUISITE NOT MET, needs every package's dist; CI Build Core measures it). dispatch-gates --ran: 63 accounted, 62 run, 1 NOT-MEASURED, exit 0. Records: /mnt/project-files/epic-8345/gates-27198-{cmds,ran,ran-verdict}.txt. CI on the PR: not yet read at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "5 — issue_read #27198, pull_request_read #28096, create_pull_request #28297 (draft, no assignee, no reviewers), subscribe_pr_activity #28297, add_issue_comment (this report)",
"api_writes": "git push to claude/project-thread-1bmr1f; MCP create_pull_request #28297; MCP add_issue_comment on #27198. No fleet relay credentials in this container.",
"deviations": [
"PR body first line is the seat signature, attribution block on lines 3-4 (the precedent of #28128).",
"WIP commit 1fb66e7c carries the epic PM's session id in its Claude-Session trailer by mistake; later commits carry this thread's session. The branch cannot be force-pushed (criterion 1), so it stays."
],
"open_questions": [
"Version Packages #26517 (18.3.0) already consumed .changeset/27185-exchange-rate-table.md (changeset-release/main 1f5d5a8e). If 18.3.0 ships before R2, sys_exchange_rate is released ahead of its R2 pair, and this PR's refusal then narrows a released state: the arm must become (narrowing) with an ADR-0087 disposition before R2.",
"homeAmount declarations are deployment metadata, so per the card's predicate every organization's set currency locks once one exists, even an organization with no converted rows. Kept as written; narrowing it to organizations that hold converted rows would need a data read across objects."
],
"out_of_scope_findings": [
"class: release-pairing; reach: changeset-release/main 1f5d5a8e carries #27185's changeset in 18.3.0 while #28096 lists #27185 in the R2 set; carrier: epic PM / Bill (Version Packages is the maintainer's hand)"
]
}
Activity
objectstack-fleet commentedon Oct 31, 2026
epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5
Claim: PM loop round 1 · 2026-10-31T02:56Z
Session:
session_01Utp6CbTZrFqRYNgjSaSbN5(epic PM,epic:#8345)Account:
os-billBranch:
claude/project-thread-1bmr1f(card threadsession_01Utp6CYUAEJV7QpzuJm62Te; the platform assigns the thread's one push branch — the recorded platform exception), cut from PR #28096's headcf13692214(#27191 not yet onmain;Merge-after: #28096, main merged in once it lands, never rebased)Worktree:
objectstack-issue-27198Domain:
domain:serverSeat:
epic:#8345File surface:
packages/services/service-settings/src/**(the write-side predicate onlocalization.currency, its Setup copy and translations, tests),.changeset/*; ⛔ no governed path, ⛔ nopackages/spec/src/**, ⛔ nopackages/objectqlwrite path (stop on breach; explain in the report)Container & model:
M,mode:thread,model: opus (TIER_DEFAULT; dispatch-gates --tier: no path-derived mandate),effort: default (the platform's; only the maintainer sets thread effort)Clause-②: no — to confirm against the tree (a new refusal on a settings save is a narrowing; say which)
Metadata-protocol: none — to confirm against the tree
Responsibility: n/a — not a defect card
Thread-read: none
Premise: holds
Serial constraints cleared: #27185 landed (PR #27246,
194e66c0); #27191 approved and in auto-merge (PR #28096), built on its head withMerge-after: #28096.Release pairing: R2 — ⛔ not released alone; R2's release waits for the customer's terms (ruling 6522614678).
Maintainer authority: Bill, project thread, 2026-10-28 「epic的卡你统一派」, 「并发可以5」, 「你自主完成后续阶段」; ruling 6522614678 (letter A, 「A 现在启动 R2 开发」).
os-bill commentedon Oct 31, 2026
epic:#8345#1 · os-bill · session_01Utp6CbTZrFqRYNgjSaSbN5
os-dev-report
{ "issue": 27198, "status": "done", "branch": "claude/project-thread-1bmr1f", "pr": "https://github.com/objectstack-ai/objectstack/pull/28297", "head": "5e49fa73b3b40666dfcd0bd22116cdea5ccc1a3e", "session": "session_01Utp6CYUAEJV7QpzuJm62Te", "premise_still_valid": true, "summary": "Real lock, not the warning-first step: the settings write path hosts a predicate cleanly. New mechanism, named in the PR: the settings write predicate, SettingsService.registerWritePredicate(namespace, key, predicate). setMany asks it only when the write changes the key (submitted vs the value the caller's cascade resolves), after validatePatch and before the crypto pass and every write; refusals refuse the whole batch as SettingsValidationError, invalid_value at the key, with constraint. The lock (localization-currency-lock.ts), registered by SettingsServicePlugin right after bindEngine: used = the organization's sys_exchange_rate rows (system count, explicit organization filter) or any registered object declaring currencyConfig.homeAmount on a field. A change to a SET currency (other code or clear) is refused while used; the message names N objects (labels) and M exchange rates; constraint { lockedBy: 'in_use', objects, exchangeRates }. Unset -> first value passes (ADR-0144 Open item 4, this card's half). Setup copy = the Localization page's live-status banner (locked: info; unset but needed: warning; nothing while unused), in en / zh-CN / ja-JP / es-ES, picked from the request's Accept-Language (else ?locale) and passed as SettingsDisplayOptions beside the context, never inside it (the door pins hold context identity). Base: #28096's head cf136922 (not on main); PR says Merge-after: #28096; main merges in once it lands, no rebase. Declarations: Clause-②: yes (widening) for the new service-settings exports; Metadata-protocol: none; changeset service-settings patch. Corrected from the card's Clause-② no: the new exports widen the public face; the refusal narrows nothing released today (sys_exchange_rate and homeAmount are in no published version).", "tests": "head 5e49fa73: @objectstack/service-settings 67 files / 1107 tests passed; tsc --noEmit exit 0; eslint on touched files exit 0. New: settings-write-predicate.test.ts 7/7, localization-currency-lock.test.ts 24/24 (save succeeds before the first rate row and before any homeAmount; refused after each, message names the users; clear and namespace reset refused; re-save passes; unset -> set passes; lock lifts; refusal and banner in all four locales; PUT 400 SETTINGS_VALIDATION). Ablations, each restored to HEAD: setMany call removed 16/31 red; isUsed -> false 13 red; homeAmount detector blinded 8 red; organization filter dropped 2 red; change gate dropped 2 red.", "gates": "dispatch-gates --commands on 5e49fa73: 63 commands (9 paths vs #28096 head). All ran with exit captured before any pipe: 62 exit 0, 1 exit 3 (check:dual-build-cjs-loads, PREREQUISITE NOT MET, needs every package's dist; CI Build Core measures it). dispatch-gates --ran: 63 accounted, 62 run, 1 NOT-MEASURED, exit 0. Records: /mnt/project-files/epic-8345/gates-27198-{cmds,ran,ran-verdict}.txt. CI on the PR: not yet read at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "5 — issue_read #27198, pull_request_read #28096, create_pull_request #28297 (draft, no assignee, no reviewers), subscribe_pr_activity #28297, add_issue_comment (this report)", "api_writes": "git push to claude/project-thread-1bmr1f; MCP create_pull_request #28297; MCP add_issue_comment on #27198. No fleet relay credentials in this container.", "deviations": [ "PR body first line is the seat signature, attribution block on lines 3-4 (the precedent of #28128).", "WIP commit 1fb66e7c carries the epic PM's session id in its Claude-Session trailer by mistake; later commits carry this thread's session. The branch cannot be force-pushed (criterion 1), so it stays." ], "open_questions": [ "Version Packages #26517 (18.3.0) already consumed .changeset/27185-exchange-rate-table.md (changeset-release/main 1f5d5a8e). If 18.3.0 ships before R2, sys_exchange_rate is released ahead of its R2 pair, and this PR's refusal then narrows a released state: the arm must become (narrowing) with an ADR-0087 disposition before R2.", "homeAmount declarations are deployment metadata, so per the card's predicate every organization's set currency locks once one exists, even an organization with no converted rows. Kept as written; narrowing it to organizations that hold converted rows would need a data read across objects." ], "out_of_scope_findings": [ "class: release-pairing; reach: changeset-release/main 1f5d5a8e carries #27185's changeset in 18.3.0 while #28096 lists #27185 in the R2 set; carrier: epic PM / Bill (Version Packages is the maintainer's hand)" ] }Generated by Claude Code