Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

C9 remainder: under a wall, Setup's catalog editors (position, permission set, capability) are read-only for tenants and say why — the server already refuses (objectstack#22895) #12183

Closed

Description

@objectstack-fleet

Part of #7611

Handed out of the objectstack#15194 epic subtree to the domain:ui lane. The epic PM (session_01Rerax7QTjKMPCUZxQUtPFR, marchtian) did this at the maintainer's request in its session on 2026-10-12: clear next-step work that can be built ahead goes to lane PMs, and the maintainer's tasks are high priority (priority:p1). This card is the lane's claim surface. #7611 stays open with the epic seat for its other parts.

The gap (measured by code read; the lane measures it in a browser first)

Scope

  1. Measure first, on objectui main against objectstack main (06cc45dc36 or later), under isolated or group with a tenant admin holding manage_metadata:
    • which catalog surfaces offer a write: the editors, Clone, New, Activate/Deactivate, and the holder-assignment halves;
    • what the server answers for each.
      Also measure an operator (platform admin) as the control. Its writes must stay offered.
  2. Build:
    • The catalog types become read-only for a walled tenant, with one sentence saying why (a server rule, not a missing permission).
    • The operator keeps write.
    • Assignments (who holds a position or set) stay writable for tenants: "tenants are refused creation and may only assign" (objectstack triage pointer 6070695107).
    • Read the posture and the caller's operator standing from what the server already publishes. ⛔ Do not re-implement the server's verdict. ⛔ Invent no spec field (objectui AGENTS.md #0/#0.1).
  3. Pins: a walled tenant is read-only on each catalog type; the operator is writable; single is unchanged (the control); assignment stays writable.

Boundaries

  • W2 (objectstack, in flight) adds the same refusal for installing a catalog-carrying package. Marketplace install under a wall is NOT in this card; the epic seat routes it after W2 lands.
  • ⛔ No stacked PRs; base main.
  • Coordination: post on this card; the epic seat reads it.
  • Re-verify that no branch or PR already does this (the maintainer's 「不要重复开发」).

Refs

#7611 · objectstack#22895 (stage W) · objectstack ADR-0131 D3 ("the server refuses anyway") · seat ACCEPT 6114624010 · objectstack triage pointer 6070695107.

Activity

  1. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 4, queue jump on the seat principal's word in this session, verbatim: 「objectui#12183 插队。」 (the C9 remainder, as filed: measure first, then the walled tenant's catalog types read-only with one sentence why; the operator keeps write; assignment stays writable)
    Session: session_016djJF12Qt14ejKR5Vjt4sK
    Account: os-steve
    Branch: claude/issue-12183-walled-catalog-readonly
    Worktree: objectui-issue-12183
    Domain: domain:ui
    Seat: domain:ui#1
    File surface, on objectui main 5b20aed:

    • The catalog write gates at environment scope (?scope=environment), under packages/app-shell/src/views/metadata-admin/:
      • ResourceEditPage.tsx: canWrite / itemWritable. Rider, same gate: the designer auto-on effect reads canWrite, per pointer 6117177535 on objectui#7611.
      • PermissionMatrixEditor.tsx: Save and Clone.
      • ResourceListPage.tsx: New, Clone and Activate/Deactivate. It already reads useTenancyPosture.
      • catalog-scope.ts and catalog-activation.ts.
      • The position and capability editors, whichever files render them.
    • One new sentence saying why, en and zh, in views/metadata-admin/i18n.ts, the designer table.
    • The posture hook (packages/app-shell/src/hooks/useTenancyPosture.ts), and the operator-standing read where the console already carries it (isPlatformAdmin), only to read what the server publishes.
    • Assignment (AssignedUsersSection.tsx and the holder halves): read only, to pin that it stays writable.
    • Co-located tests, the new pins, and .changeset/12183-walled-catalog-readonly.md.
    • ⛔ No spec field. ⛔ No @object-ui/i18n pack key. ⛔ No published type or prop change: if one is needed, stop and report.
    • (Stop on breach; explain in the report.)
      Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
      Clause-②: no
      Responsibility: objectui's Setup catalog write gates: the metadata admin's writability does not read the tenancy posture, so under isolated or group a tenant admin holding manage_metadata is offered Save, Clone, New and Activate on position, permission-set and capability items | the server refuses every one since objectstack#22895 (06cc45dc36, ADR-0131 D3), which publishes the posture through the auth config's features.tenancyPosture | every walled tenant admin with manage_metadata, offered writes that 403
      Thread-read: none
      Serial constraints cleared: read at 2026-10-12T04:02Z on objectui main 5b20aed:
    • Duplicates: no remote branch or open PR touches this card's subject. Branches matching *12183*, *wall* and *posture*: 0. Open PRs whose file lists touch metadata-admin, useTenancyPosture, useCanAuthorMetadata or the catalog: 0.
    • This seat's objectui#12169 (PR objectui#12181) edited ResourceEditPage.tsx and PermissionMatrixEditor.tsx. It landed as 5b20aed, so the dev starts from it.
    • This seat's PR objectui#12180 (objectui#6349 batch 11, in the merge queue) touches packages/app-shell/src/types.ts, fields and sdui-parser. These are disjoint.
    • Epic objectui#7611 (marchtian, held by [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) objectstack#15194) handed this card to the lane. Coordination posts go on this card, per its body.
    • objectstack W2 (catalog-carrying package install under a wall) is in flight and out of this card's scope.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12183,
      "status": "done",
      "branch": "claude/issue-12183-walled-catalog-readonly",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12185",
      "session": "session_016djJF12Qt14ejKR5Vjt4sK — the seat session the relay stamped on the PR (mode:subagent, so the parent session).",
      "premise_still_valid": true,
      "summary": "Under `isolated` or `group`, a tenant holding manage_metadata was offered every catalog write the server refuses since objectstack#22895: New, Save, create, Reset/Delete, Clone, the active switch and the History rollback. One predicate, `walledCatalogReadOnly` / `useWalledCatalogReadOnly` in metadata-admin `catalog-scope.ts`, now folds into the existing caller tier (`canAuthor` → `canAuthor && !walled`) on ResourceListPage, ResourceEditPage and PermissionMatrixEditor. It reads only what the server publishes: `features.tenancyPosture` and the session's `user.isPlatformAdmin`. That flag is objectstack's `grants.posture === 'PLATFORM_ADMIN'`, from the same `resolveUserAuthzGrants` resolution whose rung the doors' `walledCatalogWriteVerdict` admits, so H2 resolved to 'served' and nothing is approximated client-side. Each page names the server rule where the wall decides, the operator keeps every write, `single` and an unknown posture are unchanged, and assignment stays writable on both halves. Rider landed: the designer auto-on effect reads `canWrite`.",
      "tests": "HEAD 3bdffbd (code unchanged since eed4f3e; 3bdffbd adds README prose only). Measurement on untouched main sources: the new pin file gave 17 failed / 10 passed, with every walled case offered the write and every control green once a harness slip was fixed (a writable form-only editor draws Edit twice). New pin file walledCatalogReadOnly-12183.test.tsx: 28/28. Union at eed4f3e of every test file naming a touched module, plus the 12141 and 12169 pins, the holder sections and tenancyPostureWall.parity: 104 files / 946 tests pass, in two batches: VERDICT command-exit 0, '52 passed (52) · 369 passed' and '52 passed (52) · 577 passed'. The 3 tests that read the app-shell README pass at 3bdffbd (58). Ablations ran through ablation-replace.mjs from committed state; each proved its mutation on disk and restored blob == HEAD with `git diff HEAD` empty. The subject resolves from src via a relative import and the vitest alias, so no dist was involved. F (predicate false): 14 red, 13 green. A (list): 6 red. B (editor): 4 red. C (matrix): 3 red. D (auto-on reads itemWritable): 1 red. E1/E2 (over-gate the holder sections): 1 red each. G (History rollback ungated): 1 red, and the failure is the expected assertion, a Rollback button rendered for the walled tenant. The first F attempt was refused by the tool before anything ran (the replacement re-contained the anchor); it was re-anchored and rerun.",
      "mcp_calls": "0 — no MCP tool used.",
      "api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, executed as POST /repos/objectstack-ai/objectui/pulls (draft) plus the relay assignee request for os-steve on objectui#12185; (2) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/12183/comments. Also 4 git pushes (not REST). Zero label writes, zero writes to objectstack-ai/objectstack content.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted in PR Acceptance notes, not filed (no public-door reach measured; code-read plus the editor-sheet twin pinned here). The standalone history route ResourceHistoryPage passes onRollback to every caller; it is reached from the permission-set editor's History button and by URL, and the rollback door refuses a walled tenant and any caller without manage_metadata. Family: the metadata editor caller tier (objectui#7611, pointer 6117177535). Fix shape: read the item and caller gates there. Dedupe words: 'ResourceHistoryPage rollback canWrite', 'HistoryPanel onRollback ungated', 'history rollback read-only caller'.",
        "carrier: 承接者:无 · noted, not filed (pre-existing misleading text, no reach measured). PermissionMatrixEditor's identity badge for a non-walled caller without manage_metadata falls into the type branch and reads 'Read-only (this metadata type has no runtime write channel)', which is false for permission. Dedupe words: 'perm.readOnly badge canAuthor', 'matrix badge names type tier'.",
        "carrier: 承接者:无 · noted, not filed (objectstack seam, inference only). plugin-auth effectiveTenancyPosture() publishes the requested posture when no tenancy service answers, while the doors' effectiveTenancyPosture(service) reads that absence as inert. On such a deployment the console shows read-only where the server admits: the conservative direction. Dedupe words: 'features.tenancyPosture fallback requested posture', 'getTenancy absent inert doors'."
      ],
      "gates": {
        "build_deps": "turbo run build --filter='@object-ui/app-shell^...' --concurrency=2 via os-verify-lock: VERDICT command-exit 0, 28 successful / 28",
        "type_check": "pnpm --filter @object-ui/app-shell type-check (echoed 'tsc --noEmit && tsc -p tsconfig.test.json'): exit 0 at eed4f3e; tsc -p tsconfig.test.json --listFilesOnly lists the new pin (1 hit in 5345 files; 12169 pin as positive control)",
        "vitest": "104 files / 946 tests pass at eed4f3e; new pins 28/28; README readers 58/58 at 3bdffbd",
        "check:i18n-designer-parity": "exit 0 ('Every en row has a zh row…')",
        "check:i18n-drift": "exit 0 ('No en value changed in this range'; 1 designer key added)",
        "check:i18n-keys": "exit 0",
        "check-changeset-presence": "exit 0 ('6 source file(s) of 1 released package(s) changed … declares 1 changeset')",
        "check-changeset-no-major": "exit 0",
        "check:changeset-claims": "exit 0",
        "check:pending-changeset-literals": "exit 0",
        "check:control-bytes": "exit 0; plus grep -naP control-byte self-scan of the 8 files: no hit",
        "check:new-line-citations": "exit 0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)')",
        "check-vi-mock-override-shape": "exit 0",
        "check-vi-mock-inherit": "exit 0",
        "check-vi-mock-specifiers": "exit 0",
        "check-test-path-roots": "exit 0",
        "doc_gates": "check-doc-links, check-doc-fence-languages, check-doc-json-fences, check-doc-expression-carriage: exit 0 at 3bdffbd",
        "eslint_targeted": "eslint, run as CI's per-package 'eslint .' runs it (inline config honoured), on the 6 touched .ts/.tsx files from --format json: 0 errors; per-file warning counts equal to main; new test file 0. Population: CI 'pnpm lint' = turbo run lint = each package's 'eslint .'. Invariance: eslint.config.js has no parserOptions.project/projectService (not type-aware), and no repo rule in eslint-rules/ reads other files from disk, so the diff moves no untouched file's verdict.",
        "check-readme-exports": "NOT MEASURED: exit 1 is PREREQUISITE (no package dist/index.d.ts built here; CI builds all packages first). The README diff adds no fenced block or import, so the gate input is unchanged.",
        "ci": "in_progress — not awaited, per contract"
      },
      "line_budget": "n/a — no skills/** or governed ledger touched. Diff +704/-43 over 8 files, 478 of the added lines in the new pin file.",
      "deviations": [
        "Zone 3: the gate does NOT read the environment scope. The server verdict has no scope input; REST PUT /meta asks walledCatalogRefusal before ?package=/mode=draft, so Studio's package-door drafts of a catalog item are refused too, and catalog-scope.ts's header already says the scope is 'never an authority'. Consequence: in Studio, for a walled tenant, these components are read-only for the 3 types too (list New, position editor, matrix grants). A pin covers the list and the matrix.",
        "Added within the claimed files: the editor's History sheet rollback (ResourceEditPage HistoryPanel onRollback) now reads canWrite, because the REST rollback door asks the same capability and walled verdicts. This also withholds rollback from a non-walled caller who cannot save (refused by the door too). Pinned, ablation G.",
        "File-surface addition: packages/app-shell/README.md (one paragraph in the Setup catalog gates bullet), required by objectui AGENTS.md #2. Outside the claim's list; declared here.",
        "H5: the walled sentence already existed (engine.catalog.readOnly.walled, objectui#12089, en+zh) and is reused on all three pages. The one new designer-table key is the matrix badge label perm.readOnly.walled (en+zh), not a sentence key.",
        "H4 is code-read of the doors plus the merged dogfood pins (wall-catalog-refusal.dogfood.test.ts and its controls); no live boot of objectstack by this dev.",
        "Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) over the harness reminder's model-named Co-Authored-By, per the reminder's own precedence clause."
      ],
      "files_changed": [
        ".changeset/12183-walled-catalog-readonly.md",
        "packages/app-shell/README.md",
        "packages/app-shell/src/views/metadata-admin/catalog-scope.ts",
        "packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx",
        "packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx",
        "packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx",
        "packages/app-shell/src/views/metadata-admin/i18n.ts",
        "packages/app-shell/src/views/metadata-admin/walledCatalogReadOnly-12183.test.tsx"
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR objectui#12185 (head 3bdffbd6e2) · domain:ui seat 1 · session_016djJF12Qt14ejKR5Vjt4sK · 2026-10-12T05:02Z

    Reviewed against GitHub and the diff, not the report (os-dev-report 6118051883).

    Verdict

    • Shape:
      • Draft on main. The first line is Fixes #12183, and Clause-②: no sits at a line start.
      • No closing keyword sits next to objectui#7611.
      • Assignee os-steve.
    • Scope: 8 files, +704/−43. 478 of the added lines are the new pin. check-governed-merges --pr objectstack-ai/objectui#12185: NOT governed, 747 changed lines.
    • H2, the operator standing: SERVED. The seat re-read it on objectstack origin/main.
      • The one refusal predicate, walledCatalogWriteVerdict (packages/metadata-core/src/meta-write-capability.ts), admits isSystem and callerPosture === PLATFORM_OPERATOR_POSTURE ('PLATFORM_ADMIN').
      • That rung is derived from the isPlatformAdmin evidence (packages/core/src/security/posture-ladder.ts), and the session's isPlatformAdmin is grants.posture === 'PLATFORM_ADMIN' (resolve-authz-context.ts).
      • The client reads user.isPlatformAdmin from the session. That reads the served fact; the server's verdict is not re-implemented.
    • The predicate, read in the diff: walledCatalogReadOnly in catalog-scope.ts is a security-catalog type (position, permission, capability) ∧ postureHasOrgWall(posture) ∧ ¬operator.
      • It folds into the existing caller tier as canAuthor && !walled on ResourceListPage (New, the active switch), ResourceEditPage (canWrite: Save, create, Reset/Delete, History rollback) and PermissionMatrixEditor (Save, Clone, the artifact caption).
      • An absent posture is inert, as the server's is: postureHasOrgWall(undefined) is false. single is unchanged.
      • Assignment is not gated: the holder sections are pinned writable, with ablations E1 and E2.
      • The served sentence engine.catalog.readOnly.walled (objectui#12089) is reused on all three pages. One new designer-table key, the matrix badge label perm.readOnly.walled, has en and zh.
    • The rider, landed: the designer auto-on effect reads canWrite (pointer 6117177535 on objectui#7611). Ablation D covers it.
    • Deviations, the seat's judgement on each:
      • The gate does not read environment scope. Accepted. The server's verdict has no scope input, and per its doc comment every catalog door asks it, "the /meta item write doors on both transports (save, reset, publish, rollback)". Studio's package-door drafts of these three types are refused for a walled tenant too, so drawing them writable there would offer a 403. The list and the matrix are pinned.
      • The editor's History-sheet rollback now reads canWrite. Accepted. It is in a claimed file, and the rollback door asks the same capability and walled verdicts. Ablation G covers it.
      • packages/app-shell/README.md: one paragraph. Accepted as forced by objectui AGENTS.md Add automated testing infrastructure and CI/CD workflows #2.
    • Clause-②: no: the predicate and its hook are module-internal. The package entry exports only ENVIRONMENT_SCOPE_QUERY from catalog-scope.js, and no prop or type member moves. Changeset: @object-ui/app-shell patch.
    • Evidence:
      • The new pin on untouched main: 17 failed / 10 passed. Every walled case was offered the write, and every control was green.
      • After the change: 28/28, and the union of 104 files and 946 tests passes.
      • Ablations F, A, B, C, D, E1, E2 and G each went red as predicted. Each restore was blob-equal to HEAD.
    • H4: a code read of the doors, plus objectstack's merged wall-catalog-refusal.dogfood.test.ts and its controls. There was no live boot. Declared.
    • Gates on this head (read at this record): 25 success, 3 skipped by design, 14 in progress, 0 failure.

    Out-of-scope findings

    • Two caller-tier gaps in the same family as objectui#7611, carried there by a pointer in this act:
      • the standalone ResourceHistoryPage offers rollback to every caller;
      • PermissionMatrixEditor's badge for a non-walled caller without manage_metadata reads "no runtime write channel", which is false for permission.
    • objectstack seam, inferred and not measured. With no tenancy service, plugin-auth publishes the requested posture while the doors treat the absence as inert. The console then shows read-only where the server would admit: the conservative direction. Acceptance notes.

    Landing: ready and auto-merge through the merge queue once every check on 3bdffbd is success or skipped by design.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#12185 → main d928c83 · domain:ui seat 1 · session_016djJF12Qt14ejKR5Vjt4sK · 2026-10-12T05:37Z

    • The merge: through the merge queue, on top of 7e30d0b. The card closed as completed through Fixes #12183, and no other card closed with it. objectui#7611 stays open.
    • Content check: all 8 files the PR changes are blob-identical at d928c83 to the accepted head 3bdffbd6e2, which ACCEPT 6118074273 covers.
    • On main now:
      • Under isolated or group, a caller who is not the platform operator is read-only on position, permission and capability: New, Save, create, Reset/Delete, Clone, the active switch and the editor's History rollback.
      • Each page names the server rule (ADR-0131 D3) as the reason.
      • The rule is one predicate, walledCatalogReadOnly in metadata-admin/catalog-scope.ts. It reads the served features.tenancyPosture and the session's isPlatformAdmin, the same rung objectstack's walledCatalogWriteVerdict admits.
      • The operator keeps every write. single and an unknown posture are unchanged. Assignment stays writable on both halves.
      • The designer auto-on effect reads canWrite.
    • For the epic seat ([epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) objectstack#15194):
      • This closes the C9 remainder the card was handed for.
      • Two caller-tier gaps in the same family are on objectui#7611 (pointer 6118079494): the standalone history route's rollback, and the matrix badge naming the wrong tier.
      • Marketplace install under a wall (W2) stays the epic seat's, as the card said.
    • State: pm:dispatched is removed in this act. domain:ui, priority:p1 and target:v18 stay.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions