Skip to content

Setup: replace developer wording and hand-written JSON with fields a business administrator can use #30208

Open

Description

@objectstack-fleet

epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

From a real-user walkthrough of Setup, filed on the maintainer's word: 「以上都同意,立卡」 (2026-11-05). Measured on main b7075d9908, with the console in dev mode, unless a line says otherwise. Test app scaffolded with create-objectstack@18.3.0.

Items

# Where Observed Suggestion
1 New permission set assignment fail-closed, 半开区间 [from, until), 智能体授权 in help text Business wording
2 New business unit 图谱语义, Null 表示租户根节点, HRIS, 图谱查询 Business wording
3 Edit organization 「为 true 时…」 「开启后…」
4 Permission set assignment 已绑定到 everyone 锚点 「所有登录成员都有」
5 Settings → 数据生命周期 Retention, row quota and class quota are raw JSON boxes Rows with an object picker and a duration
6 通知偏好 Topic and channel are free text, default *, and a wrong value silently matches nothing; 免打扰 is JSON Pick from known topics and channels; a time range control
7 队列, 队列成员 Whole pages are English in a Chinese console: titles, columns, help (新建Queue) Add the translations
8 邮件投递 Status banner No mail leaves this machine, and the test toast Sent test email to…, are English Localize
9 Setup list headers Badges 身份, 管理配置, 平台架构 say nothing to an administrator Remove, or explain

Take the rows independently. Translation rows are the cheapest and do not wait on the others.

Activity

  1. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p3 · domain:ui · pm:queue · 2026-11-05T03:04Z

    ⛔ Not a claim.

    • Read fresh. Setup forms use developer wording and hand-written JSON where a business administrator needs fields. p3.

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

    One more row of the same kind, on main 04f36b63f8: Setup's 通知模板 new form takes topic and channel as free text, so a typed value that matches nothing fails with no message. Pick from known topics and channels, as this card asks for 通知偏好.

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#6 · os-sales · session_01L6bhgUR8Nu4M7tbtgES7q2

    Claim: PM loop round 3 · 2026-11-05T12:19Z
    Session: session_01L6bhgUR8Nu4M7tbtgES7q2
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: the card thread's designated claude/project-thread-* branch, named in a claim amendment once the thread reports it
    Worktree: objectstack-issue-30208
    Domain: domain:ui
    Seat: domain:ui#6
    File surface: the rows whose fix lands in ui/** (row 4 ui/packages/app-shell/src/views/metadata-admin/AssignedUsersSection.tsx, rows 7 and 9 where the text is drawn by ui/, rows 1–3 and 8 only where the string lives under ui/), their tests, ui/packages/i18n locale entries they need, one root .changeset/ file. ⛔ packages/** (row 8's banner and rows 1–3's help texts are served from packages/plugins/plugin-email and packages/platform-objects translations: domain:server), ⛔ rows 5 and 6 (new field controls: report them as a remainder unless they are a pure ui/ control over an existing field shape). Rows not done are named in the report for the seat to split (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
    Clause-②: no (a new language-pack key, if a row needs one, is declared by the dev in the PR body and the seat re-reads this line at ACCEPT)
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6651344343
    Premise: holds in part, read at origin/main 677ac3bb: row 4's 「已绑定到 everyone 锚点」 is ui/packages/app-shell/src/views/metadata-admin/AssignedUsersSection.tsx:161; row 3's 「为 true 时」 and row 8's banner are server-side strings (packages/platform-objects, packages/plugins/plugin-email); the dev re-measures each row first
    Serial constraints cleared: PR #30243 edits ui/packages/i18n/src/locales/zh.ts (other keys; normal concurrency, the later lander merges main). No maintainer ruling beyond the filing word 「以上都同意,立卡」.

  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#6 · os-sales · session_01L6bhgUR8Nu4M7tbtgES7q2

    席位核对 · Amendment · 2026-11-05T12:23Z · claim 6657817034 branch named

    Branch: claude/project-thread-9po7qw

    The card thread's harness-designated branch (a project-thread platform exception to the claude/issue-<n>-<slug> convention); the dev thread is session session_01L6bhgWjg9S7avVTjuNn9P2. Everything else in claim 6657817034 stands.

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#6 · os-sales · session_01L6bhgWjg9S7avVTjuNn9P2
    os-dev-report

    {
     "issue": 30208,
     "status": "rework",
     "branch": "claude/project-thread-9po7qw",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/30488",
     "session": "session_01L6bhgWjg9S7avVTjuNn9P2",
     "premise_still_valid": true,
     "summary": "Partial by design (Refs, not Fixes): ships rows 4 and 9, the two rows whose text ui/ draws. Row 4: AssignedUsersSection's everyoneNote no longer names the everyone anchor (zh 「所有登录成员都持有此权限集。」, en 'Every signed-in member holds this set.'). Row 9 takes the card's 'explain' arm: the managedByBadge short labels for config / system-data / better-auth now say what they mean for the administrator (zh 在此配置 / 平台预置 · 可编辑 / 经身份服务写入; en Configured here / Built in · editable / Via identity service) in all ten ui language packs, with ManagedByBadge.tsx's English fallbacks matched; no key added or removed, tooltip text and the draw rule (objectui#12194, objectui#13385) unchanged. Rows 1, 2, 3, 5, 6, 7, 8 and the 6651344343 通知模板 row are not shipped; reasons and owning lanes in out_of_scope_findings and the PR's Acceptance notes.",
     "tests": "pnpm --dir ui exec vitest run on AssignedUsersSection.test.tsx + ManagedByBadge.keyboard-27521.test.tsx: 2 files, 22/22 passed, lock VERDICT command-exit 0. Positive control: the 4 source files restored to base c5800818 with the new tests kept: 4 failed / 18 passed (exactly the 4 non-control new cases), restored with git checkout HEAD --, git diff HEAD empty. Wider: packages/i18n/ + app-shell metadata-admin/ + components/ + 5 managed-by badge view files: 825/825 files, 9149 passed 14 skipped, VERDICT 0. After merging origin/main (head a5f5ec0b): console closure rebuilt (73 tasks), packages/i18n/ + the two files: 97/97 files, 1465 passed 13 skipped, VERDICT 0. No ablation beyond the positive control: copy-only change.",
     "gates": "dispatch-gates --commands at cd7b73ac derived 35 families; all 35 exit 0; --ran: 35 derived, 35 run, 0 NOT-MEASURED (derived zero, every line with an exit code). Local extras: turbo type-check @object-ui/app-shell + @object-ui/i18n VERDICT 0; pnpm --dir ui run check:spec-symbols exit 0; eslint on changed files + packages/i18n/src/locales exit 0; pnpm --dir ui run check:new-line-citations 0 new, exit 0; pnpm check:doc-authoring exit 0; pnpm check:pm-skill-id-lint exit 0. CI check-runs on PR #30488: in_progress at report time (not waited on).",
     "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
     "mcp_calls": "5 — issue_read x2 (card, comments), pull_request_read x2 (PR #30442 for body shape, PR #30488 read-back); no MCP write tool",
     "api_writes": "2 — fleet-write relay pr_create (run 42034634236, #30488, body read back identical 11159 bytes, no assignees) and this os-dev-report comment via post-stamped. git push of claude/project-thread-9po7qw and of the evidence branch claude/issue-30208-evidence are not REST writes.",
     "open_questions": [],
     "security_findings": "none",
     "out_of_scope_findings": [
      "carrier: seat (split) · row 1 · permission set assignment help texts (fail-closed, 半开区间 [from, until), 智能体授权) are served from packages/plugins/plugin-security/src/translations/zh-CN.objects.generated.ts (半开区间 also plugin-approvals) · domain:server",
      "carrier: seat (split) · row 2 · business unit help (图谱语义, Null 表示租户根节点, HRIS, 图谱查询) in packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts · domain:server",
      "carrier: seat (split) · row 3 · 「为 true 时…」 on organization and other booleans in the same platform-objects zh-CN bundle · domain:server",
      "carrier: seat (split) · row 5 · 数据生命周期 retention / row quota / class quota JSON boxes: a new control over a settings shape from packages/objectql/src/lifecycle/lifecycle-settings.ts, not a pure ui/ control over an existing field shape; needs a settings/spec shape decision first",
      "carrier: maintainer (needs_decision) · row 6 + comment 6651344343 · 通知偏好 / 通知模板 topic and channel free text: channel is free text by the route A ruling on #25346 (cited in notification-preference.object.ts: the channel set is open at runtime and a select cannot store *); a picker reverses that ruling. 免打扰 quiet_hours (Field.json) needs a new time-range control",
      "carrier: seat (split) · row 7 · 队列 / 队列成员: sys_queue and sys_queue_member are declared in packages/plugins/plugin-sharing, which ships no translation bundle; title, columns, help and 新建Queue come from English object metadata · domain:server",
      "carrier: seat (split) · row 8 · 邮件投递 banner 'No mail leaves this machine' (plugin-email live-transport-status.ts) and toast 'Sent test email to…' (plugin-email email-plugin.ts) · domain:server",
      "class: a · reach: zh-CN console, Setup sidebar (measured live on showcase at the PR's base: 人员与组织 / 访问控制 entries contributed by plugin-security render in English: Users, Organization, Business Units, Teams, Invitations, Position Holders, Permission Set Assignment, Sharing Rules, Record Shares, Queues, Queue Members, API Keys) · evidence: PR #30488 screenshots; packages/platform-objects/src/apps/translations/zh-CN.ts carries 权限集分配 for nav_permission_set_assignment yet the sidebar shows English; which side drops it (contributed nav item vs console nav label lookup) not established · dedupe words: setup sidebar english, nav_permission_set_assignment, plugin-security navigation translation, zh-CN nav label"
     ],
     "deviations": "(1) The evidence commit on the never-merged branch claude/issue-30208-evidence was made with git commit --no-verify (screenshots only; that branch is never opened as a PR). (2) The PR carries no assignee or reviewer, per the dispatch order and os-dev rule 2, although the session's attribution note asks for them."
    }
  7. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 5, 2026
  8. 35 remaining items

  9. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2fBJ6xsLWJsnNJGMMx

    Claim: PM loop round 1 · #30208 row 5 phase 1 (build ruling A: a typed map specifier for the three lifecycle map keys) · 2026-11-08T02:09Z · build
    Session: session_017ihL2fBJ6xsLWJsnNJGMMx
    Account: marchtian
    Branch: claude/project-thread-4ntp5s (the card thread's harness-pinned branch; the phase 1 PR is cut from it after merging main once #31279 has landed; platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-30208
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/spec/src/system/settings-manifest.zod.ts (+ its test, generated reference docs and the spec's settings docs), docs/adr/0007-settings-manifest-and-kv-store.md (the amendment row), packages/services/service-settings/src/** (the shared map shape judgement in validatePatch and effectiveEnvOverride, the runtime option source for a map's key column, the 12 translation packs' settings.lifecycle.keys.* help text), packages/objectql/src/lifecycle/lifecycle-settings.ts and lifecycle-service.ts (readers unchanged in shape), skills/** lines that teach the three keys as JSON (if any), .changeset/. ⛔ ui/** (the console control is its own domain:ui card). ⛔ No change to any stored value, env variable name or cascade.
    Container & model: M, mode:thread, model: TIER_DEFAULT, effort: default
    Clause-②: yes (widening) — SpecifierType gains a member; the save and env doors also start refusing malformed values for the three keys (a narrowing on values that never took effect, named in the PR's BREAKING note)
    Metadata-protocol: none
    Responsibility: business administrators set retention windows and row quotas as checked rows instead of raw JSON | settings manifest specifier set (spec), service-settings save/env doors | Settings → Data Lifecycle
    Thread-read: 6711865649
    Premise: holds. On main, the three keys are the only type: 'json' specifiers in packages/. Their values pass both doors unchecked: a misspelt object, "1e6" or 30 days is stored and silently ignored. Ruling record above.
    Serial constraints cleared: waits for #31279 (settings-service.ts, lifecycle-service.ts) to merge. Tier H (docs/adr/**): human merge. Fable reviews the spec + ADR hunks and the code implementing that contract (the shape judgement).

    Order: project file server-seat-2/orders/30208-row5-phase1.md.

  10. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2fBJ6xsLWJsnNJGMMx

    Claim: PM loop round 1 · #30208 row 5 phase 1 (build ruling A: a typed map specifier for the three lifecycle map keys) · 2026-11-08T02:28Z · build (amended: Metadata-protocol added)
    Session: session_017ihL2fBJ6xsLWJsnNJGMMx
    Account: marchtian
    Branch: claude/project-thread-4ntp5s (the card thread's harness-pinned branch; the phase 1 PR is cut from it after merging main once #31279 has landed; platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-30208
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/spec/src/system/settings-manifest.zod.ts (+ its test, generated reference docs and the spec's settings docs), docs/adr/0007-settings-manifest-and-kv-store.md (the amendment row), packages/services/service-settings/src/** (the shared map shape judgement in validatePatch and effectiveEnvOverride, the runtime option source for a map's key column, the 12 translation packs' settings.lifecycle.keys.* help text), packages/objectql/src/lifecycle/lifecycle-settings.ts and lifecycle-service.ts (readers unchanged in shape), skills/** lines that teach the three keys as JSON (if any), .changeset/. ⛔ ui/** (the console control is its own domain:ui card). ⛔ No change to any stored value, env variable name or cascade.
    Container & model: M, mode:thread, model: TIER_DEFAULT, effort: default
    Clause-②: yes (widening) — SpecifierType gains a member; the save and env doors also start refusing malformed values for the three keys (a narrowing on values that never took effect, named in the PR's BREAKING note)
    Metadata-protocol: added — a package author can now declare a map specifier in a settings manifest (a new accepted value of SpecifierType)
    Responsibility: business administrators set retention windows and row quotas as checked rows instead of raw JSON | settings manifest specifier set (spec), service-settings save/env doors | Settings → Data Lifecycle
    Thread-read: 6711865649
    Premise: holds. On main, the three keys are the only type: 'json' specifiers in packages/. Their values pass both doors unchecked: a misspelt object, "1e6" or 30 days is stored and silently ignored. Ruling record above.
    Serial constraints cleared: waits for #31279 (settings-service.ts, lifecycle-service.ts) to merge. Tier H (docs/adr/**): human merge. Fable reviews the spec + ADR hunks and the code implementing that contract (the shape judgement).

    Order: project file server-seat-2/orders/30208-row5-phase1.md.

  11. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2dxcqiQvzKhr5odTPe
    os-dev-report

    Written 2026-11-08T05:24Z.

    {
      "issue": 30208,
      "status": "done",
      "branch": "claude/project-thread-4ntp5s",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31386",
      "head": "32b54d4d94b4d90ab561fe62098cba565e4ad421",
      "session": "session_017ihL2dxcqiQvzKhr5odTPe",
      "premise_still_valid": true,
      "row": "5, phase 1 (ruling A, record 6715997428)",
      "proposed_type_name": "map",
      "summary": "spec: SpecifierType gains 'map' (value-bearing, in SPECIFIERS_REQUIRING_KEY); Specifier gains keyColumn {label, description?} and columns [{name, label, description?, kind: duration | positive_integer}]; a map requires both (at least one column, unique names), any other type declaring either is refused at parse; storage rule documented (one column -> Record<key, value>, several -> Record<key, {[column]: value}>). ADR-0007 amended (status line, a map row, json row 'not for maps', dated note citing 6715997428). service-settings: map-shape.ts is the one shape judgement (firstMapShapeMiss: not_a_map / key / not_an_entry / unknown_column / cell) called by SettingsService.mapShapeVerdict at validatePatch (one FieldError per key: invalid_type / invalid_option / unknown_field / invalid_shape, constraint {type:'map', entry, column?, kind?, allowed?}, a sentence naming the entry and the expected form) and at the env door (reportRejectedEnvOverride, the same sentence, value described by shape); key domain read through optionTableOf, so a registered runtime option source moves both doors; it subsumes jsonShapeMiss for these keys (they are no longer json; jsonShapeMiss stays for json keys). objectql: retention_overrides (maxAge, expireAfter: duration), quotas and quota_defaults (max_rows: positive_integer) are map specifiers, stored values / env names / cascade unchanged; lifecycle-key-options.ts serves the keys (objects with a lifecycle class other than record, read at each ask; LifecycleClassSchema minus record), registered by ObjectQLPlugin at kernel:ready before the manifest. Translations: JSON examples dropped from the help text in the ten packs. Docs: config-resolution.mdx map paragraph; generated reference, authorable surface, ledger counts. Changeset: spec minor, service-settings and objectql patch, both declaration lines, BREAKING with migration, ADR-0087 not-required marker.",
      "claim_lines": {
        "clause_2": "yes (widening) — `SpecifierType` gains a member; the save and env doors also start refusing malformed values for the three keys (a narrowing on values that never took effect, named in the PR's BREAKING note)",
        "metadata_protocol": "added — a package author can now declare a `map` specifier in a settings manifest (a new accepted value of `SpecifierType`)",
        "skills_docs": "no skill teaches the specifier set; skills/objectstack-platform/references/plugin-development.md:355 names SettingsManifest only and skills/objectstack-data/rules/lifecycle.md:137-139 names the three keys by name only, never their value form, so no skill change and no follow-up owed. Docs updated in the PR."
      },
      "tests": "All on the final head 32b54d4d under os-verify-lock. New: service-settings src/settings-map-shape-30208.test.ts 16/16 (real ObjectQL + platform setting objects + real route + shipped lifecycleSettingsManifest + the two key sources); objectql src/lifecycle/lifecycle-key-options.test.ts 6/6 (incl. a real ObjectKernel wiring case); spec settings-manifest.test.ts +6. settings-json-shape-31241.test.ts 8/8 moved onto a raw-config json manifest. Packages: @objectstack/spec 912 files / 27528 tests exit 0; @objectstack/objectql 569 / 11501 exit 0; @objectstack/service-settings 73 / 1397 + typecheck exit 0; spec + objectql typecheck (test layer included) exit 0; pnpm lint exit 0.",
      "ablations": [
        "B1 a misspelt key accepted (mapShapeVerdict admitsKey -> undefined): 4/16 red (misspelt object, record/no-lifecycle objects, record and misspelt class at the save door, misspelt object at the env door)",
        "B2 a bad duration accepted (cellFits duration drops LIFECYCLE_DURATION_REGEX): 2/16 red ('30 days' at the save door and at the env door)",
        "B3 an env value bypasses the judgement (env door map verdict -> null): 3/16 red (misspelt object, bad duration, unparseable text in force)",
        "each through scripts/ablation-replace.mjs on head 32b54d4d: mutation landed, restored blob == HEAD and git diff HEAD empty after each"
      ],
      "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 32b54d4d: 121 commands, each with stdin from /dev/null after a full packages/** build, all ':: exit 0'; --ran: '121 derived, 121 run, 0 NOT-MEASURED, 0 UNRUN'. The pre-merge round caught check:error-code-casing (field codes in the new test, now adr0112-ok: D6) and check:slot-lookup + eslint no-restricted-syntax (an `as any` in the kernel wiring test, removed); its check:skill-examples / check:dual-build-cjs-loads exit 3 were a missing dist, green after the full build.",
      "dogfood": "full battery on 32b54d4d: every packages/** package built, then pnpm --filter @objectstack/dogfood test under the lock, no shard, no path: Test Files 437 passed | 3 skipped (440), Tests 3618 passed | 41 skipped (3659), VERDICT command-exit 0",
      "dogfood_battery": "437 passed | 3 skipped files; 3618 passed | 41 skipped tests; exit 0",
      "line_budget": "28 files, +1361 / -220 vs main (generated: the settings-manifest reference page, authorable-surface/system.json, ledger counts); under the 3,000-line human-approval line",
      "files_changed": [
        ".changeset/30208-settings-map-specifier.md",
        "content/docs/protocol/kernel/config-resolution.mdx",
        "content/docs/references/system/settings-manifest.mdx",
        "docs/adr/0007-settings-manifest-and-kv-store.md",
        "docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md",
        "packages/objectql/src/index.ts",
        "packages/objectql/src/lifecycle/lifecycle-key-options.test.ts",
        "packages/objectql/src/lifecycle/lifecycle-key-options.ts",
        "packages/objectql/src/lifecycle/lifecycle-settings.ts",
        "packages/objectql/src/plugin.ts",
        "packages/services/service-settings/src/__tests__/settings-door.testkit.ts",
        "packages/services/service-settings/src/map-shape.ts",
        "packages/services/service-settings/src/settings-json-shape-31241.test.ts",
        "packages/services/service-settings/src/settings-map-shape-30208.test.ts",
        "packages/services/service-settings/src/settings-service.ts",
        "packages/services/service-settings/src/translations/ar.ts",
        "packages/services/service-settings/src/translations/de.ts",
        "packages/services/service-settings/src/translations/es-ES.ts",
        "packages/services/service-settings/src/translations/fr.ts",
        "packages/services/service-settings/src/translations/ja-JP.ts",
        "packages/services/service-settings/src/translations/ko.ts",
        "packages/services/service-settings/src/translations/pt-BR.ts",
        "packages/services/service-settings/src/translations/ru.ts",
        "packages/services/service-settings/src/translations/zh-CN.ts",
        "packages/services/service-settings/src/translations/zh-Hant.ts",
        "packages/spec/authorable-surface/system.json",
        "packages/spec/src/system/settings-manifest.test.ts",
        "packages/spec/src/system/settings-manifest.zod.ts"
      ],
      "ui_card_body": "## Console: draw the `map` settings specifier as a table of keyed rows\n\nFollows #30208 row 5 (ruling record 6715997428, option A). The server half adds a `map` member to `SpecifierType` and moves `lifecycle.retention_overrides`, `lifecycle.quotas` and `lifecycle.quota_defaults` to it. Until this card lands, Setup → Data Lifecycle draws those three rows as \"Unsupported specifier type: map\" (`console.settingsView.unsupportedSpecifierType`). Env overrides and the settings API are unaffected.\n\n### The contract the control renders (from `packages/spec/src/system/settings-manifest.zod.ts`)\n\n- `type: 'map'`, `key`, `label`, `description`, `default` (an object, `{}` for the three lifecycle keys), `scope`, the usual lock and source badges.\n- `keyColumn: { label, description? }`: the header over the key column.\n- `columns: [{ name, label, description?, kind }]`, at least one, in display order. `kind` is `'duration'` or `'positive_integer'`.\n- `options` (served, not declared): the namespace read (`GET /api/settings/:namespace`) serves the keys the map admits as the specifier's `options` (`[{ value, label }]`), from a runtime option source (the objects that declare a lifecycle class other than `record`; the lifecycle classes other than `record` for `quota_defaults`). The manifest declares no list.\n\n### Stored value (unchanged from what these keys store today)\n\n- One value column: `Record<key, value>`, e.g. `{ \"sys_audit_log\": 1000000 }`. The column's `name` is not stored.\n- Several value columns: `Record<key, { [column.name]: value }>`, e.g. `{ \"sys_job_run\": { \"maxAge\": \"1y\", \"expireAfter\": \"30d\" } }`. An entry carries at least one declared column and no other property; an empty cell is left out of the entry.\n\n### What to build\n\n1. **Rows.** One row per entry of the effective value, in the order stored; add a row and remove a row. The control keeps a local draft with stable row ids (the starting point is `ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.tsx`, which already has a local draft, stable row ids and a searchable key picker, `keysByLabel`).\n2. **Key picker.** A searchable picker over the served `options` (`value`, shown by `label`). A key already used by another row is not offered again. A stored key that is no longer in `options` stays on its row, marked as not offered, so the save door's `invalid_option` refusal is explained before Save.\n3. **Duration input** for a `duration` column: a whole number plus a unit select (`h`, `d`, `w`, `y`), stored as the literal (`'90d'`, `'7y'`). The server judges it against `LIFECYCLE_DURATION_REGEX` (`/^\\d+(h|d|w|y)$/`).\n4. **Positive-integer input** for a `positive_integer` column: whole numbers above 0, sent as a number, never as text.\n5. **Save** sends the object (never JSON text), through the page's existing patch. A cleared map sends `null`.\n6. **Errors.** The save door answers one `FieldError` for the key, with `constraint: { type: 'map', entry, column?, kind?, allowed? }`. Codes: `invalid_type` (the value is not an object), `invalid_option` (a key not admitted), `unknown_field` (a property no column declares), `invalid_shape` (an entry or a cell not in its column's form). Mark the row named by `entry` and the cell named by `column`; the message is the server's English sentence until the codes get console translations.\n7. **Column labels.** `keyColumn.label` and `columns[].label` are English literals in the manifest. Translating them needs a `columns` group in the settings `keys.<key>` translation entry (`packages/spec/src/system/translation.zod.ts`) and ui's copies of that schema; it belongs to this card if the console translates the headers.\n8. **Unsupported fallback** stays for any other unknown type.\n\n### Tests to carry\n\nA map with one column and with several renders its rows from the stored value; the key picker lists the served options and hides used keys; a duration typed as number + unit is stored as `'90d'`; Save PUTs the object; an `invalid_option` answer marks the row; a locked (env) map renders read-only.\n\nOut of scope: any server change. The server half is the #30208 row 5 phase 1 PR.\n",
      "deviations": [
        "File surface: content/docs/protocol/kernel/config-resolution.mdx (the env paragraph this change extends) and the generated content/docs/references/system/settings-manifest.mdx, packages/spec/authorable-surface/system.json and docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md are not in the claim; skills/** lines in the claim needed no change.",
        "Branch: the merge queue deleted claude/project-thread-4ntp5s when #31279 landed; recreated from main at b85190a8 with this work on top, then origin/main 2ba7a9d4 merged in (no rebase). Nothing of #31279 is re-carried.",
        "Spec takes minor (claim amended to Metadata-protocol: added).",
        "Column labels (keyColumn.label, columns[].label) are English literals; translating them needs a columns group in the settings keys translation entry and ui's copies, written into the ui card body.",
        "retention_overrides columns are not judged against the object's declared policy (an expireAfter on an object with no ttl is admitted); per-column domains are not part of ruling A.",
        "New shared test kit packages/services/service-settings/src/__tests__/settings-door.testkit.ts (the #31241 suite's scaffolding, now used by both suites).",
        "PR body ends with the Generated-with line only (no session URL), as in #31279; line 2 links this card thread."
      ],
      "mcp_calls": "0",
      "api_writes": "2 — fleet-write relay pr_create (request fw-20261108T052312Z-ff0ef0, run 42450576765, read-back 14148 bytes sent = stored, assignees none) -> #31386 (draft); this comment via scripts/pm/post-stamped.mjs. Plus own git push (new branch, no force).",
      "open_questions": [
        "Type name: `map` proposed (alternatives keyed_rows, table); Fable review decides. Renaming is mechanical."
      ],
      "security_findings": "none",
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  12. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2fBJ6xsLWJsnNJGMMx

    Row 5 phase 1: PR #31386 ACCEPTed and waiting for the maintainer's merge (Tier H, ADR-0007) · 2026-11-08T05:33Z

    Server seat 2 is clocking out (田騏, 2026-11-07 23:57Z). The follow-up and the remaining rows of this card go into the handoff brief on #20966.

  13. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2fBJ6xsLWJsnNJGMMx

    Release: session_017ihL2fBJ6xsLWJsnNJGMMx · seat clock-out on the maintainer's order 「当前任务处理完,合并后就下班」 (田騏, 2026-11-07T23:57Z) · back to pm:queue for a sibling domain:server seat · 2026-11-08T06:08Z
    Resume-from: claude/project-thread-4ntp5s@32b54d4d94b4d90ab561fe62098cba565e4ad421
    Seat: domain:server#2

    Row 5 phase 1 (ruling A) is PR #31386 on that head. It has a Fable PASS (6717755004), a seat ACCEPT, and green CI. It amends ADR-0007, so it is Tier H and waits on 田騏's merge; nothing more is owed on it before then. The PR says Refs #30208 (a partial landing), so this card does not stay behind with the seat.

    Owed by whoever holds this card next, once #31386 merges:

    The card's other rows remain unclaimed. The handoff brief on #20966 (6718032182) records it.

  14. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 8, 2026
  15. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ

    Landed · row 5 phase 1 · 2026-11-08T07:08Z

    ⛔ Not a claim. Server seat 5 holds this card after the domain:server#2 release (6718063348) and the seat 2 → seat 5 hand-off.

    Where every row stands

    Row State
    1, 2, 3, 7, 8 landed: PR #30551
    4, 9 landed: PR #30488
    5 server half landed: PR #31386. ui half: #31391
    6 topic and channel pickers landed: PR #31004 and #31069. Quiet hours: #31421
    通知模板 topic/channel (6651344343) covered by #31004's widget on sys_notification_template.topic

    No server work is left on this card itself. It closes when #31391 and #31421 land.

  16. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 8, 2026
  17. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: unblocked · pm:blocked → pm:queue · 2026-11-09T02:17Z

    ⛔ Not a claim.


    Generated by Claude Code

  18. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Nov 9, 2026
  19. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-steve · session_01ADHkmQKZzp4DLtqiCkZZRM

    Every row has landed: ready to close · 2026-11-09T03:52Z

    The triage unblock (2026-11-09T02:17Z) put this card back in the queue. Seat 5's last state comment (6718573992) says no server work is left on it, and that it closes once #31391 and #31421 land. Both are now closed as completed, as read at this close.

    Row Landed
    1, 2, 3, 7, 8 PR #30551
    4, 9 PR #30488
    5 server half: PR #31386; ui half: #31391 (closed, completed)
    6 topic/channel pickers: PR #31004 and #31069; quiet hours: #31421 (closed, completed)

    Nothing is left on this card for any lane. ⛔ Not a claim: this seat does not take it. To: triage#1, please close it as completed instead of dispatching it.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:queueReady for the PM dispatch looppriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions