Skip to content

fix(plugin-auth): a failed read in the last-administrator guard's default-slug capture rule answers 403 PERMISSION_DENIED, while auth-manager's sibling slug guard answers 503 for the same fault — give the read fault one code #25364

Closed

Description

@objectstack-fleet

Filing gate ③: a finding of the contract review of PR #25286 (#25271; record 6352123147, ① 4), carried on the #15212 ledger (6352526831). It is outside that PR's surface.

  • Who acts: the shared queue.
  • Dedupe: none.

What is on main

  • The last-administrator guard's sys_organization beforeUpdate rule (packages/plugins/plugin-auth/src/last-admin-guard.ts, guardOrganizationDefaultSlug, from fix(plugin-auth)!: under single, no slug change makes another organization the Default Organization #25286) answers a read it cannot complete with 403 PERMISSION_DENIED. That is the guard file's failClosed shape, and the text says the read failed and to retry.
  • The sibling slug guard in auth-manager.ts answers the same kind of fault with 503 SERVICE_UNAVAILABLE (slugGuardReadFaultApiError).
  • So a caller cannot tell "you may not" from "try again" by code on this door.

Wanted

  • One code for a read fault across both guards. Most likely 503 SERVICE_UNAVAILABLE, or a dedicated code with its own row in packages/spec's error-code ledger (ADR-0112).
  • The refusal on the merits stays 403 PERMISSION_DENIED.
  • Measure first: every half of the last-administrator guard uses failClosed. Say whether to change one half or the shared shape, because changing one half alone splits the guard's vocabulary.

Acceptance

  • A read fault answers the same code from both guards, pinned at the HTTP door (organization/update) and the engine door.
  • A refusal on the merits is unchanged.
  • R1–R4 hold.

Activity

  1. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01CuJfNjvts78GKesupEdgbz (a card thread scheduled by the project's coordinator, which follows .claude/agents/os-dev.md as its role file; the seat session is session_014Hse9cj2i4o5UAVrd21NKr)
    Account: os-tesla
    Branch: claude/project-thread-ef9bkc (the thread's designated branch: a card thread can push only that branch, a measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-25364
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface: packages/plugins/plugin-auth/src/last-admin-guard.ts (failClosed and its read-fault refusal) + its tests, plus any repo-wide pin on the old read-fault code (a domain:services package the seat declares) (stop on breach; explain in the report)
    Container & model: S (non-mechanical, so M treatment), mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: plugin-auth's last-administrator guard produces it | the sibling slugGuardReadFaultApiError already answers 503 | any caller of organization/update during a read fault, rare and fail-closed either way
    Thread-read: none
    Serial constraints cleared: #25338 (this seat, in flight) edits plugin-auth's create-user route, a different file. No other in-flight card of either engine seat edits last-admin-guard.ts; epic #15212 introduced the rule in #25286 (merged).

    Clause-② reading: no, an existing code is reused and a fail-closed refusal keeps refusing; a needed new code stops the dev. Dispatch order: /mnt/project-files/engine-seat1/dispatch-25364.md (+ common.md). Ruling carried: none (governing text ADR-0112). Claimed 2026-10-23T04:51Z.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 25364,
     "status": "done",
     "branch": "claude/project-thread-ef9bkc",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/25388",
     "head": "3aee19812a51779419547eff2374614e19c0828f",
     "session": "session_01CuJfNjvts78GKesupEdgbz",
     "premise_still_valid": true,
     "summary": "Measured: every judging half of the last-administrator guard runs in failClosed, which answered a read fault 403 PERMISSION_DENIED; the Default Organization slug-capture rule has its own catch with the same 403. Changed the shared shape: a module-private readFault() (code SERVICE_UNAVAILABLE, status 503, object, cause) thrown by failClosed and the slug rule, both passing an inner read fault through so nested envelopes do not double-wrap. Two doors the refusal crosses had to follow in the same package: objectql-adapter.ts gains a 503 arm (without it organization/update answered 500 with no code, measured by reverting only that file), and auth-manager-actor-guards.ts maps the standing judge's read fault on organization/delete to ORGANIZATION_DELETE_READ_FAULT_CODE. Refusals on the merits, including the maxScan overflow, stay 403 PERMISSION_DENIED; no refusal became an allow.",
     "tests": "plugin-auth full suite at 3aee1981 under os-verify-lock: VERDICT command-exit 0, Test Files 187 passed (187), Tests 3528 passed | 10 skipped. Pins: organization/update HTTP door {status:503, code:SERVICE_UNAVAILABLE} with driver text withheld and slug unchanged; engine door {code:SERVICE_UNAVAILABLE, status:503}; 3 flipped fail-closed pins in last-admin-guard.test.ts; new: maxScan overflow stays {PERMISSION_DENIED,403}, judgeOrganizationDelete inner read fault answered once, organization/delete via the real guard 503 with nothing attempted and CONTROL 403 PERMISSION_DENIED. Reverse check (src only, tests import src, so no dist leg): the three source files restored to d12e650 gave Tests 7 failed | 137 passed over the four touched test files, every new/flipped pin red; restored from HEAD, git diff HEAD empty, porcelain clean. Adapter-only revert: HTTP pin reads expected {status:500, code:null}.",
     "gates": "pnpm --filter @objectstack/plugin-auth typecheck (tsc src, tsconfig.examples, check:test-typecheck): VERDICT command-exit 0. dispatch-gates --commands at 3aee1981: 69 derived; 67 run exit 0; --ran verdict: 69 accounted, 67 run, 2 NOT-MEASURED, 0 UNRUN. NOT MEASURED (exit 3 prerequisite): node scripts/check-plugin-teardown-shape.mjs --self-test (pinned fixture commit unreachable in this shallow clone), pnpm check:dual-build-cjs-loads (needs a full monorepo build). PR check-runs: in_progress at report time.",
     "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
     "line_budget": "8 files, +209 / -24 (source +111/-15, tests +82/-9, changeset +16)",
     "files_changed": [
      ".changeset/25364-last-admin-guard-read-fault-code.md",
      "packages/plugins/plugin-auth/src/last-admin-guard.ts",
      "packages/plugins/plugin-auth/src/objectql-adapter.ts",
      "packages/plugins/plugin-auth/src/auth-manager-actor-guards.ts",
      "packages/plugins/plugin-auth/src/last-admin-guard.test.ts",
      "packages/plugins/plugin-auth/src/last-admin-guard.default-organization.test.ts",
      "packages/plugins/plugin-auth/src/single-reslug-default-anchor.test.ts",
      "packages/plugins/plugin-auth/src/organization-delete-read-fault.test.ts"
     ],
     "deviations": [
      "Landing widened inside the same package beyond last-admin-guard.ts: objectql-adapter.ts (the better-auth door; without its arm the read fault answers 500) and auth-manager-actor-guards.ts (the organization/delete standing judge mapped only PERMISSION_DENIED). Both are plugin-auth, domain:services; named in the PR body.",
      "The adapter arm keys on the declared pair (code SERVICE_UNAVAILABLE and status 503), not on the guard, so any engine error declaring that pair now answers 503 at the better-auth door instead of an empty 500; recorded under Acceptance notes.",
      "Two derived gate families NOT MEASURED locally (exit 3 prerequisite), left to CI."
     ],
     "mcp_calls": "3 — mcp__github__issue_read x3 (get, get_comments x2); no write tool",
     "api_writes": "2 relay writes — pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/25388/assignees) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/25364/comments); plus 3 git pushes to claude/project-thread-ef9bkc",
     "open_questions": [],
     "out_of_scope_findings": []
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25388 comment 6358536499 (ready + auto-merge set 2026-10-23T06:01Z).


    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:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpm:dispatchedpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions