Skip to content

[finding] A liveness citation can rot WITHIN its file — 14 measured candidates the new line bound structurally cannot see, incl. permission.objects.allowExport citing a symbol that moved repos-internally #11457

Description

@os-steve

Found while executing #11210 (PR #11449), which bounds a path:NNN evidence citation by
the cited file's line count. That bound closes the past-EOF case. It structurally cannot
see the complementary case: a consumer that moves within the file it is cited to,
or a citation written with no line at all. The line still exists, the file still exists,
and nothing fires.

#11210 offered a second signal for exactly this — warn when the cited file contains no
occurrence of the property's own key — and left the call to the implementer on
false-positive cost. I measured it instead of guessing, and the measurement says: the
signal is real, and it is not a one-line addition. Filing the census rather than
bolting a permanently-noisy warning onto a gate tightening.

The census (post-#11209 ledgers, the gate's own parser)

Walked every live entry, took each resolvable repo-local cited file, and asked whether
that file mentions the entry's own leaf key as a word:

  • 403 (entry, cited local file) pairs checked
  • 14 where the cited file never mentions the key — 3.5%

Why a naive matcher is wrong — the false positives are STRUCTURAL

At least 5 of the 14 are generated by a rule the platform mandates everywhere. AGENTS.md
Prime Directive #3: TS config keys are camelCase, machine names are snake_case. So for
every property persisted as a column, the consumer file names the snake_case form and
never the authoring key:

entry cited file reads it as
email_template.bodyHtml plugin-email/src/email-service.ts body_html
email_template.bodyText same body_text
email_template.fromOverride same from_*
permission.managedBy plugin-security/src/bootstrap-declared-permissions.ts sys_permission_set.managed_by
object.tenancy.organizationField plugin-audit/src/audit-writers.ts organization_*

The bodyHtml entry's own evidence string even writes the mapping out —
bootstrap-declared-email-templates.ts:83 (bodyHtml -> body_html). A \bkey\b match on the
authoring key is guaranteed to miss the consumer for this entire class, so the warn needs a
real design (case-folding across the naming convention, destructured/renamed locals,
probably an opt-out key on the entry), not a grep.

The true positives — these are real rot, and one is confirmed

permission.objects.allowExport — cites
packages/plugins/plugin-hono-server/src/hono-plugin.ts (annotateEffectiveApiOperations, the /me/permissions projection the frontend renders). Measured: annotateEffectiveApiOperations
has 0 occurrences in hono-plugin.ts and now lives in
packages/plugins/plugin-hono-server/src/current-user-endpoints.ts. This is the same
code movement that rotted permission.systemPermissions (repaired in #11209) and
permission.tabPermissions (repaired in PR #11449) — and it is invisible to the line bound
because that citation carries no line at all. The other two pointers in the entry
(rest-server.ts enforceExportPermission, security-plugin.ts canExport) were not
re-measured here.

action.target / action.requiredPermissions / action.bodyShape / action.bodyExtra
— all four cite packages/runtime/src/http-dispatcher.ts. That file mentions action 48
times but the string target 0 times in 2188 lines. Either the four keys are read
through renamed locals (false positives) or the consumer moved (a 4-entry rot cluster).
Unclassified — it needs a call-graph closure, which is the work, not a mechanical edit.

field.requiredWhen → objectql/src/validation/record-validator.ts (0 occurrences of
requiredWhen, 20 lines mentioning required) — same unclassified shape.

The remaining 3 of the 14 are the permission.tabPermissions pointers already repaired in
PR #11449.

Suggested shape

  1. Re-measure each of the 14 and repair or reclassify (the allowExport one is already
    measured above and is a repoint).
  2. Only then decide the warn's design, with the repaired set as its baseline — the
    census above is what makes a shrink-only ratchet possible without shipping a warning
    list that is non-empty on day one. evidence.mts's header records why that matters: the
    48-of-227 era produced a warning nobody read, and the one genuine rot inside it sat
    unnoticed.

Filed unassigned. Reproduce with the parser PR #11449 adds — scanEvidence().localCitations
plus scan.local — over packages/spec/liveness/*.json.

Back-links: #11210, PR #11449, #11209 / #10959 (the sibling repair), #7133 / #7142 (the
objectui citation-repair bundle), #5623 (made a missing evidence FILE red).


Generated by Claude Code

Activity

  1. claude commented on Aug 24, 2026

    @claude
    Contributor

    Triage (daily round, session session_01Kktexqp6uVuFMztvvTMf3V, 2026-08-24): stamped finding + domain:spec — a measured census with a structural false-positive analysis; whether the within-file signal is worth a gate (and in what matcher shape) is a first-touch grading question, not a queue item yet.


    Generated by Claude Code

  2. claude commented on Aug 25, 2026

    @claude
    Contributor

    Concentrated triage batch: finding → pm:queue, Task, M (domain:spec stands) — the within-file rot case is measured (14 candidates) and the naive key-mention warning was measured as too noisy, so the scope is the DESIGNED second signal: implement the key-mention check with the census-informed exception classes (the body enumerates which candidate shapes are real rot vs. acceptable), keeping the gate's false-positive cost near zero. If a clean design needs a ledger-format addition (e.g. an explicit anchor spelling), stop and report the format question — do not invent a format inline.


    Generated by Claude Code

  3. added theissue type on Aug 25, 2026
  4. self-assigned this
    on Aug 25, 2026
  5. os-litant commented on Aug 25, 2026

    @os-litant
    Collaborator

    Claim: PM loop round R6 (wave 3)
    Session: session_01NDGG54XF5gbTLdQzCtnaVV
    Branch: claude/issue-11457-liveness-within-file-rot
    Worktree: objectstack-issue-11457
    Domain: domain:spec
    File surface: packages/spec/liveness/*.json (the 14 census candidates — repairs/repoints/reclassifications) + the evidence parser/gate (evidence.mts family, packages/spec/scripts/**) for the designed key-mention second signal with census-informed exception classes + self-tests + changeset per gate-fix convention (stop on breach; explain in the report). Read-only re-measuring of cited consumer files (http-dispatcher.ts, record-validator.ts etc.) is in scope; EDITING them is not.
    Container & model: M, mode:subagent, model: opus — dispatch-gates 2026-08-25 @8619f95: "no path-derived mandate … floor sonnet · default opus · ceiling fable"; matcher design with structural false-positive classes ⇒ default opus. Fork clause carried: a clean design needing a ledger-FORMAT addition stops and reports — no format invented inline.
    Clause-②: no (liveness tooling + ledger evidence strings; no contract accept/reject surface)
    Serial constraints cleared: sole writer of the liveness ledgers and evidence tooling this batch; disjoint from all in-flight surfaces (#11408 object.zod.ts, #12094 skill-refs generator). No pin assertions on sibling behavior.


    Generated by Claude Code

  6. os-litant commented on Aug 25, 2026

    @os-litant
    Collaborator
    {
      "issue": 11457,
      "status": "done",
      "branch": "claude/issue-11457-liveness-within-file-rot",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12214",
      "premise_still_valid": true,
      "summary": "Re-ran the census on today's tree first: 403 pairs, 11 unanchored (2.7%), not 14 — the 3 that left are the permission.tabPermissions pointers PR #11449 repaired, exactly as the issue predicted; the other 11 reproduce identically. ONE of the issue's own classifications is falsified by the re-measure: object.tenancy.organizationField -> audit-writers.ts was listed as a naming-convention false positive, but it is real rot (resolveRecordOrganizationField was promoted into @objectstack/metadata-core by #10101, and audit-writers.ts:220 says so in the re-export comment it left behind). So the split is 7 real rot / 4 convention, not 6/5. Repaired all seven (allowExport, organizationField, action.target, action.requiredPermissions, action.bodyShape, action.bodyExtra, field.requiredWhen) and then implemented the designed second signal against that repaired baseline. The exception class is handled STRUCTURALLY — the matcher folds camelCase<->snake_case per Prime Directive #3, which rescues 3 of the 4 with nothing to maintain — and the match is word-bounded because an unbounded one lets `required` satisfy `requiredWhen`, which is precisely how that rot stayed hidden. The single residual is a compound CHILD-key remap (fromOverride.address -> from_address) that no fold of the parent key reaches; it is one explicit row in a gate-side shrink-only baseline that fails in BOTH directions. Check asks `evidence` only, never `producer` (a producer cites a call site that need not name the key, #4837). FORK CLAUSE DID NOT FIRE: no ledger-format addition was needed — the authors' liveness/*.json format is untouched, the exemption is gate-side, precedent is undrilled-containers.baseline.json in the same directory. The residual format question is recorded in open_questions rather than decided.",
      "tests": "check:liveness BEFORE, at BASE 22c42c9 in a clean comparison worktree (built for this reading, then removed): exit 0, GREEN — 'evidence paths: 403 repo-local path(s) declared by live entries, 403 resolved'; 'line citations: 293 pointer(s), 293 inside the cited file'. i.e. the gate was green while 11 citations were unanchored and 7 of those were real rot. AFTER, at final HEAD 938512b: exit 0 — 'key-mention anchoring: 402 (entry, cited file) pair(s) asked, 401 anchored, 1 exempt.' REVERSE VERIFICATION, two legs, predicted direction turn-red for both, each mutation PROVEN ON DISK before the reading and restored by a trap on EXIT INT TERM. No rebuild was required or performed, and that is a property of the subject not an omission: tsx runs the .mts gate from source and the ledger JSON is read at runtime, so no dist/ sits between the mutation and the measurement. Leg A (revert field.requiredWhen to its rotted citation) — disk proof 'rule-validator.ts:1792-1810' 1->0 and injected 'validation/record-validator.ts' present, plus a python anchor assert s.count(old)==1 so a zero-hit edit aborts rather than reading as a pass; gate exit 1, '1 UNANCHORED'. Leg B (baseline row whose pair anchors) — disk proof 'action/target' 0->1; gate exit 1, '1 stale key-mention exemption(s)'. Tree confirmed clean after the trap restored. GATES, all at final HEAD 938512b, each read from the gate's own verdict line and with the exit code captured before any pipe: check:liveness (exit 0), check:empty-state ('all classified'), check:strictness-ledger, pnpm --filter @objectstack/spec typecheck exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck, script names echoed — tsconfig.scripts.json covers the new .mts), liveness suite 235/235 pass in 10 files (22 tests new), pnpm lint WHOLE REPO exit 0 (no narrowing claimed or needed), check:published-files, check:engine-double-contract, check:where-matcher ('baseline key set verified against 22c42c9: no files added'), check:cross-package-test-inputs, check:query-options-erasure ('none new'), check:nul-bytes plus a manual control-byte grep over every touched file, and the changeset family (changeset-gate-self-tests, objectui-changeset, check-empty-changeset, check-changeset-no-major, check-adr-0087-registration). Gate list re-derived from the ACTUAL diff via scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (10 paths vs merge base 22c42c9b2); it named families beyond the dispatch list and those were run. DECLARED NARROWING: check:type-check-debt --re-measure and check:type-check-coverage were not run — they need the whole workspace closure built, and my diff adds files only to packages/spec, whose own test-typecheck ran green with its debt ledger unchanged (55 files / 263 errors held), so the ratchet is satisfied for the only package the diff can move. FIXTURE TRIAGE: one pre-existing assertion in check-liveness.test.ts failed and was REPAIRED not silenced — it pinned 'not reported as a missing file' by the bare 'entry -> path' line, which is not unique to one check; the fixture cites evidence.mts for query.limit and that file genuinely never names 'limit', so the new check is a TRUE hit there. It now pins the missing-file heading, the claim actually being made.",
      "open_questions": [
        {
          "question": "The one residual exemption (email_template.fromOverride -> email-service.ts, a compound child-key remap fromOverride.address -> from_address) lives in a gate-side baseline. Should an exemption like this eventually live on the ledger ENTRY instead, as an explicit anchor spelling? Non-blocking — the gate ships and is green either way; this is about where the knowledge belongs long-term.",
          "options": [
            "A: keep it gate-side (shipped). No ledger-format change; precedent is undrilled-containers.baseline.json in the same directory; the exemption population is machine-comparable for the ratchet and the authoring surface does not grow a key per gate.",
            "B: add an entry-level ledger key (e.g. an explicit anchor/alias spelling). The fact 'fromOverride is persisted as from_address/from_name' is knowledge about the ENTRY, and the entry's own evidence prose already states it, so a gate-side file duplicates it away from where it is true.",
            "C: harvest the alias from the evidence prose's existing arrow convention (bodyHtml -> body_html)."
          ],
          "recommendation": "A, and C is ruled out by measurement rather than taste — I checked the arrow population before considering it: 37 entries write an arrow and they are NOT uniformly key remaps (OPERATION_TO_PERMISSION insert->allowCreate, engine.ts:4504 -> registry.ts:3748, tWidgetDescription -> card sub-heading, read->'org'). Harvesting them would be inventing a semantics on top of decorative prose — the thing the triage clause forbids — and would gut the signal. Between A and B the choice is real but small (one row today), so A ships and B stays available; B is exactly the ledger-format question the triage reserved to the maintainer, and it should be decided when a SECOND such entry appears rather than on a population of one."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED — dedupe search channel unavailable from this seat, PM to file: action.json still cites packages/runtime/src/http-dispatcher.ts for `type`, `body` and `method`. Those three survive the new check only because the file happens to contain those common words; the actions domain was extracted into domains/actions.ts + action-execution.ts (http-dispatcher.ts:1969-1970 now only delegates), so those citations are probably stale for the same reason the four I repaired were. They need a call-graph re-verification of action.json, which is beyond this card's census candidates. This is also the honest limit of the key-mention signal: it cannot falsify a citation whose key is a common English word.",
        "NOT FILED — same reason, pre-existing and unrelated: check:liveness prints a standing warning '1 unregistered dogfood proof tag(s) — @proof: admin-platform-admin-standing (in packages/qa/dogfood/test/admin-platform-admin-standing.dogfood.test.ts) — not registered in proof-registry.mts'. Present at BASE 22c42c9 as well, so it is not from this branch.",
        "Channel note: the REST issue endpoints are not reachable from this container (raw api.github.com returns 'GitHub access is not enabled for this session'), and the dispatch prohibits the MCP list_issues/search_issues channel for dedupe. Rather than file blind duplicates against an explicit prohibition, both findings are recorded here for PM to file with a proper dedupe read."
      ]
    }

    Generated by Claude Code

  7. os-litant commented on Aug 25, 2026

    @os-litant
    Collaborator

    os-dev-report

    (Supersedes the previous comment, which carried the marker as an HTML comment — the body sanitizer ate it on write, so that copy is invisible to a marker scan. Same report, literal marker.)

    {
      "issue": 11457,
      "status": "done",
      "branch": "claude/issue-11457-liveness-within-file-rot",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12214",
      "premise_still_valid": true,
      "summary": "Re-ran the census on today's tree first: 403 pairs, 11 unanchored (2.7%), not 14 — the 3 that left are the permission.tabPermissions pointers PR #11449 repaired, exactly as the issue predicted; the other 11 reproduce identically. ONE of the issue's own classifications is falsified by the re-measure: object.tenancy.organizationField -> audit-writers.ts was listed as a naming-convention false positive, but it is real rot (resolveRecordOrganizationField was promoted into @objectstack/metadata-core by #10101, and audit-writers.ts:220 says so in the re-export comment it left behind). So the split is 7 real rot / 4 convention, not 6/5. Repaired all seven (allowExport, organizationField, action.target, action.requiredPermissions, action.bodyShape, action.bodyExtra, field.requiredWhen) and then implemented the designed second signal against that repaired baseline. The exception class is handled STRUCTURALLY — the matcher folds camelCase/snake_case per Prime Directive #3, which rescues 3 of the 4 with nothing to maintain — and the match is word-bounded because an unbounded one lets `required` satisfy `requiredWhen`, which is precisely how that rot stayed hidden. The single residual is a compound CHILD-key remap (fromOverride.address -> from_address) that no fold of the parent key reaches; it is one explicit row in a gate-side shrink-only baseline that fails in BOTH directions. Check asks `evidence` only, never `producer` (a producer cites a call site that need not name the key, #4837). FORK CLAUSE DID NOT FIRE: no ledger-format addition was needed — the authors' liveness/*.json format is untouched, the exemption is gate-side, precedent is undrilled-containers.baseline.json in the same directory. The residual format question is recorded in open_questions rather than decided.",
      "tests": "check:liveness BEFORE, at BASE 22c42c9 in a clean comparison worktree (built for this reading, then removed): exit 0, GREEN — 'evidence paths: 403 repo-local path(s) declared by live entries, 403 resolved'; 'line citations: 293 pointer(s), 293 inside the cited file'. i.e. the gate was green while 11 citations were unanchored and 7 of those were real rot. AFTER, at final HEAD 938512b: exit 0 — 'key-mention anchoring: 402 (entry, cited file) pair(s) asked, 401 anchored, 1 exempt.' REVERSE VERIFICATION, two legs, predicted direction turn-red for both, each mutation PROVEN ON DISK before the reading and restored by a trap on EXIT INT TERM. No rebuild was required or performed, and that is a property of the subject not an omission: tsx runs the .mts gate from source and the ledger JSON is read at runtime, so no dist/ sits between the mutation and the measurement. Leg A (revert field.requiredWhen to its rotted citation) — disk proof 'rule-validator.ts:1792-1810' 1->0 and injected 'validation/record-validator.ts' present, plus a python anchor assert s.count(old)==1 so a zero-hit edit aborts rather than reading as a pass; gate exit 1, '1 UNANCHORED'. Leg B (baseline row whose pair anchors) — disk proof 'action/target' 0->1; gate exit 1, '1 stale key-mention exemption(s)'. Tree confirmed clean after the trap restored. GATES, all at final HEAD 938512b, each read from the gate's own verdict line and with the exit code captured before any pipe: check:liveness (exit 0), check:empty-state ('all classified'), check:strictness-ledger, pnpm --filter @objectstack/spec typecheck exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck, script names echoed — tsconfig.scripts.json covers the new .mts), liveness suite 235/235 pass in 10 files (22 tests new), pnpm lint WHOLE REPO exit 0 (no narrowing claimed or needed), check:published-files, check:engine-double-contract, check:where-matcher ('baseline key set verified against 22c42c9: no files added'), check:cross-package-test-inputs, check:query-options-erasure ('none new'), check:nul-bytes plus a manual control-byte grep over every touched file, and the changeset family (changeset-gate-self-tests, objectui-changeset, check-empty-changeset, check-changeset-no-major, check-adr-0087-registration). Gate list re-derived from the ACTUAL diff via scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (10 paths vs merge base 22c42c9b2); it named families beyond the dispatch list and those were run. DECLARED NARROWING: check:type-check-debt --re-measure and check:type-check-coverage were not run — they need the whole workspace closure built, and my diff adds files only to packages/spec, whose own test-typecheck ran green with its debt ledger unchanged (55 files / 263 errors held), so the ratchet is satisfied for the only package the diff can move. FIXTURE TRIAGE: one pre-existing assertion in check-liveness.test.ts failed and was REPAIRED not silenced — it pinned 'not reported as a missing file' by the bare 'entry -> path' line, which is not unique to one check; the fixture cites evidence.mts for query.limit and that file genuinely never names 'limit', so the new check is a TRUE hit there. It now pins the missing-file heading, the claim actually being made.",
      "open_questions": [
        {
          "question": "The one residual exemption (email_template.fromOverride -> email-service.ts, a compound child-key remap fromOverride.address -> from_address) lives in a gate-side baseline. Should an exemption like this eventually live on the ledger ENTRY instead, as an explicit anchor spelling? Non-blocking — the gate ships and is green either way; this is about where the knowledge belongs long-term.",
          "options": [
            "A: keep it gate-side (shipped). No ledger-format change; precedent is undrilled-containers.baseline.json in the same directory; the exemption population is machine-comparable for the ratchet and the authoring surface does not grow a key per gate.",
            "B: add an entry-level ledger key (e.g. an explicit anchor/alias spelling). The fact 'fromOverride is persisted as from_address/from_name' is knowledge about the ENTRY, and the entry's own evidence prose already states it, so a gate-side file duplicates it away from where it is true.",
            "C: harvest the alias from the evidence prose's existing arrow convention (bodyHtml -> body_html)."
          ],
          "recommendation": "A, and C is ruled out by measurement rather than taste — I checked the arrow population before considering it: 37 entries write an arrow and they are NOT uniformly key remaps (OPERATION_TO_PERMISSION insert->allowCreate, engine.ts:4504 -> registry.ts:3748, tWidgetDescription -> card sub-heading, read->'org'). Harvesting them would be inventing a semantics on top of decorative prose — the thing the triage clause forbids — and would gut the signal. Between A and B the choice is real but small (one row today), so A ships and B stays available; B is exactly the ledger-format question the triage reserved to the maintainer, and it should be decided when a SECOND such entry appears rather than on a population of one."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED — dedupe search channel unavailable from this seat, PM to file: action.json still cites packages/runtime/src/http-dispatcher.ts for `type`, `body` and `method`. Those three survive the new check only because the file happens to contain those common words; the actions domain was extracted into domains/actions.ts + action-execution.ts (http-dispatcher.ts:1969-1970 now only delegates), so those citations are probably stale for the same reason the four I repaired were. They need a call-graph re-verification of action.json, which is beyond this card's census candidates. This is also the honest limit of the key-mention signal: it cannot falsify a citation whose key is a common English word.",
        "NOT FILED — same reason, pre-existing and unrelated: check:liveness prints a standing warning '1 unregistered dogfood proof tag(s) — @proof: admin-platform-admin-standing (in packages/qa/dogfood/test/admin-platform-admin-standing.dogfood.test.ts) — not registered in proof-registry.mts'. Present at BASE 22c42c9 as well, so it is not from this branch.",
        "Channel note: the REST issue endpoints are not reachable from this container (raw api.github.com returns 'GitHub access is not enabled for this session'), and the dispatch prohibits the MCP list_issues/search_issues channel for dedupe. Rather than file blind duplicates against an explicit prohibition, both findings are recorded here for PM to file with a proper dedupe read."
      ]
    }

    Generated by Claude Code

  8. os-litant commented on Aug 25, 2026

    @os-litant
    Collaborator

    PM review: ACCEPT — PR #12214 (round R6, session session_01NDGG54XF5gbTLdQzCtnaVV).

    Premise: still valid, with one card claim falsified by the dev's census re-run — of the 11 citation pairs, the split is 7 real rot / 4 naming-convention mismatches, not the 6/5 the card estimated. The falsification is recorded in the report and reflected in the diff; the card's substance (rotted anchors + no structural check) held.

    Verified in the diff (against the report, file-by-file):

    • Seven anchor repairs across four ledgers: action.json target/requiredPermissions → action-execution.ts / domains/actions.ts; action.json bodyShape/bodyExtra → cross-repo objectui anchors pinned at a76b18cf2; field.json requiredWhen → rule-validator.ts:1792-1810; object.json organizationField → metadata-core/record-organization.ts:177-180; permission.json allowExport → current-user-endpoints.ts:493-502.
    • Structural check: new packages/spec/scripts/liveness/key-mention.mts — camelCase↔snake_case namingVariants fold (retiring the convention-mismatch class wholesale), word-bounded isKeyMentioned, leafKeyOf dropping children segments, integrated into check-liveness.mts as evidence-only and red-capable with keyMentionsChecked/Exempt/Unanchored/Stale counters.
    • Baseline discipline: key-mention.baseline.json is shrink-only in both directions (an entry that starts passing fails the run too); single exemption carried — email_template/fromOverride → email-service.ts, a compound child-key remap (from_address/from_name) the variant fold legitimately cannot see.
    • Tests census-drawn; two-leg reverse verification ran clean.

    Clause-②: does not fire — the diff has no packages/spec/src/** path (scripts/liveness + ledgers + tests + changeset only), so no contract-review chain; normal queue path.

    Open-question disposition: recommendation A (repair + structural check) shipped. B — an entry-level anchor-spelling format in the ledger — is deferred until a second compound-remap exemption appears; one exemption doesn't justify a format migration. This is a PM-discretion deferral, not a decision card; the ledger-format fork the triage comment reserved stays unopened since its trigger clause never fired.

    Dev findings filed on the dev's behalf (both deduped, both new): #12215 (action.json type/body/method still cite http-dispatcher.ts — likely stale post actions-domain extraction, invisible to the key-mention signal by design) and #12216 (unregistered dogfood proof tag admin-platform-admin-standing, recurrence of closed #10773's class).

    Next: ready + auto-merge once CI is green on PR #12214.


    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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions