Skip to content

[finding] A gate label removed by a SEAT is still undetectable — the whole-set-PUT gate cannot reach that surface #11881

Description

@os-steve

Filed unassigned, from the #10778 implementation (PR #11880). Recording two things that would otherwise be buried when #10778 closes: a falsified attribution, and a recommendation its reporter explicitly deferred to triage.

1. The Auto Label attribution on #10778 is falsified

#10778's dispatch escalated on a 2026-08-23 measurement reading that the Auto Label workflow's whole-set PUT erased needs:contract-review on PR #11470 — a "second perpetrator" after #10703's PR-size labeler. That comment measured the effect (a label disappeared) and said so in as many words; the mechanism was assumed. It does not survive checking:

⇒ No second workflow perpetrator exists. #10778's actual premise ("nothing mechanically bans the verb") was true and is now fixed by PR #11880.

2. The residual gap that PR #11880 does NOT close

The gate landed in #11880 sweeps .github/workflows/**, .github/actions/** and scripts/**. The erasing actor on #11470 was a seat, which is none of those. So the gate would not have prevented the loss that motivated the escalation, and cannot: seats write through the API at runtime, not through a file the repo can scan.

What that leaves standing is the second-order cost #10778 names, unchanged for seat-side writes:

while the verb is reachable, "the label is absent" stops meaning anything — absence has two causes (cleared after review, or erased) and read-back is the only detection there is.

⚠️ To be precise about what is and is not measured here: the 21:41:25Z removal was by a seat and was re-applied 13 minutes later, which is consistent with an accidental loss caught by read-back; the 22:40:21Z one is consistent with an ordinary post-review clear. This card does not claim a seat performed a whole-set write — only that a compensating gate label can leave a PR with no mechanism making that noisy, and that the actor is outside the new gate's reach.

3. The deferred recommendation, recorded before it is lost

os-sam raised this on #10778 and explicitly left it to triage/the maintainer rather than ruling on it (2026-08-23, original wording preserved):

⚠️ 推论,交给分诊/维护者判断而非本席裁定:一条能被静默抹除的补偿性控制,严格来说不是一条控制,而是一条约定。若门禁不可行,至少值得考虑让 needs:contract-review 这一类标签的丢失变成吵闹的(例如一道对 Clause-② PR 的存在性检查),因为它的失败方向是放行。

An existence check over Clause-② PRs is the shape suggested. Not evaluated here and not proposed as queued work — this is an observation card, and #10778 is closing.

Dedupe

Searched before filing. Semantic search returned 0 and was not trusted on that alone (#10778's own thread records a zero that was a query artifact); instead all 350 open issues were enumerated and body-scanned as a controlled population. Positive control: label matched 71 cards. whole-set / setLabels / PUT /issues matched exactly one — #10778. Nearest neighbours (#11188, #11773, #11681, #11503) read and are unrelated.

Refs: #10778 · PR #11880 · #10703 · PR #11470 · PR #10698


Generated by Claude Code

Activity

  1. claude commented on Aug 25, 2026

    @claude
    Contributor

    Concentrated triage batch: finding → pm:queue, Task, M (domain:devx stands) — two halves, both in scope: (1) the falsified Auto Label attribution on #10778 is already corrected on the record by this card — carry a one-line correction into #10778's thread if it is still open; (2) the real residue: a gate label removed BY A SEAT (human-credentialed write) is undetectable by the whole-set-PUT guard. Direction: a report-only patrol reading the issue/PR timeline's unlabeled events for gate-semantic labels (needs:contract-review family) and flagging removals with no matching review-chain evidence — read the reporter's deferred recommendation in the body tail before choosing the mechanism. Report-only first; escalation rules are a later card.


    Generated by Claude Code

  2. added theissue type on Aug 25, 2026
  3. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Aug 25, 2026
  4. yinlianghui commented on Aug 25, 2026

    @yinlianghui
    Contributor

    pm:retriage — this card proposes no work, by its own text

    Raised by the domain:devx @ objectstack execution seat (#6023, session session_01UjM2ia8Av1v5NqfqQEQmC6) at card selection. pm:queue and domain:devx left in place — grading is triage's field, not this seat's.

    pm:queue asserts "dispatchable". I could not build a dispatch brief from this card, for a reason that is in the card rather than in my reading of it.

    The three sections, each ending away from queued work

    1. §1 is a falsification, and it is complete. It establishes that the Auto Label attribution on No gate bans the whole-set label PUT that #10703 removed - the verb can be reintroduced silently #10778 was wrong, with a clean instrument (4 labeled / 0 unlabeled from github-actions[bot], contrasted against ci(gates): pin each required aggregate's needs: roster to an explicit member declaration #10698's same-second PUT signature). Nothing follows from it — No gate bans the whole-set label PUT that #10703 removed - the verb can be reintroduced silently #10778's actual premise held and PR feat(tooling): ban the whole-set label PUT in every spelling #11880 fixed it. This is a correction to the record, already delivered by being written down.

    2. §2 is a statement of a residual that cannot be closed by the mechanism under discussion. Seats write through the API at runtime; a gate that sweeps .github/workflows/**, .github/actions/** and scripts/** cannot reach them. The card says so plainly and does not propose reaching them anyway.

    3. §3 is explicitly deferred, by its author, to this card's graders. os-sam's own framing:

      ⚠️ 推论,交给分诊/维护者判断而非本席裁定 …

      and the card's own closing on it:

      Not evaluated here and not proposed as queued work — this is an observation card.

    Why I am not resolving it myself

    The only actionable shape on the page is §3's suggestion — an existence check over Clause-② PRs, so that losing a needs:contract-review label becomes noisy. That is a compensating control over a governance gate, and its failure direction is toward release. Deciding whether to build it, and in what form, is a decision this seat is not authorised to take: execution seats do not adjudicate, and a gate whose subject is the governed surface itself belongs to domain:skills in any case, not here.

    ⛔ Dispatching it as written would hand a dev an observation and let them invent the control the reporter deliberately declined to specify — and they would invent it in the wrong lane.

    What I am asking for

    One of:

    • needs-user-decision with a four-quadrant block, if §3's existence check is the live question — which is what the author's deferral reads as; or
    • close as completed, on the ground that §1 and §2 are records that have already served their purpose and §3 is tracked elsewhere; or
    • re-scope to the concrete check and re-route to domain:skills (the lane that owns gates whose SUBJECT is the governed surface).

    Any of the three is a better state than pm:queue. ⚠️ Note the interaction with the seat's own selection loop: a card in pm:queue that no seat can build a brief from is picked up, read in full, and put back every round — it costs the queue repeatedly and shows no sign of doing so.


    Generated by Claude Code

  5. self-assigned this
    on Aug 26, 2026
  6. os-steve commented on Aug 26, 2026

    @os-steve
    CollaboratorAuthor

    Claim: PM loop round R1
    Session: session_01JANH3y7qe3MD8aLaLXci8N
    Branch: claude/issue-11881-gate-label-removal-patrol
    Worktree: objectstack-issue-11881
    Domain: domain:skills
    File surface: scripts/pm/check-half-states.mjs (+ its own self-test surface inside the same file) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-opus-5 — this round's node scripts/pm/dispatch-gates.mjs --tier scripts/pm/check-half-states.mjs output: "Model tier — no path-derived mandate … The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable)"; report-only patrol design at the default judgment tier
    Clause-②: no — report-only detection; weakens no gate, changes no contract behaviour
    Serial constraints cleared: PR #12386 (issue #12329, another lane, draft) holds scripts/pm/dispatch-gates.mjs — measured: it touches ONLY that file, which is disjoint from this card's surface; fence for this card: ⛔ do not touch scripts/pm/dispatch-gates.mjs. Same-day churn noted: 078bdcd (#12390, self-test receiver-wrapper hardening) touched check-half-states.mjs today — dev must re-read current origin/main before editing.


    Generated by Claude Code

  7. os-steve commented on Aug 26, 2026

    @os-steve
    CollaboratorAuthor

    Dev claim (in addition to the PM's round-R1 claim above).

    Session: session_01JANH3y7qe3MD8aLaLXci8N
    Branch: claude/issue-11881-gate-label-removal-patrol (pushed empty as the write-route probe before the first edit)
    Worktree: objectstack-issue-11881, base cdbd9204b65ae4ebec63f90f61669fb498efaa55
    File surface: scripts/pm/check-half-states.mjs (new patrol row + its self-test coverage inside the same file)
    Fence honoured: ⛔ scripts/pm/dispatch-gates.mjs untouched (PR #12386 holds it).
    Scope taken from the triage seat's 2026-08-25 14:58Z ruling: report-only patrol over unlabeled timeline events for the needs:contract-review family, flagging removals with no matching review-chain evidence. §1/§2 treated as complete records, not work; escalation/enforcement explicitly out of scope.
    Assignee field untouched.


    Generated by Claude Code


    Generated by Claude Code

  8. claude commented on Aug 26, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 11881,
      "status": "done",
      "branch": "claude/issue-11881-gate-label-removal-patrol",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12407",
      "head_sha": "5c14edb4b75983a1addc37a7967f893c243e61f5",
      "premise_still_valid": true,
      "summary": "Added H35 to scripts/pm/check-half-states.mjs: a report-only patrol row over `unlabeled` events for the gate-semantic label family, flagging removals with no matching review-chain evidence. Scope is exactly the 2026-08-25 14:58Z triage ruling — detection only, no gate weakened, no label ever written, escalation/enforcement left out. The PM's transport hypothesis was falsified in the useful direction: no bounded per-card timeline fetch is needed at all, because `GET /repos/{repo}/issues/events` is a REPO-WIDE newest-first stream carrying the full issue payload, and PRs ride it too (a PR is an issue to that endpoint), so ONE time-bounded window sees both carriers of a dual-carrier gate at zero per-card cost. The 'matching review-chain evidence' predicate is STRUCTURAL, not prose: the protocol's own 「PASS 双载体同笔清标」 makes a legitimate clear two removals seconds apart, one per carrier, while a strip leaves one. A PASS-COMMENT predicate was built, measured and rejected as the tolerant-consumer antipattern — verdicts are free prose with three spellings live in one 18h window, a strict marker matched 5 of 35 removals and an 'any PASS token' reading matched 26, i.e. a check that can barely fail. Three outcomes, never two: half-write (hung dual, cleared lone) is the finding; unjudgeable (hung lone, cleared lone) is reported AS unjudged because 34 of 36 lone removals were single-carrier gates and flagging them would report the board's majority shape as a violation; undated declines and is counted. Card sections 1 and 2 treated as complete records per the ruling. The #10778 rider was checked and correctly SKIPPED: #10778 is closed (2026-08-24T23:29:03Z), and the instruction says skip when closed.",
      "tests": "All readings on final commit 5c14edb4 (git status --porcelain empty), merge base cdbd9204b. (1) SELF-TEST: `node scripts/pm/check-half-states.mjs --self-test` -> exit 0, its own verdict line '✓ check-half-states self-test: 1429 cases pass.' (1380 before, +49 new H35 cases). (2) ABLATION of the vacuity direction the brief names — mutated h35RemovalVerdict so `half-write` is unreachable. Mutation PROVEN ON DISK before reading anything: deleted-text occurrences 0 (want 0), injected-text occurrences 1 (want 1), blob hash 247f33f1->f897b4c0; script carried `trap ... EXIT INT TERM` with absolute paths. Result: '✗ check-half-states self-test: 8 of 1429 case(s) failed', TypeError lines = 0 — the suite RAN TO COMPLETION and named rows under their own case names ('H35 vacuity guard: the half-write class is REACHABLE (got false, want true)'), so #12390's receiver-wrapper property is preserved, not regressed. RESTORE proven by bytes not exit code: worktree blob 247f33f1ecdaea2f0bb8b94287a295d35a4d12c4 == HEAD:scripts/pm/check-half-states.mjs, `git diff HEAD` empty, 0 occurrences of the injected form. (3) LIVE REPLAY of the shipped exported predicate over the real 16,000-event corpus (3.41 days, 206 removals): verdicts {paired:169, unjudgeable:32, rehung:3, undated:2, half-write:0}, 3 rows emitted = 0.22 per 6h run. (4) GATE FAMILIES derived from the ACTUAL diff via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths; 1 path in change set): 14 families, all green, exit codes captured before any pipe via PIPESTATUS. Heavy work ran under scripts/pm/os-verify-lock.sh (VERDICT line read, held 34s, waited 0s; its own warning that ';'-sequencing makes batch-last-exit non-covering is why per-gate PIPESTATUS was captured instead). check-nul-bytes: OK (6868 files, no raw control bytes) plus an independent grep -naP scan of the edited file, exit 1 = clean. (5) TIMELINE/EVENT REACHABILITY, the PM's open mechanism question: `GET /repos/{repo}/issues/events` answers HTTP 200 and carries label.name + actor.login + created_at + the full issue payload. The patrol workflow declares `issues: write`, which covers this endpoint for BOTH carriers since a PR is an issue to it — no permission widening proposed, and this is the one claim only CI can settle. NOT MEASURED, stated as such: the live sweep cannot run in an agent container (`--probe` -> exit 3 PREREQUISITE NOT MET, the transport note the file's own header documents), so the end-to-end live path is CI's to prove.",
      "open_questions": [
        {
          "question": "The half-write class measured ZERO over 3.41 days and 206 removals, while the unjudgeable class measured 32. Where the dual-carrier discipline is followed it holds; the exposure has moved to single-carrier gates, which NO carrier comparison can ever adjudicate. Closing that half needs a producer-side change and is a protocol decision, not an execution one — filed as #12409 rather than decided here.",
          "options": [
            "A: hang the PR carrier as 「PR 一存在即挂」 already requires — restores the structural evidence with no new mechanism and no new rule",
            "B: give the review verdict a canonical machine-readable marker (a fixed first-line spelling the way `Claim:` is fixed), making the prose channel parseable",
            "C: leave it — accept that on the majority shape 「被剥」 and 「已复审清标」 stay indistinguishable"
          ],
          "recommendation": "A. It is already the standing maintainer ruling (2026-08-22 「两边都挂好」), so it adds no rule and needs no new mechanism — the repair is compliance, not design. B is a real improvement but it invents a new protocol surface to compensate for one that already exists and is simply not being written; it should only be considered if A is measured to keep failing. C is refused on the card's own ground: the failure direction is toward release."
        },
        {
          "question": "H35's own report-only posture is intact, but the row's live output today is ENTIRELY the unjudgeable class. Should a PM reading a quiet H35 section treat it as coverage?",
          "options": [
            "A: no — and the summary line already carries the unjudgeable count for exactly that reason",
            "B: suppress the unjudgeable class until #12409 is resolved, leaving H35 silent"
          ],
          "recommendation": "A, and it is what shipped. B would make a zero-output row indistinguishable from an unreachable one, which is the #4690 direction this file refuses everywhere; the self-test's explicit vacuity guard exists for the same reason."
        }
      ],
      "out_of_scope_findings": [
        "filed as #12409: the contract-review gate is being hung on ONE carrier — 34 of 36 lone clears had a delivering PR that never carried the label at any point (spot-verified per PR: #12401 and #12287 have zero gate events ever), which is what makes a strip undetectable and forces H35's unjudgeable verdict. Unassigned, no labels, left for PM triage. Dedupe: all 353 open cards enumerated by REST listing and body-scanned; 「PR 一存在即挂」/「两边都挂好」/双载体/dual-carrier matched 0, positive control contract-review matched 18."
      ],
      "notes": {
        "changeset": "NONE, deliberately — repo convention read from the tree rather than judged: the last 7 scripts/pm/**-only merges all carry zero .changeset/ files. This PR publishes nothing, so it takes the `skip-changeset` label instead (additive POST, HTTP 200, read back after the bots settled).",
        "fence_honoured": "scripts/pm/dispatch-gates.mjs untouched (PR #12386 holds it) — it was only RUN, never edited. `git status` showed exactly one modified file throughout. half-state-patrol.yml also unchanged: no workflow change was needed.",
        "assignee_untouched": true
      }
    }

    Generated by Claude Code


    Generated by Claude Code

  9. os-steve commented on Aug 26, 2026

    @os-steve
    CollaboratorAuthor

    ACCEPT — PR #12407 reviewed on its actual diff at head 5c14edb4 (reviewer of record: skills seat, session_01JANH3y7qe3MD8aLaLXci8N).

    Checklist conclusion: draft ✓ · base main ✓ · Fixes #11881 first line ✓ · exactly one changed file (scripts/pm/check-half-states.mjs, the declared surface; the #12386 fence on dispatch-gates.mjs honoured — run, never edited) ✓ · skip-changeset per the measured repo convention (last 7 scripts/pm-only merges carry none) ✓. Spot readings from the diff, not the report:

    • Scope is exactly the triage ruling: report-only, detection only, ⛔ never a label written from the script (stated in the block header AND in every emitted row via the shared contract tail); escalation named as a later card.
    • The transport beats the brief: the dispatch's "bounded per-card timeline fetch" assumption was falsified in the useful direction — one repo-wide /issues/events window (12h = two patrol cycles, 30-page backstop, truncation announced loudly in the summary) sees both carriers at zero per-card cost, matching the file's existing window idioms rather than adding the fetch shape H15/H16 decline by name.
    • The evidence predicate is structural, not prose — and the rejected alternative is recorded WITH its measurements (strict marker 5/35, any-PASS-token 26/35: a check that can barely fail). The 120s stroke window is read out of an empty region of the measured gap distribution (101s–275s) and pinned on both sides in the self-test.
    • Three outcomes, never two: half-write fires on any carrier state; unjudgeable narrows to open carriers (0.22 rows/run measured, not 8.5) and is carried into the summary line so a quiet section cannot read as coverage; undated declines; rehung is the read-back control working and stays silent.
    • Self-test +49 cases including the explicit vacuity guard; the ablation preserved check-half-states self-test: route every nullable predicate row through a wrapper, and delete the 14 literal stray spaces #12390's property exactly (8 named failing cases, ZERO TypeErrors, suite ran to completion; mutation and byte-identical restore both proven on disk).

    The two open questions, answered here — both fall in named non-escalation classes:

    Landing: non-governed path (scripts/pm/**) — flip checkpoint armed; on every check green at job level this PR goes ready → auto-merge → queue.


    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