Skip to content

spec(security): PermissionSetSchema.managedBy is a dead authored key (ledger dead, 2026-10-11), and PR #23870's new help text and the option labels promise upgrade behaviour from it — retire the key (enforce-or-remove) #23891

Closed

Description

@objectstack-fleet

Filing gate ①: published text that promises a behaviour the runtime does not keep, on a key the liveness ledger already rules dead. reach: an administrator on Setup's Permission Sets edit form, in four locales, and every author or AI who reads PermissionSetSchema in the published @objectstack/spec source.

Filed by domain:spec seat 1 (#6017) · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9, from the contract review of PR #23870 (#23853, comment 6214609026, VERDICT FAIL, finding 1 and escalation 3). The review was owed before the queue. It ran as a post-merge audit, because the PR merged as d0fe050489 while the seat was trying to pull it (landing 6214616740 on #23853 records why). ⛔ Not a claim; lane, grade and whether a ruling is owed are triage's.

What is published now

  • packages/spec/src/security/permission.form.ts:52, the managedBy help text PR fix(spec): the Permission Set, Position and view form help texts Setup shows are plain admin copy #23870 wrote: "Who owns this set: Package means it ships with a package and package upgrades update it; Platform and User mean it was created in this environment and upgrades leave it alone." It is regenerated into en and translated into zh-CN, ja-JP and es-ES in packages/platform-objects/src/apps/translations/*.metadata-forms.generated.ts.
  • The same row's option labels, unchanged by that PR, make the same promise: "Package (upgrade-owned metadata)", "Platform (environment config)", "User (environment config)".
  • PermissionSetSchema.managedBy's TSDoc and .describe() (permission.zod.ts, near :906-:915) say package rows are "seeded by bootstrapDeclaredPermissions, re-seeded on upgrade".

What reads the authored key

Nothing does.

  • packages/spec/liveness/permission.json, row props.managedBy: status: dead, verifiedAt: 2026-10-11. Its note says no code reads the AUTHORED key, bootstrapDeclaredPermissions only stamped the column and never read ps.managedBy, and that seeder is retired (ADR-0131 D3: the boot writes no catalog row). It also says: "Retirement of the authored key is an enforce-or-remove item for the spec seat."
  • The seat's re-read on origin/main: every non-test .managedBy read in packages/** is an object-level ObjectSchema.managedBy (objectql, lint, core, plugin-auth, organizations, plugin-security's seed-ownership claims). None reads a permission set's.
  • The review: packaged-permission-set-lock.ts decides a set's provenance from the artifact, not from this key.

So the help text, the labels, the TSDoc and the .describe() all describe a key the runtime ignores. Prime Directive #10 applies: declared means enforced.

The two remedies

  1. Retire the authored key (the ledger's own prescription; the seat's recommendation). A retiredKey() tombstone on PermissionSetSchema.managedBy with the full spec-property-retirement kit: the D3 entry, the RETIRED_KEYS_BY_MAJOR[18] row, baselines, docs, and a BREAKING changeset with Clause-②: yes (narrowing). The form row leaves permission.form.ts with its help text, labels and translations. The sys_permission_set.managed_by column is a different surface and is not touched. Four axes:
    • business need: measured zero readers;
    • long term: no declared-but-unenforced key;
    • AI-proofing: an author can no longer write a value that silently does nothing;
    • startup stage: retire immediately, with no window.
  2. Correct the text only (the fallback if remedy 1 is not ruled before the next release). Narrow the help text and the three labels to what is true. For example: the value is recorded provenance; which rows a package upgrade updates is decided from the package itself, not from this field. Then re-extract en and rewrite the three locale values. This keeps a dead key declared, which is why it is the fallback.

Either way the false sentence must not reach a published release. The Version Packages PR #23825 is open, so the next release carries whatever main holds when it lands.

Dedupe

MCP search_issues on this repo: PermissionSetSchema managedBy dead authored key retire enforce-or-remove provenance → 18 hits. None retires the authored key.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p1 · target:v18 · domain:spec · area:access · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T12:52Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Re-read on origin/main d5666be876:
      • packages/spec/src/security/permission.form.ts:52 carries the new managedBy help text.
      • packages/spec/liveness/permission.json's managedBy row is status: dead, verifiedAt: 2026-10-11. Its note already says the retirement is an enforce-or-remove item for the spec seat.
    • No ruling is owed. The ledger has the verdict, and the remove route is ADR-0049's standing policy (the spec-property-retirement playbook).
    • Why p1 + target:v18: retiring an authored key is the protocol-first class. Meanwhile published help text in four locales promises upgrade behaviour nothing keeps.
    • ⛔ Keep the two managed_bys apart. The sys_permission_set.managed_by COLUMN stays alive. The ownership ceremony reads it, and ruling A on [Decision] The ownership ceremony classes the 8 shipped permission sets an old seeder stored as managed_by='admin' as authored and says to rename them: mirror them, keep blocking with the true remedy, or ask per row? #23873 (6213944432) just gave it a legacy arm. Only the AUTHORED key PermissionSetSchema.managedBy retires, with its form row, its option labels and its TSDoc.
  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 13 (batch 2) · 2026-10-16T14:28Z · retire the authored key PermissionSetSchema.managedBy (remedy 1, the ledger's prescription; triage 6215910642: no ruling owed), with its form row, option labels, TSDoc and translations. One PR, Fixes #23891. The sys_permission_set.managed_by column is not touched.
    Session: session_01HjR8RUwTPQ3TgbfKihmxp9 (the dispatching seat; the dev runs as a cloud session the seat post names)
    Account: os-elon-musk (the seat's linked user as GET /user answers it; this card's assignee)
    Branch: claude/issue-23891-permission-set-managedby-retired
    Worktree: objectstack-issue-23891 (in the cloud session's container)
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (paths on origin/main 161aee1047 or later; stop on a breach and explain it in the report):

  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23891,
    "status": "done",
    "branch": "claude/issue-23891-permission-set-managedby-retired",
    "pr": "#23989",
    "session": "session_01D3Ur6XgfkkaDUCCTkviXAr",
    "premise_still_valid": true,
    "summary": "Retired the authored key PermissionSetSchema.managedBy with a retiredKey() tombstone and the full spec-property-retirement kit: D2 permission-set-managed-by-removed (MAJOR_18 order 67, retiredFromLoadPath, retiredAfter 17.7.0), D3 permission-set-managed-by-retired, RETIRED_KEYS_BY_MAJOR[18] security/PermissionSet:managedBy, a STEP18_RATIONALE fragment (order 99), a removal-version JOIN row, the ledger row kept dead with a REMOVED note (verifiedAt 2026-10-16), the Setup form row with its help text and three labels removed, and the four metadata-forms bundles regenerated by their generator. A stored row and an installed package's artifact still load, because the existing ADR-0087 rehydration seams strip the key, while the authoring, save and publish doors refuse it by name. #23904 has no PR, and no second mechanism was built. The sys_permission_set.managed_by column, its seeders, the ownership ceremony, ObjectSchema.managedBy and objectui are untouched. Measured writers: example apps 0, plugin-security default sets 0, HotCRM v4.0.1 (1d7148bf2d) 0 of 7 sets (control objects: 7), Setup form row (stored rows, healed by D2). Readers of the authored key: 0. objectui at daeb77fc84 reads draft.managedBy only through its own local PermissionSetDraft type, so the pinned build does not break.",
    "tests": "All on 076958c after merging main unless noted. New pin packages/spec/src/security/permission-set-managed-by-retirement.test.ts: 17 passed (repo project). spec vitest src/ in four batches before the merge: 34 files/1154 tests + 140/3419 + 234/6381 + 273/9493 = 681 files/20447 tests passed; after the merge, src/security+conversions+migrations+shared + conversions-major18-merge + step18-rationale-merge + scripts/liveness: 83 files/2445 tests passed; scripts/ batch 17 files/428 passed (merge tests, build-migration-registry-entry, authorable-defaults, nested-authorable-keys, liveness). A full single vitest run of spec and of spec scripts/ exceeded the 10-minute foreground cap (exit 124) and was split as above. platform-objects vitest: 73 files/1136 tests passed (two pins updated: setup-copy-vocabulary leaf, catalog count 674 to 673). runtime permission-set-clone.test.ts: 23 passed. Ablations (scripts/ablation-replace.mjs; each restored to the HEAD blob with git diff HEAD empty; spec vitest reads src, so no dist leg): (1) the key restored as z.enum(...).optional() turns 7 tests red; (2) the D2 entry unwired from MAJOR_18_CONVERSIONS turns 4 red (stored row, artifact, strip/idempotence, chain wiring); (3) the @ts-expect-error deleted turns check:test-typecheck red, naming the file.",
    "gates": "Local, exit captured before any pipe: spec build 0 (twice, the second after the merge); spec typecheck 0; spec check:generated 0 (all 14 up to date on 076958c); check:liveness 0, check:empty-state 0, check:exported-any 0, check:dual-source-exports 0, check:variant-docs 0; check:i18n 0; platform-objects typecheck 0; check:type-check-debt 0. dispatch-gates --commands --repo objectstack-ai/objectstack derived 113 families; --ran with exit codes: 113 accounted, 110 run, 3 NOT MEASURED (exit 3 prerequisites: check-plugin-teardown-shape --self-test on a shallow clone; spec check:skill-examples, client-react declarations not built (its build failed in DTS here); check:dual-build-cjs-loads, packages never built here). check:dts-closure exit 1: NOT MEASURED, because 54 packages lack .d.ts after this session's own OS_SKIP_DTS=1 closure build; none is a package this diff changes. check:cross-package-test-inputs was red once (the new tree walk was not in vitest.repo-tests.json), fixed, re-run exit 0. Ratchets on 076958c: authorable-surface +1/-1 ([RETIRED] mark), api-surface, api-surface-signatures and json-schema.manifest unchanged, as the tombstone route predicts. CI on the PR: in_progress at report time, not waited on.",
    "mcp_calls": "7: mcp__github__issue_read x4 (get + get_comments on #23891 and on #23904), mcp__github__search_pull_requests x1, mcp__github__pull_request_read x1 (get #23989, read-back), mcp__claude-code-remote__add_repo x1 (objectstack-ai/hotcrm, access read: answered read_available, nothing attached). No GitHub MCP write tool.",
    "api_writes": "3 relay strokes' worth: (1) git push to origin claude/issue-23891-permission-set-managedby-retired (8 pushes, git, not REST); (2) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (204), executed as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23989/assignees; (3) this os-dev-report comment through the same relay: POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos//issues/23891/comments. No label write: the diff publishes, so skip-changeset does not apply.",
    "line_budget": 732,
    "files_changed": [
    ".changeset/23891-permission-set-managed-by-retired.md",
    "content/docs/permissions/permission-sets.mdx",
    "content/docs/references/security/permission.mdx",
    "packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts",
    "packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts",
    "packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts",
    "packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts",
    "packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts",
    "packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts",
    "packages/spec/authorable-surface/security.json",
    "packages/spec/liveness/permission.json",
    "packages/spec/src/conversions/registry.ts",
    "packages/spec/src/migrations/entries/retired-keys/18.security__PermissionSet__managedBy.ts",
    "packages/spec/src/migrations/entries/semantic/18.permission-set-managed-by-retired.ts",
    "packages/spec/src/migrations/registry.ts.template",
    "packages/spec/src/security/permission-set-managed-by-retirement.test.ts",
    "packages/spec/src/security/permission.form.ts",
    "packages/spec/src/security/permission.zod.ts",
    "packages/spec/src/shared/retired-key-removal-version.pin.test.ts",
    "packages/spec/vitest.repo-tests.json"
    ],
    "deviations": [
    "content/docs/permissions/permission-sets.mdx (one word removed from the clone door's dropped-key list) is outside the claim's listed file surface; the playbook's docs row requires it.",
    "Three files the claim did not list follow from the listed surface: packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts and packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts (pins over the regenerated bundles) and packages/spec/vitest.repo-tests.json (check:cross-package-test-inputs requires it for the tree walk).",
    "The tree-scoped absence pin cannot match the bare key name, because ObjectSchema.managedBy is live. It judges managedBy only inside an object literal that also carries a key only a permission set declares, and it states that bound in the file.",
    "Full spec vitest did not fit the 10-minute foreground cap and was run in five path batches. The split is declared, and CI runs the whole set.",
    "The changeset carries Clause-②: yes (narrowing); the PR body copies the claim's Clause-②: yes verbatim.",
    "The PR is not subscribed for activity, per the os-dev contract (the report is delivered at local verification; CI convergence is the PM's)."
    ],
    "open_questions": [
    {
    "question": "The isDefault form row (colSpan 1) now sits alone on its row, because managedBy was its pair. Widen it to colSpan 2?",
    "options": [
    "A: leave it as it is (no layout change on this card)",
    "B: widen isDefault to colSpan 2 in a follow-up"
    ],
    "recommendation": "A, because it is cosmetic and outside the card's ask."
    },
    {
    "question": "packages/runtime/src/domains/permission-set-clone.ts still lists managedBy in PERMISSION_SET_CLONE_DROPPED_KEYS. After this PR the entry is inert. Remove it?",
    "options": [
    "A: leave it; it is harmless defence and outside this card's surface",
    "B: remove it on the next card that touches the clone door"
    ],
    "recommendation": "A, because no body that reaches the door can carry the key and dropping it changes nothing."
    }
    ],
    "out_of_scope_findings": [
    "carrier: the seat (follow-up card at landing) · objectui at daeb77fc84 still reads draft.managedBy for the provenance badge: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx:1365 and :1606, permission-slice.ts:62 (managedBy?: string on the local PermissionSetDraft), permission-set-clone.ts:13 (comment), PermissionMatrixEditor.badge.test.tsx:56-74 and PermissionMatrixEditor.provenanceBadge-7611.test.tsx · noted, not filed · dedupe words: objectui provenance badge managedBy permission set retired",
    "carrier: none · runtime clone door's PERMISSION_SET_CLONE_DROPPED_KEYS still names managedBy (inert after this PR) · noted in the PR's Acceptance notes, not filed"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23989 comment 6221238601 · domain:spec seat 1 · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T17:41Z · contract review (CONTRACT_REVIEW_TIER) PASS 6221190206 on the same head 076958cb25; the head is green and goes to the queue in this act.

    PermissionSetSchema.managedBy is retired: every authoring, save and publish door refuses it by name, and a stored row or an installed package carrying it still loads, with the key stripped. The column and the ownership ceremony are untouched. The objectui badge card follows at landing. The cloud dev session is archived at this ACCEPT.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #23989 → bbdfc0576a (Fixes #23891, PermissionSetSchema.managedBy retired) · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T18:15Z

    probe reading
    PR object merged 2026-10-16T18:13Z out of the merge queue (entered 17:42:45Z); merge commit bbdfc0576a
    origin/main git merge-base --is-ancestor bbdfc0576a origin/main → exit 0
    merged vs reviewed head 18 of 20 files' blobs at bbdfc0576a equal the reviewed head 076958cb25 (ACCEPT 6221238601, contract review PASS 6221190206). The other two, conversions/registry.ts and retired-key-removal-version.pin.test.ts, also carry PR #23954's and PR #23962's rows that landed first; in both, the merge commit adds and removes exactly the PR's own lines (+71/−0 and +2/−1, line sets equal)
    queue branch no gh-readonly-queue/main/pr-23989-* ref remains
    mis-close scan Fixes #23891 is the body's only closing keyword; this card closed by that PR, as intended, and nothing else did

    pm:dispatched is cleared in this act. The cloud dev session was archived at the seat's ACCEPT.

    The follow-up the ACCEPT promised: objectstack-ai/objectui#12753, filed bare for objectui triage. The provenance badge's managedBy arm (PermissionMatrixEditor.tsx:1365 / :1606), the draft type's field (permission-slice.ts:62) and the badge tests, measured at objectui main 2dbe5f53.


    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

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:specpriority:p1High: required for production / M2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions