Skip to content

Settings read: an env-pinned key does not say whether a value is stored beneath the pin, so Setup cannot offer to clear it #30823

Closed

Description

@objectstack-fleet

domain:ui#3 · os-tesla · session_01L9qibxW6qraU2Z3mwupUGp

Filing gate: ③ a maintainer ruling that names this work: #30385 ruling A (maintainer 「同意」 on 2026-11-05), item 5, through #30665, whose body says: "The page needs the server to say whether a stored value sits beneath the pin. If the settings read does not carry that today, the reader states the gap and the server field is filed to domain:server first." ⛔ Not a claim. Triage sets the grade and the lane. Suggested grade: p2 (#30665's grade; it is the half that blocks it).

Business reason. An administrator who pinned a settings key with an OS_* variable to recover (the #30385 case: the allowed-IP list) must clear the value saved beneath the pin before removing the variable, or the old value comes back. PR #30448 made the clear possible through the API; #30665 puts it in Setup. Setup can only show "clear the saved value" on a field that has one, and today nothing tells it.

Reader. The domain:server seat (@objectstack/service-settings, the read path), then the domain:ui seat holding #30665.

Dedupe. stored value beneath pin (59 hits), env override stored beneath (17), settings read env locked stored (92): nearest #30665 (the ui half, blocked on this), #30664 (closed: resetNamespace now clears beneath a pin, PR #30793, no read field), #30385 (closed: the clear itself, PR #30448). None adds a read field. New.

What happens (origin/main 3da9a4c1)

SettingsService answers a key an env override pins from the override branch alone (packages/services/service-settings/src/settings-service.ts ~L1952): { value, source: 'env', locked: true, lockedReason: 'Set via env: …', cascadeChain: [ { scope: 'env', … } ] }. The stored cascade beneath it (resolveStored) is not read on that branch, so neither the value nor the fact that a row exists reaches the caller. The cascade chain carries the env entry only.

Wanted

Unblocks: #30665.

Activity

  1. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p1 · domain:server · pm:blocking · 2026-11-06T05:09Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    enhancementNew feature or request
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    on Nov 6, 2026
  3. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

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

    Claim: PM loop patrol · 2026-11-06T05:27Z
    Session: session_016WV4cytLmJxfAmgPVjUbbW
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-30823-settings-stored-beneath-pin (placeholder; the card thread's harness-pinned branch replaces it by a full re-post of this claim before any push)
    Worktree: objectstack-issue-30823
    Domain: domain:server
    Seat: domain:server#1
    File surface: packages/services/service-settings/src/settings-service.ts (the env-override branch of the read) and settings-secret-redaction.ts if a secret key's beneath-pin entry needs redacting, their tests, settings-routes.ts only if the REST answer does not already carry the service's read, content/docs/** (the settings read's description), one root .changeset/ file. ⛔ packages/spec/** (stop and ask: every spec seat is vacant, consent goes to the maintainer), ui/**, docs/adr/**, skills/**, .claude/**. Stop on breach and explain in the report
    Container & model: M, mode:card-thread (p1), model: default, effort: xhigh
    Clause-②: yes (widening), expected: the read of an env-pinned key carries more than it does today. The dev confirms the final shape in the PR body
    Metadata-protocol: none
    Responsibility: the settings read's provenance (@objectstack/service-settings, SettingsService.get and GET /api/settings/:namespace) | none | the Setup page that offers to clear a value saved beneath a pin (#30665, domain:ui)
    Thread-read: 6677477512
    Premise: holds on origin/main c17719dd. SettingsService.get returns from the env-override branch (settings-service.ts about :1953) with a one-entry cascadeChain and never calls resolveStored, so a row stored beneath the pin is invisible to the caller. ResolvedSettingValueSchema.cascadeChain (packages/spec/src/system/settings-manifest.zod.ts about :827) already admits entries for every scope below env, so a shape that fills the chain beneath the pin may need no schema change; the dev decides, and stops before any packages/spec/** edit. ⛔ A secret-typed key's stored value never reaches the caller
    Serial constraints cleared: no open PR names this card. Open PR #30720 (draft) edits settings-routes.ts, and #30783 (draft) adds pin tests beside it; if this card has to touch settings-routes.ts, the dev rebases on whichever lands first

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

    @objectstack-fleet
    ContributorAuthor

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

    Claim: PM loop patrol · 2026-11-06T05:30Z (re-post of 6677721533 with the card thread's branch; it supersedes that comment)
    Session: session_016WV4cytLmJxfAmgPVjUbbW (seat); dev session session_016WV4d13xV55y9KgsteqXK9
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-56aarf (the card thread's harness-pinned branch, objectstack-ai/objectstack)
    Worktree: objectstack-issue-30823
    Domain: domain:server
    Seat: domain:server#1
    File surface: packages/services/service-settings/src/settings-service.ts (the env-override branch of the read) and settings-secret-redaction.ts if a secret key's beneath-pin entry needs redacting, their tests, settings-routes.ts only if the REST answer does not already carry the service's read, content/docs/** (the settings read's description), one root .changeset/ file. ⛔ packages/spec/** (stop and ask: every spec seat is vacant, consent goes to the maintainer), ui/**, docs/adr/**, skills/**, .claude/**. Stop on breach and explain in the report
    Container & model: M, mode:card-thread (p1), model: default, effort: xhigh
    Clause-②: yes (widening), expected: the read of an env-pinned key carries more than it does today. The dev confirms the final shape in the PR body
    Metadata-protocol: none
    Responsibility: the settings read's provenance (@objectstack/service-settings, SettingsService.get and GET /api/settings/:namespace) | none | the Setup page that offers to clear a value saved beneath a pin (#30665, domain:ui)
    Thread-read: 6677477512
    Premise: holds on origin/main c17719dd. SettingsService.get returns from the env-override branch (settings-service.ts about :1953) with a one-entry cascadeChain and never calls resolveStored, so a row stored beneath the pin is invisible to the caller. ResolvedSettingValueSchema.cascadeChain (packages/spec/src/system/settings-manifest.zod.ts about :827) already admits entries for every scope below env, so a shape that fills the chain beneath the pin may need no schema change; the dev decides, and stops before any packages/spec/** edit. ⛔ A secret-typed key's stored value never reaches the caller
    Serial constraints cleared: no open PR names this card. Open PR #30720 (draft) edits settings-routes.ts, and #30783 (draft) adds pin tests beside it; if this card has to touch settings-routes.ts, the dev rebases on whichever lands first

  6. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

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

    席位核对 · Amendment · 2026-11-06T05:37Z · claim 6677761672

    Amended surface: packages/services/service-settings/README.md, one sentence in the "Env override" section. It says that the namespace (inspection) read lists the stored entries beneath a pin's env entry. The README ships in the package tarball, so it is changed with the code, in the same PR.

    Shape noted (dev, no spec edit):

    • getNamespace, the inspection path that GET /api/settings/:namespace already passes through, lists the stored chain beneath the env entry. The env entry stays first and effective, and its effective flag is stripped from the entries below it.
    • get() and getMany() keep answering a pin from env alone, because they are per-request hot paths. The cascadeChain docblock already scopes the chain to the inspection path. The card's "settings REST answer carries it" is met by the namespace read.
    • A secret key's entries beneath the pin carry the mask or null, never the decrypted value.
    • An env value that coerces to null keeps an env-only chain, and the PR names this as a gap.

    Everything else in the claim is unchanged.

  7. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

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

    os-dev-report

    PR #30835 (draft, head de2998b3), claim 6677761672, surface amendment 6677849918.

    {
      "issue": 30823,
      "status": "done",
      "branch": "claude/project-thread-56aarf",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30835",
      "session": "session_016WV4d13xV55y9KgsteqXK9",
      "premise_still_valid": true,
      "summary": "Premise held at c17719dd: get(), getMany() and getNamespace() all answered an in-force OS_* override from the env alone with a one-entry cascadeChain. Shape, no packages/spec edit: getNamespace (the read GET /api/settings/:namespace already passes through, redacted; settings-routes.ts untouched) now lists, below the env entry, the chain the key's read carries with no override in force (the stored global/tenant/user rows the caller's cascade draws on, each with its own lock, then default), effective flag removed, so the env entry stays first, effective and the first non-null entry. value/source/locked/lockedReason unchanged. getMany delegates to a private resolveMany(..., beneathPins); a pinned key joins the existing grouped row load. A secret key's entries beneath a pin carry SETTINGS_SECRET_MASK or null from the row, never decrypted (in process too). get() and getMany() stay env-only (accepted deviation from order item 5, seat thread note: per-request hot paths, pre-bind env read pinned silent). Named gap: an env value coercing to null keeps the env-only chain, since any non-null entry beneath it would contradict the effective-source rule; saying 'stored beneath' there needs a schema field. #30665's reader: the console Settings page reads only GET /api/settings/:namespace (getSettingsNamespace in ui/apps/console/src/pages/settings/api.ts); there is no single-key read route.",
      "tests": "Final head de2998b3 (after merging origin/main 2038c56e, whole tree rebuilt, VERDICT command-exit 0): @objectstack/service-settings test Test Files 70 passed (70), Tests 1216 passed (1216), VERDICT command-exit 0; typecheck (tsc --noEmit, tsconfig includes src tests) VERDICT command-exit 0. Same counts at 1492245c before the merge. New pins (10): settings-service.test.ts top-level describe x7 (stored row beneath the pin, whole answer compared; after the clear the entry reads null and a pin with no row lists none; global row + upper row lock ride along; user key lists the caller's own row only; secret shows the mask, never the value, decrypt never called, null after the clear; env value coercing to null keeps one entry; get()/getMany() env-only), settings-routes.test.ts top-level describe x2 (GET lists stored row + secret mask, wire carries no plaintext, decrypt never called; PUT null through the pin then GET lists null), settings-getmany.test.ts top-level describe x1 (same find count with and without the pin). One existing pin updated: the REST env-locked secret case now expects the default entry beneath the env one. Ablations via scripts/ablation-replace.mjs on settings-service.ts, suites import src (no build between), each anchor 1 -> 0, blob changed, restored blob == HEAD and git diff HEAD empty: A getNamespace asks resolveMany(..., false) -> 9 failed | 198 passed (all beneath-pin cases + the updated secret pin; null-edge and env-only cases stay green by design); B pinned secret materialised by decrypting -> 2 failed | 205 passed; C null-value guard removed -> 1 failed | 160 passed.",
      "gates": "dispatch-gates --commands at de2998b3: 90 families, each run from the worktree root with its exit captured before any pipe, all 90 exit 0; --ran: '90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN' (derived zero). pnpm lint exit 0 (recorded outside the derivation). Prerequisites done before the pass: git fetch --unshallow, whole-tree turbo build. Record: /mnt/project-files/server-lane-seat1/30823/gates-ran-de2998b32b.txt. CI check-runs on de2998b3, all completed: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (8/8 shards success), Build Core success, Governed Surface Queue Guard success, Test UI success, Live E2E UI success, Lint UI skipped, Type Check UI skipped, Build & E2E UI skipped, PR-body guards success, Check Changeset success, Check PR Size success, Build Docs success, Dogfood Regression Gate success.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "line_budget": "7 files, +388 / -12 vs merge base 2038c56e (400 changed lines); under the 3000-line human-merge threshold",
      "files_changed": [
        ".changeset/30823-settings-stored-beneath-pin.md",
        "content/docs/protocol/kernel/config-resolution.mdx",
        "packages/services/service-settings/README.md",
        "packages/services/service-settings/src/settings-getmany.test.ts",
        "packages/services/service-settings/src/settings-routes.test.ts",
        "packages/services/service-settings/src/settings-service.test.ts",
        "packages/services/service-settings/src/settings-service.ts"
      ],
      "deviations": [
        "Order item 5 named SettingsService.get beside the inspection path; get() and getMany() stay env-only (per-request reads: resolve-authz-context getMany('localization') on every request context; settings-prebind-read-warning case 5). Accepted by the seat in the thread; stated in the PR body.",
        "Changeset level patch, not the order's minor: AGENTS.md Post-Task Checklist step 3 sets the level by Metadata-protocol kind, and none takes patch (a non-protocol widening ships patch); Check Changeset is green on it.",
        "README.md edited under the seat's surface amendment 6677849918; settings-getmany.test.ts (the grouped-load harness) carries the read-count pin, a test beside the surface's files.",
        "settings-service.ts edits beyond the env branch: getMany's body moved into the private resolveMany, and resolveKeyFromRows gained an optional materialise parameter (default materialiseRow), so every caller's walk is the same code.",
        "pnpm lint run locally because the standing lines ask; os-dev assigns it to CI.",
        "The branch carries one plain merge of origin/main (no force-push); the harness-pinned branch is not claude/issue-*.",
        "Commit trailers are the model-free pair AGENTS.md prescribes."
      ],
      "mcp_calls": "7 read-only — mcp__github__issue_read x6 (get #30823, get_comments #30823 x2, get #30665, get_comments #30665, get_comments #30385), mcp__github__pull_request_read x1 (get_check_runs #30835); plus subscribe_pr_activity on #30835 (session tool, not a GitHub write); no GitHub write tool",
      "api_writes": "2 — relay pr_create (fw-20261106T060111Z-165139, run 42171823958 -> #30835, 8884 bytes read back identical, no assignees, no labels); this os-dev-report via post-stamped. Plus 3 git pushes (not REST).",
      "open_questions": [],
      "security_findings": "withheld",
      "out_of_scope_findings": [
        "class: none (observation) · packages/spec ResolvedSettingValueSchema.cascadeChain docblock says 'locked: true on an upper entry means writes to lower entries are rejected', which since the ADR-0007 D6 amendment holds for every write but a clear under an env entry; and locked's docblock says 'Today this is exactly source === env' while row locks exist. carrier: seat (spec seats vacant; spec prose consent goes to the maintainer) · noted, not filed",
        "class: none (observation) · the named gap above: an override coercing to null (OS_X=null on a JSON-default key) shows nothing beneath it, so Setup cannot offer the clear there; resetNamespace still clears it. carrier: seat (would need a schema field) · noted, not filed",
        "class: none (observation) · the docs drift check named environment-variables.mdx, authentication.mdx, drivers.mdx and kernel/index.mdx; re-read, none states the pinned read's chain, so none is falsified. carrier: none · noted, not filed"
      ]
    }
  8. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

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

    Landed · 2026-11-06T07:02Z · PR #30835 merged by the queue as 87fc795751 (2026-11-06T07:02:00Z). The settings namespace read (GET /api/settings/:namespace) now lists the values stored beneath an env pin in cascadeChain. A secret appears there as the mask or null, never as its value. Setup (#30665) can now offer to clear a saved value; #30665's Blocked-by: #30823 is cleared. This card is closed by Fixes. pm:dispatched removed (pm:blocking goes with the close).

    Recorded, not carded: a fully pinned namespace now reads the store. As a result, it logs one pre-bind warning per namespace, and its organization lookup can throw just as any unpinned key's can (contract review 6678895942, should-fixes 1 and 2).

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

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-setpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions