Skip to content

fix(spec): the Permission Set, Position and view form help texts Setup shows are plain admin copy - #23870

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23853-form-help-texts-plain
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23853-form-help-texts-plain

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23853

Clause-②: no

What changed

Six help texts an administrator reads in Setup carried ADR numbers. Each one is now plain admin copy. The ADR reference moved to the comment beside the form row, the shape of #23817.

Form leaf Before After
permission · isDefault App baseline for the everyone position (ADR-0090 D5): an app-level set is auto-bound at boot; a package-level set becomes an install-time suggestion an admin confirms. Default false. Gives this set to everyone who signs in, through the built-in everyone position. A set the app itself declares is granted at startup; a set from an installed package becomes a suggestion an admin confirms in Setup. A set with high-privilege permissions is never granted this way. Default false.
permission · managedBy Record provenance (ADR-0086 D3): who owns this set across upgrades. 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.
permission · packageId Owning package id for a package-shipped set (ADR-0086 D3). Leave empty for an environment-authored set. The package that ships this set; a value naming any other package is refused. Leave empty for a set created in this environment.
permission · adminScope Delegated administration (ADR-0090 D12): lets holders ... Delegated administration: lets holders ... (rest unchanged)
position · delegatable Holders may delegate this position themselves, time-boxed and with a reason (ADR-0091 D3). Default false — delegation is admin-only. The same sentence without "(ADR-0091 D3)".
view · section end_user_controls description What end users can do on this view — quick filters, filter tabs, visualization switching (ADR-0047, Airtable Interface parity). What end users can do on this view — quick filters, filter tabs, visualization switching.

Each sentence checked against the code that honours the field

  • isDefault: PermissionSetSchema.isDefault's docblock and plugin-security's builtin-positions.ts. An app-level set is auto-bound to the everyone position at boot. Every authenticated member holds that position implicitly. A package-level set materializes a sys_audience_binding_suggestion that an admin confirms in Setup. A high-privilege set cannot be bound to the anchor.
  • managedBy: package rows are re-seeded on upgrade. platform and user rows are environment config that package seeding never touches.
  • packageId: a value that disagrees with the package that ships the set, or that the set is saved into, is refused at every door (judgePermissionSetOwnerBinding). That includes any value on a set no package ships.
  • adminScope and delegatable: the rest of each text was already operational, and [finding] AdminScope.manageBindings and authorEnvironmentSets are honoured by no write path after C3; the ADR-0090 D9/D12 junction gates are unmeasured; permissions docs describe removed gates #23488 aligned the adminScope keys with the live schema. Only the ADR prefix moved.
  • The view's end-user controls: the userFilters tabs element (view.zod.ts) and appearance.allowedVisualizations are live keys, so "filter tabs" and "visualization switching" stay. The retired tabs repeater is not named.

Translations

  • pnpm i18n:extract regenerated en.metadata-forms.generated.ts: 6 leaves.
  • The zh-CN, ja-JP and es-ES values for the same 6 leaves were hand-written translations of the old wording, and merge mode keeps them. So they were rewritten in place to the new meaning, before the extract. Values only: no key was added or removed.
  • The *.source-hashes.generated.ts tables did not change. "Setup" stays untranslated, as the sibling leaves in each locale already leave it.

Test

packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts gains two cases. No assertion was deleted.

  • Bright leg: across all four metadataForms bundles, each of the six leaves must be a string (the population cannot shrink) and must carry no ADR number, no decision id (D plus digits), no finding F, no emit() and no kernel. It uses the matcher this file already uses.
  • Dark leg: the same walk over the replaced prose names all six leaves.

The file docblock's population item 3 names the six leaves.

No other test pins these strings. git grep for the old wording hits only .describe() text, content/docs/references/ and changesets, all outside this card.

Changeset

.changeset/23853-form-help-texts-plain.md is a patch for @objectstack/spec and @objectstack/platform-objects, with Clause-②: no. No key, type, schema or accept/refuse result changes.

Verification (head 01167a02)

  • Ablation, one leg, through scripts/ablation-replace.mjs. "(ADR-0090 D12)" was put back into the zh-CN adminScope leaf. The zh-CN case went red, naming zh-CN.metadataForms.permission.fields.adminScope.helpText (an ADR number, a decision id). The restore left the blob equal to HEAD (48bc7f4d7c85) and git diff HEAD empty.
  • pnpm --filter @objectstack/platform-objects test: 71 files, 1108 tests passed. typecheck: exit 0.
  • pnpm --filter @objectstack/spec build, then check:generated: exit 0, all 14 generated artifacts up to date.
  • pnpm check:i18n: exit 0, 10 packages in sync. The first run exited 3 because the extract closure was not built (a prerequisite, not a finding). It passed after the closure the gate names was built.
  • pnpm check:i18n-stale-fill: exit 0, 0 stale fills.
  • dispatch-gates --commands derived 86 families. 84 exited 0. 2 exited 3 on prerequisites and are NOT MEASURED locally, left to CI:
    • check-plugin-teardown-shape --self-test: shallow clone.
    • check:dual-build-cjs-loads: not every package is built.
  • --ran reconciliation: 86 accounted for, 0 UNRUN.
  • main was merged at 0af559e8 through scripts/pm/os-regen-merge.sh. The incoming commits touch none of these files and no generated artifact, so there was nothing to regenerate.

Acceptance notes


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec, touching 4 documentable anchor(s).

23 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 6422da58e83ac5032030c7daa4787fddf5eeb165.

⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see

Coarse fallback — 142 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6422da58e83ac5032030c7daa4787fddf5eeb165 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0a1737cb7579fc176a3c542043d222693e95e9a5 — the merge of head 01167a020ef92499308d6954e07fb129615ba931 into base 6422da58e83ac5032030c7daa4787fddf5eeb165, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0a1737cb7579fc176a3c542043d222693e95e9a5 && git checkout 0a1737cb7579fc176a3c542043d222693e95e9a5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6422da58e83ac5032030c7daa4787fddf5eeb165 01167a020ef92499308d6954e07fb129615ba931 && git checkout -B drift-repro 6422da58e83ac5032030c7daa4787fddf5eeb165 && git merge --no-ff 01167a020ef92499308d6954e07fb129615ba931

node scripts/docs-audit/affected-docs.mjs --json 6422da58e83ac5032030c7daa4787fddf5eeb165

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 6422da58e83ac5032030c7daa4787fddf5eeb165 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

objectstack-fleet Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · PR #23870 @ 01167a020e (#23853) · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T10:34Z

Reviewer of record. Every item below was checked against GitHub and the code, not against the report (6213387948 on #23853).

  • Shape:
  • The six leaves, each sentence read against the code on main:
    • isDefault:
      • "everyone who signs in": the everyone anchor is held by every authenticated member (resolve-authz-context.ts:1459; delegated-admin-gate.ts:309), and unauthenticated principals hold guest.
      • App-declared sets are bound at startup and package sets become a sys_audience_binding_suggestion an admin confirms (suggested-audience-bindings.ts).
      • "never granted this way": the anchor refuses high-privilege sets (builtin-positions.ts:87, validate-security-posture.ts:940).
    • managedBy: package rows are re-seeded on upgrade; platform and user rows are environment config that package seeding never touches (PermissionSetSchema.managedBy's TSDoc). The help text says the same.
    • packageId: "a value naming any other package is refused" is judgePermissionSetOwnerBinding's rule as PermissionSetSchema.packageId's TSDoc states it.
    • adminScope, delegatable and the view form's end_user_controls description only drop the parenthetical. The rest of each is unchanged.
    • The ADR ids moved to the comment beside each row. No .describe(), key, type or accept set moved.
  • Translations:
    • en is regenerated by i18n:extract.
    • The zh-CN values were read against the new English. They say the same thing, with no added or dropped clause.
    • The *.source-hashes.generated.ts tables are rightly unchanged. They hold entries only for byte-copied leaves, and these six are translated.
  • Pin: setup-copy-vocabulary.test.ts gains a bright leg over the six leaves in all four locales and a dark leg over the replaced prose. The report's ablation re-added one parenthetical and named the zh-CN leaf red.
  • The report's open question, the seat's call: A, as done. Rewrite the three jargon leaves and only drop the reference from the three that were already operational.
  • Out-of-scope findings: the .describe() texts that still open with [ADR-…] are author-facing schema docs, not Setup copy. They stay noted, not filed, as the report says.
  • CI on this head: 26 success and 6 skipped by their filters when read. Test Core 1-6/6 and Type Check · workspace were still running. The seat lands on green. No contract review is owed: the spec-side diff is help strings and comments only. Corrected in place: that is wrong. Non-test packages/spec/src/** is the Clause-② gate's path limb, which owes a CONTRACT_REVIEW_TIER PASS before the queue (6214471983).

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Taken out of the merge queue · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T11:26Z

  • Why: the seat's ACCEPT (6213589388) said "No contract review is owed: the spec-side diff is help strings and comments only." That is wrong. The diff touches non-test packages/spec/src/** (position.form.ts, permission.form.ts, view.form.ts). That is the Clause-② gate's path limb, which needs a CONTRACT_REVIEW_TIER PASS on record before the queue, and it has no prose exemption.
  • What this act did: pr_draft, which took the PR out of the merge queue it entered at 10:57:26Z. Nothing on the branch changed.
  • Next: an isolated contract review runs on 01167a020e now. On PASS, the seat re-runs the pre-landing checks and queues it again. On FAIL, a patch round.

Generated by Claude Code

Merged via the queue into main with commit d0fe050 Oct 16, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23853-form-help-texts-plain branch October 16, 2026 11:33
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 01167a020ef92499308d6954e07fb129615ba931
Local-runs: none
Scope: packages/spec/src/identity/position.form.ts,packages/spec/src/security/permission.form.ts,packages/spec/src/ui/view.form.ts + packages/spec/src/security/permission.zod.ts, packages/spec/liveness/permission.json, packages/spec/liveness/position.json, packages/spec/src/security/permission-set-owner-binding.ts, packages/spec/src/stack.zod.ts, packages/metadata-protocol/src/protocol.ts, packages/objectql/src/engine.ts, packages/plugins/plugin-security/src/builtin-positions.ts, packages/plugins/plugin-security/src/security-plugin.ts, packages/plugins/plugin-security/src/suggested-audience-bindings.ts, packages/plugins/plugin-security/src/declared-capability-context.ts, packages/plugins/plugin-security/src/packaged-permission-set-lock.ts, packages/plugins/plugin-security/src/delegated-admin-gate.ts, packages/spec/src/ui/view.zod.ts, packages/cli/src/utils/i18n-extract.ts, packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts, packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts, packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts, packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts, packages/platform-objects/src/apps/translations/zh-CN.source-hashes.generated.ts, packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts, .changeset/23853-form-help-texts-plain.md, packages/spec/package.json, packages/spec/tsup.config.ts, packages/spec/src/security/index.ts, packages/spec/src/identity/index.ts, packages/spec/src/ui/index.ts

Inputs: card #23853 (body and every comment, the parent #23829 and its addendum 6211169527 as the card cites them), PR #23870 (body, file list, net diff against main from merge-base 0af559e806), and the check-runs on the head. Read-only: the diff was read with git show and git grep against the fetched sha; nothing was built, run or re-run.

① Derived judgments

Public surface, right: the three *.form.ts hunks change string values of helpText / description and comments only. No key, type, control, option value or .describe() moves; no schema file is touched, so no accept or refuse result moves. The option labels on managedBy are unchanged.

Check-runs on the head, right: every gate-carrying context is success — Lint & Repo Gates, TypeScript Type Check, Test Core (and its six shards), Build Core, Governed Surface Queue Guard (both runs), Check Changeset, Spec property liveness, Dogfood Regression Gate. Nothing is red; the skips are the full-battery jobs the PR run does not carry.

isDefault, right: the new sentence matches the code that honours the key. security-plugin.ts (constructor) resolves the fallback set from the first isDefault set and composes it into the baseline; builtin-positions.ts declareEveryoneBaseline declares it on the everyone anchor with the fail-closed high-privilege check; suggested-audience-bindings.ts turns a package-owned isDefault set into a pending sys_audience_binding_suggestion row that a manage_metadata holder confirms, withholds it on a forbidden bit, and the mutation listener re-derives everyone when a set stops qualifying. Ledger row: live. The zh-CN, ja-JP and es-ES values carry the same four clauses.

packageId, right: judgePermissionSetOwnerBinding (permission-set-owner-binding.ts) refuses a body value that disagrees with the binding, and any value on a set no package ships; it is called from the stack schema refinement (stack.zod.ts), ObjectQL.registerApp (engine.ts) and the metadata save, restore and publish doors (protocol.ts). "Leave empty for a set created in this environment" states the last table row. Ledger row: live. The three translations agree.

managedBy, wrong: the new sentence promises behaviour from the field's value that no code derives from it. packages/spec/liveness/permission.json carries managedBy as dead (verified 2026-10-11): "no code reads the AUTHORED key"; the one consumer the schema docblock still names, bootstrapDeclaredPermissions, stamped a column and never read ps.managedBy, and it is retired under ADR-0131 D3. packaged-permission-set-lock.ts decides what a package upgrade may overwrite from the artifact, and its header says the managed_by column is "measurably the WRONG fact, in both directions". At this head the only reader of the authored key outside the schema is permission-set-clone.ts, which strips it from a copy. So "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" is a causal claim on a value the runtime never consults: a set created in this environment with managedBy: package is not updated by any upgrade, and a package-shipped set cannot be moved out of upgrade ownership by choosing Platform or User. The PR body's verification line for this field ("package rows are re-seeded on upgrade") cites the retired seeder, so the claim "each sentence checked against the code that honours the field" does not hold for this one. The three translations repeat the same promise. This is the declared-but-not-enforced copy Prime Directive #10 forbids, written into published spec source and served in four locales.

adminScope, right: only the parenthetical is gone; the surviving prose is the #23488 wording, matching delegated-admin-gate.ts (BU-subtree boundary, includeSubtree, manageAssignments, assignablePermissionSets). Ledger row: live. Translations: the same drop.

delegatable, right: only the parenthetical is gone; delegated-admin-gate.ts requires delegatable: true, a mandatory future valid_until and a mandatory reason before a non-administrator may delegate, which is "time-boxed and with a reason" and "delegation is admin-only" at the default. Ledger row: live. Translations: the same drop.

View end_user_controls description, right: only the parenthetical is gone; userFilters.element admits tabs and appearance.allowedVisualizations is live in view.zod.ts, so "quick filters, filter tabs, visualization switching" stays accurate. Translations: the same drop.

Generated bundles, right: i18n-extract.ts emits metadataForms.TYPE.fields.PATH.helpText and metadataForms.TYPE.sections.NAME.description from the form definitions, so en is the regenerated source and the six leaves move exactly as the three form files do. The three locale files change values only, on the same six leaves, no key added or dropped. The *.source-hashes.generated.ts tables hold objects.* paths only (zero metadataForms entries at the head), so leaving them unchanged is right and check:i18n-stale-fill has nothing to report.

Test, right: setup-copy-vocabulary.test.ts adds a bright leg over the six leaves in all four bundles that also pins the population (each leaf must be a string) and a dark leg over the replaced prose; no assertion is deleted. No other pin of the old wording exists at the head outside the diff (grep over packages, apps, examples, scripts).

The rest of the record above stands; the managedBy judgment is the one that fails the diff.

② Semver level

@objectstack/spec ships dist and src/**/*.zod.ts; the *.form.ts sources are not shipped as source, but permissionForm, positionForm and viewForm are exported from src/security/index.ts, src/identity/index.ts and src/ui/index.ts, each a tsup entry, so the rewritten text reaches dist/security, dist/identity, dist/ui and the root bundle. String values only, no export added, removed or retyped: patch is the right level. @objectstack/platform-objects ships the four regenerated bundles: patch is right. .changeset/23853-form-help-texts-plain.md declares both, with Clause-②: no; no accept set widens or narrows, so no is right and no ADR-0087 marker is owed. Check Changeset on the head: success.

Clause-②: no

③ Boundary flags

Dev deviation 1 (WIP commits without the trailer pair): answered. The PR is squash-landed and its body carries the session attribution; landed history is not rewritten.

Dev deviation 2 (check-plugin-teardown-shape --self-test and check:dual-build-cjs-loads not measured locally): answered by the head's check-runs. Lint & Repo Gates and Lint · scoped · tooling self-tests are success.

Dev deviation 3 (test:repo not run locally): answered by Test Core, success on the head.

Open question (rewrite depth, A or B): A is the right split in principle — do not widen past the ADR drop where the surviving prose was already verified — but A as executed overshot on one leaf. The fix for managedBy is narrower still: state the provenance meaning and drop the upgrade promise, or drop only the parenthetical as adminScope did. A sentence that holds against the ledger: "Who owns this set: Package for a set that ships with a package; Platform or User for a set created in this environment." Apply it in permission.form.ts, re-extract en, rewrite the zh-CN, ja-JP and es-ES leaves to the narrowed meaning, and correct the PR body's managedBy verification line. Everything else in the diff stands as reviewed.

Out-of-scope finding 1 (.describe() texts carry ADR ids): agreed, author-facing docs, outside the card. Out-of-scope finding 2 (managedBy option labels unchanged): agreed they are outside the card's surface; note that "Package (upgrade-owned metadata)" carries the same promise on the same dead key.

Escalated to the domain:spec seat, not filed here: the ledger row for PermissionSetSchema.managedBy says "Retirement of the authored key is an enforce-or-remove item for the spec seat". A Setup control for a dead key is the false-compliant UI half the view.form.ts comment names for the retired tabs repeater. That is a retirement under the spec-property-retirement playbook, beyond this card.

Implemented-by: claude/issue-23853-form-help-texts-plain
Reviewed-by: session_01HjR8RUwTPQ3TgbfKihmxp9

VERDICT: FAIL

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

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

2 participants