Skip to content

[seam → cloud] Census the cloud repo for readers that treat a positions[] / sys_user_position NAME as authority (the out-of-reach half of #15972's first deliverable) #17045

Closed

Description

@os-bill

Named reader: the repo:cloud execution seat (#6026), at its queue-scan step. Seam card: the work lands in objectstack-ai/cloud, which is not reachable from the filing session, so per the cross-repo rule it lives here with repo:cloud. Filed by the director seat (summon #20, session_01Tep4AYXZvyBA7jsvne5KZV, os-bill) executing the maintainer's ruling A on #15972 (decision batch #105 item 4, 2026-09-09T04:3xZ; ruling comment on #15972: "Ruling recorded — A: refuse the built-in identity names on sys_position.name at write time").

What is asked (one deliverable, measurement only)

A census over the cloud repository of every reader that turns a name found in positions[] (session payload) or in a sys_user_position row into authority — positions.includes(<built-in name>), string comparison against platform_admin / org_owner / org_admin, or any equivalent — instead of reading the capability rung. The objectstack/objectui half of this census is delivered in the PR that implements #15972 (its ## 验收备注); this card is the cloud half.

Acceptance, one line per item:

  1. The grep set and its firing control are stated (a zero-hit census across an unfamiliar repository proves nothing — A tenant can mint a sys_user_position row spelling any built-in identity name — PR #15948 closed every reader, nothing stops the row #15972: "with a control that fires"). Suggested control: the same pattern over a file known to read the rung, or a deliberately planted fixture, so the instrument is shown to fire.
  2. Every hit is listed with file:line and classified: reads the rung (fine) · reads the name as authority (defect) · other.
  3. Each name-as-authority hit becomes its own card in cloud (dedupe first), linked back here; zero hits closes this card with the control on record.
  4. ⛔ No fix is written from this card; it is a measurement.

Why it exists

#15972 (ruled A): the platform will refuse built-in identity names on sys_position.name at write time, so the row can no longer be minted. Readers outside this repository that already trust such a name would keep a live hole until the write-side refusal is deployed everywhere and existing rows are reported; the card's first deliverable was this fleet census, and the cloud repo was out of reach for every seat that has held it (#15972 body §"Out-of-repo readers are unmeasured"; 5555586823).

Governing text: ADR-0068 (built-in identity roles); packages/core resolve-authz-context.ts:1125-1129 (「Read the RUNG — never positions.includes(...)」); #15136 / PR #15948 ruling A.

Blocked-by: none — this census does not depend on the platform-side refusal landing.

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)
    on Sep 9, 2026
  2. hotlong commented on Sep 15, 2026

    @hotlong
    Contributor

    Census delivered: zero readers in cloud turn a built-in identity NAME into authority. Controls fire on the identical shape, so the zero is a reading.

    repo:cloud execution seat, R38, session session_01TAUTP6Yky8QWoHUAPDKNJQ, 2026-09-15T15:59Z. Measured on cloud cb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8 — the REST tip of main (2026-09-15T15:12:03Z), ⛔ not the container clone's self-report, which is shallow. Measurement only, per acceptance item 4: ⛔ no fix is written from this card.

    Acceptance 1 — the grep set, and the control that fires

    # pattern hits
    T1 positions\??\.(includes|has)\( 2
    T2 (===|==|!==|!=) against 'platform_admin'|'org_owner'|'org_admin', either side 0
    T3 \.(includes|has)\(\s*['"](platform_admin|org_owner|org_admin)['"] 0 (1 comment)
    T4 files reading sys_user_position 19 (5 non-test)
    T5 the three names in ANY context, non-test 7 files

    Firing controls — same instrument, same corpus, same command shape, differing only in the literal:

    • T2's shape with a role-ish literal that does occur → 6 files, e.g. organization-lifecycle.ts:871 role === 'owner' || role === 'admin', environment-lifecycle.ts:556, package-install.ts:1092.
    • T3's shape exactly, with 'owner'/'admin' → environment-authz.ts:97 roles.includes('owner') || roles.includes('admin').

    ⇒ The instrument demonstrably matches this construction in this repository; only the three built-in identity literals are absent. That is what makes T2/T3's zeros readings rather than a silent miss — the condition #15972 set ("with a control that fires").

    Acceptance 2 — every hit, classified

    T1 — 2 hits

    file:line classification
    packages/service-ai/src/routes/agent-access.ts:89 — allowList.some((entry) => entry === user.userId || positions.has(entry)) other — see the note below
    apps/objectos-ee/test/signup-membership-policy.e2e.test.ts:585 not a read — a comment, and a conforming one: it records that "objectstack#15948 forbids positions.includes('platform_admin') outright, because the array now sits on an axis a tenant admin can write via sys_user_position and could therefore mint the name", and that user.isPlatformAdmin "is derived from the posture rung — never from this array"

    T4 — the 5 non-test sys_user_position readers, none of them name-as-authority

    file classification
    security-enterprise/src/hierarchy/resolver.ts reads the anchor, fine — takes sys_user_position.business_unit_id, an id, never the name
    security-enterprise/src/plugin.ts:238 other — the table name appears in a STRUCTURAL cache-invalidation set
    service-ai/src/routes/agent-access-lint.ts other — reads live assignments to validate an agent's declared entries; a lint, not a gate
    service-ai/src/pending-action-actor.ts:155 not a read — comment naming the shared resolver
    service-cloud/src/migrations/rbac-orgless-claim.ts:14 not a read — comment in an orgless-row migration

    T5 — the 7 non-test files naming one of the three, classified

    • False positives of the grep, worth recording so the next census does not re-derive them: control-plane-org-scope-plugin.ts:774,836 and environment-crud.ts:753,783 match only because the audit event names cross_org_admin_read / cross_org_admin_write contain the substring org_admin. ⛔ Not identity names at all.
    • Comments / vocabulary, not reads: env-owner-admin-grant.ts:44, ai-routes.ts:46, agent-access-lint.ts:58,72.
    • A name, but not an authority read: app-cloud-admin/src/admin.app.ts:31 — name: 'platform_admin' is the App's own name, kept "for stable identity (deep links, RBAC, approver refs)". Its actual gate is ADMIN_ONLY = ctx.user.isPlatformAdmin == true — the rung.
    • An approver reference: marketplace-review-flow.ts:50 — approvers: [{ type: 'user', value: 'platform_admin' }]. A routing target for review, ⛔ not a decision taken on a caller's positions. ⚠️ Recorded because type: 'user' carrying an identity name rather than a user id is unusual enough that a later reader will stop on it; it grants nothing.

    ⭐ And the positive half of the census, which matters as much as the zero: cloud's own isPlatformAdmin (service-cloud/src/platform-admin.ts:68-73) resolves through sys_user_permission_set → sys_permission_set, a permission-set link — ⛔ never a position name. That is the correct pattern, in the one place in this repository that answers "is this caller a platform admin".

    Acceptance 3 — zero name-as-authority hits ⇒ no cards filed, and this card closes

    ⚠️ One reading gets a paragraph rather than a card, stated so the judgement is auditable rather than silent. evaluateAgentAccess (service-ai/src/routes/agent-access.ts) is the only place in cloud where a position name carries any authority:

    • const held = new Set([...(user.permissions ?? []), ...(user.positions ?? [])]) at L75, so an agent.permissions entry — documented as "Required permission-set capabilities" — is satisfiable by a position name;
    • positions.has(entry) at L89 matches agent.access entries against position names, which is ADR-0057 D4's intended ACL-by-position mechanism, and agent-access-lint.ts exists to validate those entries against live assignments.

    ⛔ It is not in this card's class, for a reason the card's own scope draws: it hard-codes no built-in identity name. The names it compares are supplied by an agent author, and the function's own docblock records the union as deliberate — before protocol 17 removed the roles alias, "a permissions entry naming one could not be satisfied". Ruling A closes the mint side for the built-in names at write time, which is exactly the exposure that would make this reading dangerous. ⇒ Nothing here is owed by #15972's first deliverable; it is recorded so the next census does not re-open it as a find, and so that anyone widening ruling A knows where cloud would feel it.

    ⛔ No fix, no card, no re-grade. Closing per acceptance item 3 with the controls on record, pm:queue stripped in the same stroke.


    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

No one assigned

    Labels

    repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions