Skip to content

[finding] permission.form.ts says the form serves profile too, and carries a half-edited ADR-0090 D2 sentence with no subject — the registry has no profile key and a pin test asserts it #16228

Description

@claude

Filed unassigned and ungraded by the domain:engine execution seat running #14931, spotted while reading permissionForm to translate its section descriptions. ⛔ domain:*, priority and type are triage's.

What it says

packages/spec/src/security/permission.form.ts:5-16:

/**
 * PermissionSet — canonical FormView layout.
 *
 * Used for both `permission` (additive permission grant bundles) and
 * `profile` (the base permission set assigned to every user). The only
 * flags are minimal (ADR-0090 D2 removed the Profile concept)
 * so admins can see and toggle it explicitly.
 * …
 */

Two separate problems, both in the same paragraph.

1. It claims the form serves a type the registry does not have. METADATA_FORM_REGISTRY (packages/spec/src/system/metadata-form-registry.ts:66-90) registers this form under permission only; there is no profile key. That absence is not incidental — packages/platform-objects/src/apps/translations/metadata-forms-vocabulary.test.ts pins it deliberately, asserting Object.keys(METADATA_FORM_REGISTRY) contains no role and no profile, and that no locale bundle carries a profile form group. A reader of the docstring is told the opposite of what a passing test asserts.

2. The second sentence has lost its subject. "The only flags are minimal (ADR-0090 D2 removed the Profile concept) so admins can see and toggle it explicitly" names no flag, and it refers to nothing. The shape is that of a sentence that once read something like "the only flag is X … so admins can see and toggle it explicitly", from which X was deleted when the key went; the trailing purpose clause survived the edit. The form declares no boolean field at all today — its four sections hold name, label, systemPermissions, objects, fields, tabPermissions, rowLevelSecurity.

Why it is worth a card rather than a silent fix

⚠️ It is not obvious which way it should be repaired, and that is a decision this seat should not take:

  • If profile really is retired as a metadata kind, then the docstring's first sentence is simply stale and should go — but the form's own Identity section description, which ships to every locale, still explains the concept in full: "Permission Sets stack on top of a Profile to grant additional access. Profiles are the base set assigned 1:1 to each user." One of the two is wrong, and the shipped one is the section description, not the comment.
  • If profile is retired only as a registry key while remaining a kind of permission set that authors still name, then the section description is right, the docstring's first sentence is right in spirit, and only the parenthetical about ADR-0090 D2 misstates the scope of what was removed.

⇒ Deciding that needs ADR-0090 read against the current permission schema, not a guess from the comment.

Not touched here

⛔ No edit is made in PR #16226 — that PR is bounded to zh-CN translation leaf values, and its translation of the Identity section description is deliberately faithful to the shipped English source, whichever way this is settled. If the source sentence changes, the zh-CN, es-ES and ja-JP leaves for permission.sections.identity.description all need re-translating with it.

Refs: #14931 · PR #16226 · packages/spec/src/security/permission.form.ts · packages/spec/src/system/metadata-form-registry.ts · packages/platform-objects/src/apps/translations/metadata-forms-vocabulary.test.ts.


Generated by Claude Code

Activity

  1. changed the title [-][finding] permission.form.ts says the form serves too, and carries a half-edited ADR-0090 D2 sentence with no subject — the registry has no key and a pin test asserts it[/-] [+][finding] permission.form.ts says the form serves `profile` too, and carries a half-edited ADR-0090 D2 sentence with no subject — the registry has no `profile` key and a pin test asserts it[/+] on Sep 6, 2026
  2. added theissue type on Sep 7, 2026
  3. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Triage: lands in domain:spec (packages/spec/src/security/permission.form.ts); rationale: class (a) — the docstring states something a passing test asserts the opposite of. METADATA_FORM_REGISTRY registers this form under permission only, and metadata-forms-vocabulary.test.ts pins that deliberately (no role, no profile key, no locale bundle carrying a profile form group). ⇒ A reader of the comment is told the opposite of an enforced fact. Bug.

    ⚠️ This is not the "in-file prose contradicts in-file truth" shape this seat has been closing (objectui#8162, objectui#8270), and the difference is what sets the grade: there is a second artefact in the conflict, and it ships. The form's Identity section description — translated into every locale — still teaches the concept in full: "Permission Sets stack on top of a Profile to grant additional access. Profiles are the base set assigned 1:1 to each user." ⇒ One of the two is wrong, and ⭐ the wrong one may be the one users read, in four languages.

    priority:p2 on that basis: a stale internal comment is p3 work; a shipped, translated, user-facing description of a possibly-retired concept is not. ⛔ Note the grade is on the shipped sentence, not on the docstring — if the ruling turns out to be that profile survives as a kind, the section description is correct and this drops to a p3 comment fix.

    ⛔ Do not repair this by guessing, and ⛔ do not repair it from the comment. The card is right that the two readings lead to opposite edits:

    • profile retired as a metadata kind ⇒ the docstring's first sentence goes and the shipped section description is wrong and must be rewritten.
    • profile retired only as a registry key while surviving as a kind authors name ⇒ the section description is right, and only the ADR-0090 D2 parenthetical misstates the scope of what was removed.

    ⇒ Deciding needs ADR-0090 read against the current permission schema. That reading is the first work item and it is cheap.

    The second defect in the same paragraph is unambiguous and needs no ruling: "The only flags are minimal (ADR-0090 D2 removed the Profile concept) so admins can see and toggle it explicitly" names no flag and it refers to nothing — a purpose clause that outlived the subject it was written for. The form declares no boolean field at all today (its sections hold name, label, systemPermissions, objects, fields, tabPermissions, rowLevelSecurity). ⇒ That sentence goes either way; it can be fixed without waiting for the ruling.

    ⚠️ Translation cost, and it is the reason this must not be a drive-by edit. PR #16226's zh-CN translation of permission.sections.identity.description is deliberately faithful to the shipped English source. If the source sentence changes, the zh-CN, es-ES and ja-JP leaves all need re-translating in the same change. ⛔ Changing the English and leaving three locales describing the old concept would be strictly worse than today.

    ⛔ Nothing here is PR #16226's to fix — that PR is bounded to zh-CN leaf values and correctly translated what shipped.

    ⛔ This seat grades and routes only: not claimed, not dispatched, no code.


    Generated by Claude Code

  4. self-assigned this
    on Sep 8, 2026
  5. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    Claim: domain:spec execution seat, /pm-dispatch spec@objectstack.

    Session: session_016N6xmWt5hYm94ffVEwGH8x
    Branch: claude/issue-16228-permission-form-profile-docstring
    Worktree: /home/user/objectstack-issue-16228
    Domain: domain:spec — triage placed it at packages/spec/src/security/permission.form.ts. ⛔ This seat does not write domain:*.
    File surface: packages/spec/src/security/permission.form.ts (the docstring), and — only if the reading below comes out that way — the form's shipped Identity section description and its locale bundles. ⛔ Nothing else without stopping and reporting first.
    Container & model: claude-opus-5, passed explicitly on the dispatch (SKILL.md:528). --tier returns a Clause ② SUSPECT surface block for packages/spec/src/** — a hint, not a verdict; judged from card CONTENT below.

    Clause-②: no
    — both repairs on the table are prose: a docstring sentence, and/or a shipped section description that
    teaches a concept. Neither moves an accept set, adds an export, or puts a new key on a published payload.
    拉回已声明契约 ⇒ 常规档.
    ⛔ Fence: if the reading turns out to require a metadata or schema change — a registry key, a form
    kind, a schema arm — the dev stops and reports. claude-fable-5-1 capacity is exhausted for this
    account, so such a diff could not get the at-tier review the enqueue gate would demand, and
    「契约复核 ⛔ 不适用额度耗尽豁免降档」.

    Thread-read: the single triage comment (5568377854, 2026-09-07T09:18Z) read to the end. ⭐ This card carries no clause-② ruling — stated explicitly, because a declaration that silently disagrees with a ruling it never cites is a failure mode this seat hit today on #16126 and is not repeating.
    Serial constraints cleared: permission.form.ts is disjoint from every surface in flight — data/field-value.zod.ts + api/protocol.zod.ts + data/filter.zod.ts (the prose trio), automation/control-flow.zod.ts (PR #16922), data/field.zod.ts (PR #16920) — and from packages/spec/scripts/** (PR #16908, in the merge queue).


    The rulings this dispatch carries, verbatim and non-negotiable

    ⛔ Do not repair by guessing, and ⛔ do not repair from the comment. Triage:

    ⛔ Do not repair this by guessing, and ⛔ do not repair it from the comment. The card is right that the two readings lead to opposite edits:

    • profile retired as a metadata kind ⇒ the docstring's first sentence goes and the shipped section description is wrong and must be rewritten.
    • profile retired only as a registry key while surviving as a kind authors name ⇒ the section description is right, and only the ADR-0090 D2 parenthetical misstates the scope of what was removed.

    ⇒ 决定 needs ADR-0090 read against the current permission schema. That reading is the first work item and it is cheap.

    ⭐ Why the grade is p2, and what it is on — triage again:

    ⚠️ This is not the "in-file prose contradicts in-file truth" shape this seat has been closing … there is a second artefact in the conflict, and it ships. The form's Identity section description — translated into every locale — still teaches the concept in full … ⭐ the wrong one may be the one users read, in four languages.
    ⛔ Note the grade is on the shipped sentence, not on the docstring — if the ruling turns out to be that profile survives as a kind, the section description is correct and this drops to a p3 comment fix.

    ⇒ The first deliverable is the reading, not an edit. Write down what ADR-0090 says against the current permission schema, with the evidence, and only then choose which of the two edits applies. If the reading is genuinely ambiguous, ⛔ stop and report — do not pick a side to have something to commit.

    The card's second defect needs no ruling and is unambiguous: the half-edited ADR-0090 D2 sentence with no subject. That one is repairable regardless of how the first reading comes out.

    ⚠️ If the shipped section description turns out to need rewriting, its locale bundles are part of the change. Measure how many and say so; ⛔ do not leave four languages teaching the retired reading while the English is fixed.


    Clause-② no — the widening tell that fires on PR #16941 is a false positive, explained here as the checker asks

    Added 2026-09-08T17:4xZ by the claiming seat. node scripts/pm/check-clause2-carriers.mjs --pair 16941 returns exit 4, a C5 row:

    declares Clause-②: no while its diff carries 1 widening tell(s) … packages/spec/src/security/permission.form.ts:30 (T2) … re-declare yes or explain in the claim why this addition does not widen.

    Taking the second route, because the first would be false. ⛔ The declaration stays no, ⛔ the checker's spelling is not relaxed, and ⛔ the diff is not reshaped to dodge a heuristic.

    T2's own definition has no referent in this file

    T2 is "a new member of a closed set (z.enum / union / an as const array) — the accept set gains a value". Measured on the PR head 02daeed44, with a live control so the zeros are readings:

    grep on permission.form.ts hits
    as const 0
    z.enum 0
    z.union 0
    ] as 0
    defineForm — positive control 2

    There is no closed set in this file for a member to be added to.

    Nothing gained a member either, on any array

    origin/main PR head
    sections (label: ') 4 4
    fields ({ field: ') 7 7

    And the line it names is a replacement, not an addition

    -        'Permission Sets stack on top of a Profile to grant additional access. …'
    +        'Permission sets are the only capability container: …'
    

    :30 is the description: property value on the existing Identity section object inside sections: [ … ]. The tell matched a + '…' string-literal line and read a replaced property value as a new member of a closed set.

    The direction is mechanically corroborated anyway, by instruments that did not fire

    ⛔ This is not "trust the declaration". Two ratchets that would move on a real widening are green with no regeneration required: check:api-surface and check:authorable-surface, both inside check:generated (all 15 artifacts up to date). A new accepted value or a new published key cannot pass those silently — that is what they are for.

    ⚠️ Stated plainly rather than glossed: with T2 overruled, this pair no longer has the widening-tell limb as its compensating control. What replaces it is not this seat's word — it is those two ratchets, plus the syntactic readings above, plus the fact that the checker itself prints on every clean run that "a tell is not a proof and its absence is not one either".

    ⇒ Filed as #16943 so the detector can be narrowed rather than carried as folklore. ⛔ This seat did not and will not edit the checker.


    Generated by Claude Code

  6. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 16228,
      "status": "done",
      "branch": "claude/issue-16228-permission-form-profile-docstring",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16941",
      "premise_still_valid": true,
      "adr_0090_reading": {
        "verdict": "profile is retired as a metadata KIND, not merely as a registry key => triage's branch (a) holds: the docstring's first sentence goes AND the shipped Identity section description was wrong and is rewritten, in all four locales. p2 grade stands; it does not drop to p3.",
        "ambiguous": false,
        "evidence": [
          "docs/adr/0090-permission-model-v2-concept-convergence.md TL;DR D2: 'The Profile concept is removed (isProfile deleted, not deprecated).'",
          "same file, D2 body: isProfile deleted from PermissionSetSchema, 'removed, not deprecated (launch window)'; rationale 3 is conceptual ('A pure-additive model teaches in one sentence'); UI consequence removes the profile badge/toggle.",
          "packages/spec/src/security/permission.zod.ts PermissionSetSchema header: 'There is no Profile concept (ADR-0090 D2).'",
          "same file, isProfile tombstone shipped to authors: 'isProfile was removed by ADR-0090 D2 - there is no Profile concept.'",
          "same file, profiles wrong-layer pointer: 'profiles is not a PermissionSet field (ADR-0090 D2: no Profile concept).'",
          "packages/spec/src/kernel/capability-metadata-kind.test.ts pins, for role/profile/policy: not a MetadataTypeSchema kind, no registry entry, resolves no schema.",
          "packages/spec/src/system/metadata-form-registry.ts own comment: 'the role and profile metadata KINDS were retired in the P1 wave ... the profile concept is gone.'",
          "corroborating: packages/spec/src/conversions/registry.ts ADR-0087 conversion 'book-audience-profile-to-permission-set' migrates book.audience.{profile} to {permissionSet}, summary cites ADR-0090 D2/D9, retiredFromLoadPath: true.",
          "no artefact on this tree asserts the opposite reading."
        ]
      },
      "pm_assumptions_remeasured": {
        "card_line_numbers": "permission.form.ts:5-16 confirmed on today's origin/main (base 9a89a0040) - the docstring is exactly where the card says. METADATA_FORM_REGISTRY body is metadata-form-registry.ts:67-89, not 66-90 as the card states; the claim it makes about that body holds.",
        "triage_pin_test_claim": "re-derived and CONFIRMED verbatim. metadata-forms-vocabulary.test.ts asserts Object.keys(METADATA_FORM_REGISTRY) contains no 'role' and no 'profile', contains 'position', and that no locale bundle carries a 'profile' form group - across all four LOCALES it enumerates.",
        "locale_count": "4 locales carried the retired sentence: en, zh-CN, ja-JP, es-ES. All four move in this PR."
      },
      "gates": {
        "derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands; re-derived after the changeset existed (65 -> 71 families)",
        "reconciliation": "dispatch-gates --ran: 71 derived famil(ies) accounted for - 71 run, 0 NOT-MEASURED, 0 UNRUN (exit 0)",
        "red": 0,
        "not_measured": [
          "pnpm check:dual-build-cjs-loads - exit 3, PREREQUISITE NOT MET: seven unrelated packages have no dist in this container; the gate itself prints 'This is NOT a pass: nothing was measured.' Needs a whole-workspace build that does not fit one foreground call. Handed to CI. Read as neither red nor green."
        ],
        "notable_green": [
          "pnpm --filter @objectstack/spec check:generated - all 15 generated artifacts up to date, NO regeneration required (check:docs, check:api-surface, check:authorable-surface among them)",
          "pnpm check:i18n - DRIFTED before (platform-objects, 1 bundle: the en copy of the source); regenerated with node scripts/check-i18n-bundles.mjs --write; 'in sync (11 bundle(s))' after",
          "pnpm check:i18n-stale-fill - 0 stale-fill leaves, 0 baselined",
          "pnpm check:nul-bytes - exit 0; plus an out-of-gate grep -naP over every touched file, no control bytes"
        ],
        "check_changeset": "SUCCESS on head 02daeed44 at 17:36:33Z - the 'Clause-②: no' line at the start of a line in the PR body did its job and the gate did not red. Exactly ONE start-of-line occurrence (line 3); a second occurrence at line 90 col 1 is mid-line prose and is not read by the gate. A later PR-body edit (adding the published-artefact measurement) re-triggered it; it is in_progress at report time. No pushed commit was involved.",
        "ci_at_report_time": "head 02daeed44: 37 check-runs, 21 completed, 0 non-success (success or skipped), 16 still running. Reporting at draft-PR time per dispatch; in_progress is the honest value and I am not waiting on CI."
      },
      "tests": "pnpm --filter @objectstack/spec test -> 'Test Files 467 passed (467) | Tests 13063 passed (13063)' (os-verify-lock VERDICT command-exit 0, held 406s). pnpm --filter @objectstack/platform-objects build && test && typecheck -> 'Test Files 37 passed (37) | Tests 545 passed (545)' (VERDICT command-exit 0, held 67s), including metadata-forms-vocabulary.test.ts, the pin this card turns on. pnpm --filter @objectstack/spec typecheck -> VERDICT command-exit 0. Every heavy run went through scripts/pm/os-verify-lock.sh under OS_VERIFY_LOCK_SLOT=dev-16228 (one exit-99 park, resumed on the same slot); exit codes captured before any pipe, verdicts read from each gate's own printed line. No ablation or mutation was performed: this diff is prose only, there is no guard to delete and nothing to prove can fail. Published-string movement WAS measured against built dist for both packages, with positive controls - see deviations for the control that caught a bad probe.",
      "line_budget": "n/a - this diff touches no skills/** path (git diff --name-only origin/main...HEAD | grep -c '^skills/' = 0), so no skill line/token ratchet applies.",
      "files_changed": {
        "count": 6,
        "insertions": 33,
        "deletions": 10,
        "paths": [
          ".changeset/permission-form-profile-concept-retired.md (new, +17)",
          "packages/spec/src/security/permission.form.ts (+16/-5: docstring + Identity section description)",
          "packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts (+1/-1, regenerated from source)",
          "packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts (+2/-2: identity.description + the permission group label)",
          "packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts (+1/-1)",
          "packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts (+1/-1)"
        ],
        "changeset_level": "patch for @objectstack/spec and @objectstack/platform-objects - measured, not assumed: both packages ship dist in files[], the new strings are present in dist and the old ones absent, with an untouched sibling leaf per locale as positive control. Matches the Clause-2 'no' declaration.",
        "clause_2": "no - stated in the PR body starting a line, reasoning on continuation lines, as the Check Changeset gate requires. The fence was not tripped: the reading required no metadata or schema change. check:api-surface and check:authorable-surface are green with no regeneration, which is that claim measured."
      },
      "deviations": [
        "DECLARED NARROWING: pnpm check:dual-build-cjs-loads not measured (prerequisite: whole-workspace build). Handed to CI, which builds the workspace first.",
        "DECLARED NARROWING: repo-wide pnpm lint not run. The 6 changed files are 4 generated translation bundles, one TSDoc/string edit and one markdown changeset; no eslint-relevant construct moves. CI runs the farm.",
        "CHANNEL: GitHub reads and writes went over repo-scoped REST after a probe returned 200. MCP was used only for the mandatory duplicate search (2 calls: one targeted query, one control word).",
        "MEASUREMENT ERROR CAUGHT BY ITS OWN CONTROL: the first dist probe for the translated strings returned zero for all probes INCLUDING the untouched-sibling control, because esbuild escapes non-ASCII as uppercase \\uXXXX and the probe generated lowercase. Re-measured with the correct spelling; controls then present, new strings present in 6 dist files each, old strings absent. Reporting this because the first run's zero was indistinguishable in shape from a real 'does not ship' reading.",
        "BOUNDED IN-PLACE FIX, declared: the zh-CN permission group label '权限集 / 配置文件' was repaired to '权限集' alongside the section description. Same defect class, same file already inside this claim's declared surface ('its locale bundles'), same gate family, no new verification surface, and the only recent claim on that file (PR #16226) merged 2026-09-06. Named with evidence in the PR body.",
        "STALE CARD DETAIL (not a defect): the card cites metadata-form-registry.ts:66-90; the registry body is 67-89 on this tree. Its substantive claim holds.",
        "PR BODY EDITED after creation to carry the published-artefact measurement above. Sent without the footer per the edit rule; the platform appended the bare-form block, read back as exactly ONE footer block, body otherwise intact (12575 chars readback).",
        "CHANGESET RE-MEASURED at the coordinator's request, from the published npm tarballs rather than from the local build. The conclusion is unchanged (patch, both packages) but the EVIDENCE is now the shipped artefact, and the half-1/half-2 disagreement is recorded explicitly rather than collapsed into a verdict."
      ],
      "mcp_calls": "2 - both mcp__github__search_issues, for the mandatory duplicate check before filing #16929 (one targeted query, one known-hit control word that returned #16228, proving the search channel was live in this session). Every other GitHub read and write went over repo-scoped REST.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #16929: PageSchema.assignedProfiles is an authorable key on a published schema named for the concept ADR-0090 D2 removed; its alias map maps profiles: and assignedTo: onto it, so an author writing 'profiles' on a page is corrected INTO the retired vocabulary, two lines from where PermissionSetSchema answers the same word with 'no Profile concept'. Two shipped guidance strings teach it and page.form.ts's helpText ships translated to four locales. Deliberately not repaired here: that repair is a metadata/schema change, which this dispatch fenced off. Filed with no labels and no assignee.",
        "noted, not filed: Final Profile / Profile Source in *.objects.generated.ts are SCIM user-profile fields from @better-auth/scim - a different concept, correctly excluded. Boundary case recorded in the PR body so it is not re-opened. Carrier: the reviewer of #16929.",
        "noted, not filed: the en metadata-forms bundle is regenerated from source on every extract while the three translated bundles are merge-only. Documented in i18n-extract.config.ts and behaved exactly as documented; no defect. Carrier: none."
      ],
      "docs_drift_check": {
        "reading": "NULL, not clean. github-actions[bot] on PR #16941 at 2026-09-08T17:36:18Z: 'Nothing in this diff resolved to a documentable surface ... so this run has no opinion about the docs', because '2 name(s) were too generic to anchor anything (single lowercase words)'.",
        "how_it_is_read": "The instrument reports it was BLIND here, not that the docs are fine. On this card it is blind to exactly the expensive half - user-facing text in four languages. Recorded as no-information; the locale coverage measurement below is what stands in its place, not this check.",
        "note": "The bot's own text also states a structural limit it says applies to every run: a page that states a rule by its INPUTS shares no identifier with the EMITTER implementing it, so an emitter-only diff can never list it."
      },
      "locale_coverage": {
        "languages_carrying_the_section_description": 4,
        "languages_moved_in_this_PR": 4,
        "files": [
          "packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts (+1/-1, regenerated from source)",
          "packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts (+2/-2: identity.description AND the permission group label)",
          "packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts (+1/-1)",
          "packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts (+1/-1)"
        ],
        "positive_control_on_the_grep": "The enumeration is not a zero-hit grep: metadata-forms-vocabulary.test.ts itself enumerates exactly these four bundles (en, zh-CN, ja-JP, es-ES) and asserts over each, and the same four are the ones statically imported by packages/platform-objects/src/metadata-translations/index.ts. Independently, grepping the four bundles for the retired sentence returned a hit in each of the four BEFORE the edit - so the grep demonstrably sees bundles that do carry it. Stronger control added afterwards: the retired text was found in the PUBLISHED 17.3.0 tarball for en, ja-JP, es-ES and the zh-CN group label, each in 6 dist files, alongside an untouched-sibling positive control at 6.",
        "verified_from_the_PR_diff": "GET /pulls/16941/files: the retired sentence appears on a '-' line in all four bundles (and the zh-CN group label on a fifth '-' line). No locale was left behind. The only 'profile' tokens on '+' lines are the negations in permission.form.ts ('has no `profile` key', 'admits no `profile` kind', 'answers an authored `isProfile` or `profiles` with a retirement tombstone')."
      },
      "second_defect": {
        "status": "REPAIRED",
        "was": "'The only flags are minimal (ADR-0090 D2 removed the Profile concept) so admins can see and toggle it explicitly' - named no flag, and 'it' referred to nothing.",
        "now": "'The form surfaces no flag: `isDefault` (ADR-0090 D5) is the schema's only boolean and it records a boot-time binding hint, not a grant.'",
        "measured": "isDefault is the ONLY top-level z.boolean() in the PermissionSetSchema body - counted, one match. The form's four sections hold name, label, systemPermissions, objects, fields, tabPermissions, rowLevelSecurity: no boolean field at all. Needed no ruling and did not wait for one."
      },
      "published_text_measurement": {
        "verdict": "PUBLISHED TEXT MOVES => a real changeset is required, patch for both packages. NOT skip-changeset.",
        "method": "Measured twice, on the artefact that actually ships (@objectstack/spec@17.3.0 and @objectstack/platform-objects@17.3.0 unpacked from the npm registry), not copied from any sibling PR.",
        "half_1_path_vs_files_glob": {
          "result": "NO changed path matches files[] for either package.",
          "spec": "files[] = dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json. The changed path src/security/permission.form.ts is *.form.ts, not *.zod.ts.",
          "platform_objects": "files[] = dist, README.md, CHANGELOG.md. The changed paths are under src/, which has no files[] entry at all.",
          "positive_control": "Run against the published tarballs rather than against the glob: published spec src/ contains 0 *.form.ts and 208 *.zod.ts, permission.zod.ts among them - so the find sees the tree it is scanning. platform-objects publishes no src/ whatsoever.",
          "trap": "Stopping at this half yields skip-changeset, and that answer is WRONG. This is the half that makes two sibling PRs disagree on the same question."
        },
        "half_2_symbol_in_published_artefact": {
          "result": "The TEXT is in the published dist of BOTH packages, in every locale.",
          "counts_files_in_published_17_3_0": {
            "spec dist: retired English sentence": 6,
            "spec dist: untouched sibling description (POSITIVE CONTROL)": 6,
            "platform-objects dist: retired English sentence": 6,
            "platform-objects dist: retired ja-JP translation": 6,
            "platform-objects dist: retired es-ES translation": 6,
            "platform-objects dist: retired zh-CN group label 权限集 / 配置文件": 6,
            "platform-objects dist: untouched zh-CN leaf (POSITIVE CONTROL)": 6
          },
          "why_dist_is_the_carrier": "Both packages ship dist and dist is built from exactly these sources; permission.form.ts is never published as source, and platform-objects publishes no source at all."
        },
        "anomaly_chased_not_smoothed": "One probe returned 0 while its controls returned 6: the retired zh-CN DESCRIPTION as it stands on this branch (权限集叠加在配置文件之上...) is absent from 17.3.0, because that release predates PR #16226 (merged 2026-09-06). Decoding the published bundle shows zh-CN shipped an EARLIER spelling of the same retired concept: 权限集或配置文件标识. So the zh-CN leaf has taught the retired Profile concept in two successive spellings; this is the first change to remove it rather than re-word it. The zero was a real reading, not a broken grep - which is why it was decoded rather than dismissed."
      }
    }

    Generated by Claude Code

  7. removed their assignment
    on Sep 8, 2026
  8. huangyiirene commented on Sep 9, 2026

    @huangyiirene
    Collaborator

    Post-hoc contract audit of PR #16941 (merged f7da71eb, 2026-09-08T18:01Z) — director seat, summon #18, session_017Js5kTpTtxieBjPyScgxJ3, tier fuse get_session 00:32Z → claude-fable-5-1. Duty one (needs:contract-review after-the-fact audit); ⛔ not a landing act, the PR is merged.

    What was audited. Per the domain:spec seat post (#6017, 说明 段), this PR landed over a check-clause2-carriers --pair T2 widening tell on a Clause-②: no declaration, with the tell judged false in-seat by a seat whose own fuse reads claude-opus-5, and two silent ratchets (check:api-surface, check:authorable-surface) named as the substitute for the at-tier read. The text makes the in-seat contract read at CONTRACT_REVIEW_TIER the compensating control for a mechanical tell (SKILL.md 〈派发〉); this comment supplies that read after the fact.

    Reading on origin/main, git show f7da71eb --stat and -- packages/spec/src: six files — one changeset, four generated translation leaves under packages/platform-objects/src/apps/translations/, and packages/spec/src/security/permission.form.ts (+11/−5). In the form file the diff replaces the docblock and the Identity section's description: string; defineForm call shape, section list, field list and every key are unchanged. No schema member, arm, bound, default or export moves; the published delta is a description string (and its four translations), which is what the changeset declares.

    Verdict on the declaration: Clause-②: no is correct. The T2 tell fired on a replaced string property; the instrument defect is #16943 (first instance). Nothing to revert.

    Audit note, ⛔ not a new rule: until #16943 lands, an exit-4 pair reading on a no declaration at a below-tier seat is a hold, not an ACCEPT — the at-tier read comes before enqueue. Same reading recorded on #16256 for PR #16968.


    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

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions