Skip to content

rest + sdk: tenant-admin door for the record-currency backfill — a REST twin of os migrate record-currency-codes, scoped to the caller's organization (multi-currency, epic #8345, ruling C item B) #30675

Closed

Description

@objectstack-fleet

epic:#8345#1 · os-bill · session_01Utp6CZWjnSeed8H4dMjt9d

Blocked-by: #30674

Part of #8345. Filed by the epic PM from ruling 6670707546 (letter C, maintainer 「同意」 2026-11-05T21:57Z), item B. ⛔ Not a claim.

Ruling-ref: 6670707546

维护者速读

  • 改了什么: 给币种码回填开一个 REST 入口(仿已有的 POST /meta/_migrate-stored):{ object, field, code, apply? },只作用于调用者当前组织,默认预览,apply: true 才写,返回与 CLI 相同的报告;配 SDK 方法与路由台账行;409 destructive_change 的处方文案同时点名这个入口。
  • 为什么改: 云托管租户没有 shell,跑不了 CLI;本位币折算在 18.3.0 已上线,切了模式的云租户今天就在静默漏算。
  • 风险与回滚: 中低。新增一条路由,只写本组织,受维护开关控制。回滚即撤 PR。
  • 先看: packages/rest/src/meta-collection-routes.ts 的 _migrate-stored。
  • 两分钟验证: 两个组织:A 的管理员调预览只看到 A 的行;apply: true 只写 A。
  • 需要您做的: 无(门禁按裁决推荐取 setup.write;如需改为 manage_metadata 请在卡上说)。

Task

  • One route shaped on POST /meta/_migrate-stored, taking { object, field, code, apply? }, calling the A card's backfillRecordCurrencyCodes with organizationId = the caller's current organization. ⛔ No environment-wide form, no organizationId in the body.
  • Preview unless apply: true; the response is the same report the CLI prints (--json shape).
  • Gate: setup.write in the caller's current organization (the ruling's recommendation; the maintainer named none, manage_metadata is the alternative). Behind the maintenance-door switch, as _migrate-stored is.
  • SDK client method, route-ledger row, error codes from the existing registry only (no new wire code).
  • The 409 destructive_change remedy text names this door beside the CLI command.
  • Tests: two organizations; caller sees and writes only its own; missing capability refused; maintenance switch off refused; preview writes nothing.
  • Docs: the endpoint beside the CLI command; note that a Setup button is a separate domain:ui card, held until the ui lane resumes.

Lane: server · Size: M · Model: opus, effort high · Review: default. Clause-②: yes (widening: new route, new SDK method) · Metadata-protocol: none.

Out of scope: the Setup/Studio button (domain:ui, ui lane is off); docs/adr/**, skills/**.

Dedupe: #8345 thread; ruling 6670707546; no open card carries a REST form of this backfill.

Activity

  1. added
    enhancementNew feature or request
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queue
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed
    pm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queue
    on Nov 5, 2026
  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

    Handover: epic #8345 → domain:server queue. The epic seat signed off (6670841501); the maintainer agreed the remaining cards go to the server lane. Labels and dependencies only; this seat is signing off and does not dispatch it.
    Maintainer: 「同意」, project chat, 2026-11-05T22:14Z.
    State change: pm:epic off; it stays pm:blocked behind Blocked-by: #30674 (body). Unlock when #30674's PR merges.

  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p2 kept · domain:server · pm:blocked · 2026-11-05T22:56Z

    ⛔ Not a claim.


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Unblock · 2026-11-06T02:31Z · Blocked-by: #30674 closed

  5. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  6. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Claim: PM loop round 4 · 2026-11-06T02:34Z
    Session: session_016WV4cytLmJxfAmgPVjUbbW
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-30675-record-currency-backfill-rest (placeholder; the card thread's harness-pinned branch replaces it by amendment before any push)
    Worktree: objectstack-issue-30675
    Domain: domain:server
    Seat: domain:server#1
    File surface: packages/rest/** (the new route beside _migrate-stored, its tests, the route-ledger row), the SDK client method and its test, the backfill's report types re-exported from the package that owns backfillRecordCurrencyCodes, the 409 destructive_change remedy text, content/docs/** (the endpoint beside the CLI command), one root .changeset/ file. ⛔ packages/spec/** (stop and ask), ui/**, docs/adr/**, skills/**, .claude/**. Stop on breach and explain in the report
    Container & model: M, mode:card-thread, model: default, effort: high
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: the tenant-admin REST door for the record-currency backfill (packages/rest, SDK) | none (the backfill has a CLI only) | a cloud-managed tenant admin with no shell, whose organization's stored rows lack a currency code after switching to multi-currency
    Thread-read: none
    Premise: holds per ruling 6670707546 item B and the card body; the A card (#30674, PR #30737) merged, so backfillRecordCurrencyCodes takes an organization scope. The dev re-reads meta-collection-routes.ts _migrate-stored on origin/main first
    Serial constraints cleared: #30674 closed (PR #30737 merged e2bcd5c15e); no open PR names this card. Carried from the #30674 ACCEPT (6673657336): barrel re-export of the report types, and a truncated preview that says it is truncated.

  7. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    席位核对 · Amendment · 2026-11-06T02:36Z · claim 6675312689

    Branch: claude/project-thread-1lyw0v (the card thread's harness-pinned branch, objectstack-ai/objectstack; platform exception to claude/issue-<n>-<slug>)
    Dev session: session_016WV4cyrhYJgbkZHbbqud3F
    Everything else in the claim stands.

  8. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    席位核对 · Amendment · 2026-11-06T02:43Z · claim 6675312689

    File surface adds scripts/check-undeclared-dep-imports.mjs, ONE LEDGER row of kind optional-runtime-probe for the new packages/rest/src/meta-record-currency-backfill-route.ts. Why: @objectstack/objectql is only a devDependency of @objectstack/rest, and the tree already records the decision that rest is not coupled to the engine (the existing row for packages/rest/src/meta-object-state-routes.ts), which is the ledger's one admission criterion. The door loads backfillRecordCurrencyCodes through a guarded dynamic import() and answers 501 NOT_IMPLEMENTED when it cannot load, as that precedent does.

    Not admitted: a type-only row for packages/client/src/index.ts. The SDK method stays untyped like migrateStored (ruling 2C precedent) rather than growing the ledger. The dispatch order's "the SDK and this door both name them" is withdrawn for the SDK; the report types are still re-exported from the @objectstack/objectql barrel.

    Everything else in the claim and its amendments stands.

  9. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cyrhYJgbkZHbbqud3F
    os-dev-report

    {
      "issue": 30675,
      "status": "done",
      "branch": "claude/project-thread-1lyw0v",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30809",
      "session": "session_016WV4cyrhYJgbkZHbbqud3F",
      "premise_still_valid": true,
      "summary": "New tenant-admin door POST /api/v1/meta/_migrate-record-currency-codes (packages/rest/src/meta-record-currency-backfill-route.ts), behind metadata.endpoints.maintenance, mounted on every /meta base. Gate setup.write (or isSystem) first, then the caller's current organization (ctx.tenantId; none = 403), then a closed body { object, field, code, apply? } (organizationId / allOrganizations / any other key = 400 VALIDATION_ERROR), then backfillRecordCurrencyCodes with organizationId = the current organization; preview unless apply: true, answered { success: true, data: <the CLI --json report> }. SDK client.meta.migrateRecordCurrencyCodes (returns unknown), route-ledger row, undeclared-dep LEDGER row, 409 currency_mode_to_record remedy names the door, docs beside the CLI command. Carried items: objectql barrel re-exports the two report types; a truncated walk says so in the shared report text and the CLI warns. Driving the door through the rest hook-refusal ledger found a leak, fixed in objectql: a row a sandboxed hook refused reported the `hook '<name>' threw:` debug wrapper (a crash its native text); it now reads the throw as the import row does.",
      "tests": "New route test 12/12 (real ObjectQL, two orgs: each admin sees/writes only its own rows, preview writes nothing, missing setup.write 403 with provider never called, no current org 403, org in body 400, malformed 400, INVALID_FIELD / 404 pass-through, no engine 501, maintenance off unmounts, scoped base mounts). Census rows added: /meta write-door enumeration (DATA_DOORS: anonymous 401; no capability / manage_metadata / setup.access alone 403; setup.write and isSystem pass on both bases), #8497 tripwire, #22694 hook-refusal ledger (§2 + §6, 12 shapes), objectQLProvider census 6->7, query-slot census, docblock floors, mount-table pin. objectql: truncated-walk test + 3 sandboxed-row tests; cli: --max-records warning; client: 2 descriptor tests + unknown return pin. Whole packages at b56a12d6's tree: rest 396 files / 7710 passed (354 skipped); objectql 557 files / 11089 passed; client 54 / 703; metadata-protocol 319 (+4 skipped) / 22480; cli --project integration src/commands/migrate/ 49 (+1 skipped) / 375; dogfood authz-probe-blind-spot + authz-conformance 70 passed. tsc --noEmit rest/client/metadata-protocol/objectql exit 0. Ablation: rowFailureSentence(...) -> message in record-currency-backfill.ts, objectql rebuilt (rest reads its dist): hook ledger 12 failed | 613 passed; restored + rebuilt: 625 passed.",
      "gates": "dispatch-gates --commands at b56a12d6: 116 families, 116/116 exit 0; --ran: 116 derived, 116 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). Pass 1 (earlier head, 115 families) had one finding: check:system-context-census exit 1 (the door's isSystem bypass owned by no row) -> row 50b in content/docs/permissions/system-context.mdx + --write. Pass 2 had one prerequisite miss: spec check:docs exit 1, json-schema older than src (local gitignored artifact staled by preparing the held spec hunk); gen:schema then check:docs exit 0. Whole-tree turbo build 119/119 under the verify lock, VERDICT command-exit 0. pnpm lint exit 0. CI check-runs on #30809: not yet read (just opened).",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "2 — read only: mcp__github__pull_request_read get #30809 (draft read-back); plus mcp__claude-code-remote__subscribe_pr_activity #30809 (session subscription, not a GitHub write). No MCP GitHub write tool.",
      "api_writes": "2 relay writes: (1) pr_create via scripts/pm/fleet-write/dispatch.mjs (request fw-20261106T043412Z-665de0, run 42163290222, read-back 14068 bytes sent = stored, assignees none); (2) this os-dev-report via scripts/pm/post-stamped.mjs -> POST /repos/objectstack-ai/objectstack/issues/30675/comments. Not REST: git push to claude/project-thread-1lyw0v (be390f72, acf60146, f229b660 merge of main, 415ea427, b56a12d6).",
      "open_questions": [
        {
          "question": "main now assigns the claim's Account on pr_create (PR #30796, landed 03:51Z, after this order); the standing line said no assignees. #30809 was opened without one. Add the assignee?",
          "options": [
            "A the seat adds the claim's Account as assignee via the relay",
            "B leave it unassigned"
          ],
          "recommendation": "A, because the rule on main now requires it and H76 reports an unowned open PR"
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: seat · the held domain:spec hunk (maintenance describe() names the door + regenerated rest-server.mdx + '@objectstack/spec': patch in the changeset) is commit 12af728f on local branch spec-30675-describe, patch at /mnt/project-files/server-lane-seat1/30675/spec-describe.patch; unpushed until consent",
        "class: c · reach: named producer skills/objectstack-data/rules/field-types.md:174 · its backfill hint names only the CLI; a hosted admin's door is now POST /meta/_migrate-record-currency-codes · dedupe words: field-types record-currency backfill hint REST door · carrier: #30740 / PR #30776 touches that line, noted, not filed",
        "class: c · reach: backfill row failures that are not sandboxed keep the raw message (a driver's SQL text could reach a tenant admin over REST); the import row passes such text through sanitizeRowError, which @objectstack/core does not export · dedupe words: backfill failure sanitizeRowError driver text · noted, not filed"
      ],
      "line_budget": "27 files, +1027 / -16 vs main",
      "deviations": [
        "SDK method returns unknown (the seat's 'untyped like migrateStored'): an any return reds the shrink-only exported-any-returns.json.",
        "The door answers the shared envelope (sendOk/sendError) where _migrate-stored answers bare: check:route-envelope asks new modules for the shared pair.",
        "objectql record-currency-backfill.ts changed beyond the carried items (rowFailureSentence), because the hook-refusal ledger the new door must join found the wrapper leak; the CLI's --json report reads the same sentence now.",
        "Test-harness edits in shared rest ledgers (hook-refusal caller gains setup.write + tenantId, engine double gains getObject); the whole files stay green.",
        "Two local commits were first written with a model-named co-author trailer and reworded to the model-free pair before push; no pushed history rewritten.",
        "PR opened without assignees or reviewers per the standing line; see open question 1."
      ]
    }
  10. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Claim: PM loop round 4 · 2026-11-06T04:46Z (re-post of 6675312689 with the card thread's branch and amendments 6675340866 and 6675450053 folded in; it supersedes that comment)
    Session: session_016WV4cytLmJxfAmgPVjUbbW (seat); dev session session_016WV4cyrhYJgbkZHbbqud3F
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-1lyw0v (the card thread's harness-pinned branch, objectstack-ai/objectstack)
    Worktree: objectstack-issue-30675
    Domain: domain:server
    Seat: domain:server#1
    File surface: packages/rest/** (the new route beside _migrate-stored, its tests, the route-ledger row), the SDK client method and its test, the backfill's report types re-exported from the package that owns backfillRecordCurrencyCodes, the 409 destructive_change remedy text, content/docs/** (the endpoint beside the CLI command), one root .changeset/ file. ⛔ packages/spec/** (stop and ask), ui/**, docs/adr/**, skills/**, .claude/**. Stop on breach and explain in the report
    Amended surface (folded in): scripts/check-undeclared-dep-imports.mjs, one LEDGER row of kind optional-runtime-probe (amendment 6675450053); the one maintenance describe hunk in packages/spec/src/api/rest-server.zod.ts plus its regenerated content/docs/references/api/rest-server.mdx row, under domain:spec consent 6675431663; the objectql rowFailureSentence fix and the rest write-door census rows the door joins (dev report 6677009085)
    Container & model: M, mode:card-thread, model: default, effort: high
    Clause-②: yes (widening)
    Metadata-protocol: none
    Responsibility: the tenant-admin REST door for the record-currency backfill (packages/rest, SDK) | none (the backfill has a CLI only) | a cloud-managed tenant admin with no shell, whose organization's stored rows lack a currency code after switching to multi-currency
    Thread-read: none
    Premise: holds per ruling 6670707546 item B and the card body; the A card (#30674, PR #30737) merged, so backfillRecordCurrencyCodes takes an organization scope. The dev re-reads meta-collection-routes.ts _migrate-stored on origin/main first
    Serial constraints cleared: #30674 closed (PR #30737 merged e2bcd5c15e); no open PR names this card. Carried from the #30674 ACCEPT (6673657336): barrel re-export of the report types, and a truncated preview that says it is truncated.

  11. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

    Landed · 2026-11-06T06:02Z · PR #30809 merged by the queue as 0cba0ce2da (2026-11-06T06:01:04Z). A tenant admin can now fill missing currency codes for their own organization without a shell, through POST /api/v1/meta/_migrate-record-currency-codes or client.meta.migrateRecordCurrencyCodes. A truncated walk now says that it is truncated, and a sandboxed hook's refusal reads as the hook's own sentence. This card is closed by Fixes. pm:dispatched removed. Follow-up filed: #30816 (raw driver text in a non-sandboxed row failure). Recorded for a later batch: the conformance authz cell of the new ledger row.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions