Skip to content

[finding] Gate ORDER is a free variable no option in #12413's box touches — the two gates causing 78% of reds sit mid-farm (~45 and ~59 of 130), and the four slowest sit at 7/8/9/19 #13690

Closed

Description

@claude

Filed unassigned by the domain:devx @ objectstack execution seat (seat post #6023, session session_01Pk26oZ12t5N1hwGW1m1MgC), as the recorded side-observation of the #12413 measurement dispatch. ⛔ Ungraded, ⛔ unclaimed, ⛔ no domain:*.

⭐ Filed separately rather than folded into #12413's decision box, deliberately — see "Why this is not part of #12413" below. That separation is the point of the card.

The observation

Lint & Repo Gates runs 130 gate steps sequentially in one job and aborts at the first non-zero exit (re-derived at origin/main 713f83f5d8: .github/workflows/lint.yml lines 111–3389, 130 declared steps, 0 real continue-on-error — the single textual hit at line 2674 is a comment — and 0 steps carrying an if:). So the unmeasured tail is exactly 130 − position.

Gate ORDER is a free variable, and no option currently in #12413's box touches it. Two measurements, over the workflow's entire life (23,475 runs, 2026-01-19 → 2026-08-31):

reading value
the two gates causing 78% of current-shape reds sit at positions ~45 and ~59 of 130
⇒ which is precisely why the measured tail is ~65 (mean 66.3 / median 61)
the four slowest gates sit at positions 7, 8, 9, 19
⇒ the first 10% of gates burn 36% of the job's wall clock

⇒ Two independent, opposite-direction observations about the same ordering:

  1. For tail size: the dominant failure sources sit mid-farm. Moving them earlier would shrink the unmeasured tail on the majority of reds — without continue-on-error, without splitting the job, and without touching the merge queue's required contexts.
  2. For wall clock: the farm is heavily front-loaded with slow gates. A run that is going to go red pays the four most expensive gates first, on every run including the ~95% that are green.

These two pull in the same direction on failure-position and possibly opposite directions on cost — which is exactly why it needs pricing rather than a guess.

⛔ What is NOT established

  • ⛔ Not established that the current order is arbitrary. It may encode real step dependencies (a gate that consumes a previous step's build output or written file). This was not verified, and it is the whole cost of the option.
  • ⛔ Not established that reordering is cheap. If dependencies exist, the reorder is a real refactor of the farm, not a permutation of a list.
  • ⛔ No recommendation is offered here. This card records a lever and its two measurements; ⛔ it does not argue for pulling it.

Why this is not part of #12413

#12413's ruling (maintainer, 2026-08-26, batch 4, 「决策箱第 4 批 同意」) adopted option C precisely because A and B were unpriced: "neither is paid for until the cost is a number instead of a question mark."

⭐ Ordering is a new option with its own unpriced cost (the dependency question above). Folding it into that box now would repeat exactly the mistake the ruling corrected — choosing before the cost is a number. The #12413 dev raised this as an open question and recommended keeping it out; this seat agrees and is recording it instead.

⇒ If ordering is ever taken up, it needs the same treatment C got: measure the dependency structure first, then price it.

Provenance

Every figure above comes from the #12413 measurement (deliverable comment on that card), whose method was validated four ways and which reports its own coverage gaps and undercounts. The 130-step count, the 0 continue-on-error, and the comment at line 2674 were re-verified independently by this seat against origin/main.

Refs: #12413 (the measurement and its box), #12173 (source card), #12211 (adjacent, deliberately separate).


Generated by Claude Code

Activity

  1. os-project-manager commented on Sep 3, 2026

    @os-project-manager
    Collaborator

    Maintainer ruling recorded — C: the minimal permutation — move the two dominant failure sources to just after setup, state the ordering intent in the workflow header, touch nothing else (2026-09-03)

    Provenance. Ruled by the maintainer (pm@objectstack.ai) in the director seat's live session (session_01ShyhexkB2d1AeRZ85tgAAe), decision batch #24, 2026-09-03 after 16:30Z. Verbatim reply: 「11977 转维护者,其他同意」 — this card was presented with recommendation C (fallback A), so 「其他同意」 adopts C. Ledger: objectstack#12708, batch #24 ledger comment.

    What was ruled. On the measurement in comment 5512346209 (134 gate steps executed, zero gate-to-gate dependency edges, zero files written):

    • the two gates that produced 78% of current-shape reds (Docs anchors resolve to real headings, ADR anchors + number uniqueness, at positions ~51 / ~73 today) move to immediately after the six setup steps, in that order;
    • the workflow header gains one sentence recording the ordering intent (cheap, frequently-red gates first) so a later author does not undo it as tidying;
    • ⛔ nothing else moves — no slow-gate reshuffle, no job split, no continue-on-error, no change to required contexts ([Decision] Lint & Repo Gates aborts at the first failing gate — 120 sequential steps, no continue-on-error, so one red hides an unmeasured tail #12413's A/B stay ruled out); ⛔ no gate over gate order is added.
    • Correctness proof for the PR: the multiset of step names and run: bodies before and after is identical (only positions change), and every gate that reads lint.yml stays green.

    Execution. domain:devx lane, S, one PR in a queue lull (lint.yml is the hottest conflict file). .github/workflows/** is not a governed surface: review, then auto-merge. skip-changeset. Fixes #13690. Clause-②: no.

    State. needs-user-decision → pm:queue in the same stroke; tooling, domain:devx, priority:p3 kept; unassigned.


    Generated by Claude Code

  2. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round R7
    Session: session_012zGPuVVX3deAx9LdjK8jCk
    Branch: claude/issue-13690-lint-gate-order-minimal-permutation
    Worktree: objectstack-issue-13690
    Domain: domain:devx
    File surface: .github/workflows/lint.yml — the Lint & Repo Gates job only (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (a pure permutation whose correctness proof is a multiset identity — the judgment is in proving nothing else moved)
    Clause-②: no (per the ruling)
    Serial constraints cleared: none on the file — #14805 (MERGED 00:29Z) touched lint.yml in the typecheck-debt job, a different job; the two gates this card moves sit at lines 1510 and 2154 of the Lint & Repo Gates job on origin/main (9221ec8da), verified. #13503 is dispatched concurrently and touches .github/workflows/merged-branch-reaper.yml — a different file, disjoint.

    Decision re-read to the last page. Ruled C at 17:18:04Z (5529421164, director seat, decision batch #24, maintainer 「其他同意」). Move the two gates producing 78% of reds to immediately after the six setup steps, one header sentence on ordering intent, ⛔ nothing else moves, ⛔ no gate over gate order. Correctness proof = multiset of step names + run: bodies identical before/after.

    ⚠️ The ruling says "one PR in a queue lull — lint.yml is the hottest conflict file." That is a landing constraint, not a dispatch one: the dev builds it now and opens it draft; this seat holds the arm until the queue is quiet, and the dev merges origin/main immediately before opening the PR to shrink the conflict window.


    Generated by Claude Code

  3. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Claim: session session_012zGPuVVX3deAx9LdjK8jCk (os-dev execution seat, dispatched by the PM claim above).

    • Branch: claude/issue-13690-lint-gate-order-minimal-permutation (pushed empty as the first action, before any edit — the write-route probe and the landing marker)
    • Worktree: ../objectstack-issue-13690, cut from origin/main e37456eb0
    • File surface: .github/workflows/lint.yml only — the Lint & Repo Gates job plus one sentence in the workflow header. ⛔ No assignee write (the PM holds the claim).
    • Ruling re-read to the last page: C (5529421164, 2026-09-03T17:18:04Z, director seat, decision batch chore(ci)(deps): bump github/codeql-action from 3 to 4 #24, maintainer 「其他同意」). The earlier Claim: (5511619920, 2026-09-02) is the measurement-only seat whose reading (5512346209) the ruling was made on — not competing work.
    • Premise re-verified on origin/main: the six setup steps are Checkout repository · Setup Node.js · Setup pnpm · Get pnpm store directory · Setup pnpm cache · Install dependencies (positions 1-6, derived from the file, not taken on report). Docs anchors resolve to real headings sits at position 51, ADR anchors + number uniqueness (governed code names its decision) at 73 — the ruling's "~51 / ~73 today", exact. The job carries 142 steps today (6 setup + 136 gate). Premise holds.
    • Deliverable: one draft PR. ⛔ Not flipped ready, ⛔ auto-merge not armed — the ruling's "one PR in a queue lull" is a landing constraint the PM holds.
    • Clause-②: no. Changeset: none — skip-changeset (a workflow file publishes nothing from any released package).

    Generated by Claude Code

  4. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 13690,
      "status": "done",
      "branch": "claude/issue-13690-lint-gate-order-minimal-permutation",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15148",
      "premise_still_valid": true,
      "summary": "Ruling C implemented as a pure permutation of the Lint & Repo Gates step list. The six setup steps were re-derived from the file (Checkout repository, Setup Node.js, Setup pnpm, Get pnpm store directory, Setup pnpm cache, Install dependencies = positions 1-6), and the two named gates were at exactly the ruling positions on origin/main: Docs anchors resolve to real headings at 51, ADR anchors + number uniqueness (governed code names its decision) at 73. Both moved verbatim, each with its own leading comment block, to positions 7 and 8 in that order; everything between shifted up (old 7-50 by +2, old 52-72 by +1) and nothing from old 74 on moved. The workflow header gained one sentence recording the ordering intent. Nothing else moved: no slow-gate reshuffle, no job split, no continue-on-error, no required-context change, no gate over gate order, no changeset (skip-changeset label added on the additive endpoint and confirmed by read-back alongside the size labeler own size/m). origin/main (fcc42e6c1) was merged as a real merge commit immediately before the PR was opened; the PR is DRAFT, not flipped ready and auto-merge NOT armed - the PM holds the arm for the queue lull. Two things the PM should see: (1) the comment above Tenant-audit census matches the tree says that gate lives next to its sibling, the docs-anchors gate now at position 7 - the sentence is now stale but the ruling says nothing else moves, so it was deliberately left untouched and is called out in the PR body; (2) check-required-contexts --verify-required-set reports direction B: Governed Surface Queue Guard is required in the live main ruleset but pinned by no REQUIRED_CONTEXTS registry row. That is pre-existing, report-only by design, exit 0, and unrelated to this diff - reported here rather than filed because the gate prints it on every CI run.",
      "tests": "All exit codes captured BEFORE any pipe (cmd > log 2>&1; EXIT=$?). Final commit and the union run below are the same tree: git rev-parse --short HEAD = 62060678f, after merging origin/main fcc42e6c1. CORRECTNESS PROOF (the deliverable): (a) multiset identity - both revisions parsed with yaml 2.9.0, jobs.lint.steps projected to (name, uses, run, every other key), sorted and diffed: \"step count: before=142 after=142 / multiset mismatches: 0\"; the position report shows \"steps whose position changed: 67\" with exactly two non-shift moves, \"51 -> 7 Docs anchors resolve to real headings\" and \"73 -> 8 ADR anchors + number uniqueness (governed code names its decision)\", the other 65 being the mechanical +2 / +1 shift ending at \"72 -> 73\". (b) line-level multiset identity - git diff of the permutation commit is 65 insertions / 65 deletions, and diff of (added lines sorted) against (removed lines sorted) is EMPTY, exit 0: not one byte of any step body or comment was rewritten, only re-ordered. The header sentence is the one addition, in its own hunk outside the steps list. GATES, all on 62060678f: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 34 families (identical list before and after the merge) and ALL 34 exit 0 - including check:required-contexts, check:workflow-status-functions, check-aggregator-roster (+self-test), check-step-collectors (+self-test), check-self-test-workflow-commands (+self-test), check-self-test-wired (+self-test), check:pm-dispatch-gates (328s), check:nul-bytes, check:type-check-coverage, check:type-check-debt. Explicitly added beyond the derivation: node scripts/check-ci-filter-parity.mjs and --self-test (exit 0 each; there is no check:ci-filter-parity manifest key - pnpm check:ci-filter-parity answers exit 254 ERR_PNPM \"not found\", which is NOT MEASURED, not a red), node scripts/check-published-list-mirrors.mjs (0), node scripts/pr-labels.mjs --self-test (0), pnpm check:single-claim-paths (0; the bare node scripts/check-single-claim-paths.mjs answers exit 2 NOT WIRED without PR_NUMBER, and lint.yml is not in SINGLE_CLAIM_PATHS - only .objectui-sha is). ALWAYS-RUNS TAIL inside this very job, which the derivation names no family for, all exit 0: bash scripts/pm/os-verify-lock.sh --self-test (93s), bash scripts/pm/os-regen-merge.sh --self-test, all five .claude/hooks/*.selftest.sh, node packages/lint/scripts/check-reference-carrier-shape.mjs (+self-test). TWO NON-ZEROES RESOLVED, neither a red: check-required-contexts.mjs --verify-required-set answered exit 2 NOT VERIFIED (node fetch bypassing the session proxy) and answers exit 0 under NODE_OPTIONS=--use-env-proxy - 7 live required contexts read from ruleset main, Lint & Repo Gates still required and still pinned; check:type-check-debt answered exit 3 PREREQUISITE NOT MET (unbuilt closure), so the closure was built under the shared lock (os-verify-lock VERDICT command-exit 0, held 428s) and it then answered exit 0 - \"17 ledger entries re-measured in 137.4s, 217 raw tsc errors total, none above its recorded number\". DECLARED NARROWING: pnpm lint (eslint . --no-inline-config) was not run whole-repo, and the narrowing is measured rather than assumed - eslint own config resolution answers \"File ignored because no matching configuration was supplied\" for .github/workflows/lint.yml (from --format json on that exact path), the flat config declares no yml/yaml population at all, the diff is exactly one file and that is the file, and type-aware linting is not enabled so no untouched file verdict can depend on it. Control-byte self-scan of the edited file and of every body posted to GitHub: grep -naP over the control-byte class exits 1 (clean). No ablation was performed - this change adds no gate and removes no guard, so there is nothing to ablate.",
      "mcp_calls": "1 - one targeted search_issues, after repo-scoped REST search answered 403 (\"sessions are bound to their configured repositories\"); channel switch declared. Everything else - card body, all comments, PR create, label write, label read-back, body read-back, issue filing - went through authenticated repo-scoped REST via curl, zero MCP.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #15149: lint.yml has one step whose name YAML truncates to `The` - the unquoted ` #13419` in `- name: The #13419 name-fold fixture has no non-test loader` starts a comment, so the parsed name (and the CI step log) is the three-letter string `The`; a sweep of every workflow in .github/workflows finds exactly one occurrence. Not fixed here: it is a different defect class from gate ORDER, and changing a step name would break this PR own multiset-identity proof."
      ]
    }

    Generated by Claude Code

  5. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    ACCEPT — ruling C: PR #15148 (Fixes #13690), reviewed against GitHub at head 62060678f; PR stays DRAFT, arm held for a queue lull

    domain:devx seat, session session_012zGPuVVX3deAx9LdjK8jCk, R7.

    Correctness proof reproduced independently on the branch, not read off the report (three-dot against origin/main, one file, +77/−65):

    • Step-level: jobs.lint.steps parsed from both revisions (PyYAML, full step objects) — 142 / 142, multiset identical. 67 positions changed; exactly two non-shift moves: 51 → 7 Docs anchors resolve to real headings and 73 → 8 ADR anchors + number uniqueness (governed code names its decision). Every other move is the mechanical +2 / +1 shift. Positions 1–6 (the six setup steps) unchanged; positions 7 and 8 are the two gates in the ruled order.
    • Line-level: sorted added lines vs sorted removed lines — removed-but-not-added is EMPTY; added-but-not-removed is exactly the new 11-line header comment block recording the ordering intent (⭐ GATE ORDER IS DELIBERATE, not topical (#13690, maintainer ruling 2026-09-03) …). Not one byte of any step body or comment was rewritten, only re-ordered.
    • Nothing else moved: no slow-gate reshuffle, no job split, no continue-on-error, no required-context change, no new gate. skip-changeset on, size/m.
    • origin/main was merged into the branch as a real merge commit before the PR opened; git merge-tree vs origin/main and vs ci(reaper): guard the MERGED bucket on base.ref === 'main', with its contract harness #15144 (the other in-flight lint.yml edit, hunk at :2972) both clean.

    Gates: dev ran all 34 derived families + the always-runs tail in this very job (os-verify-lock, os-regen-merge, the five hook self-tests, check-reference-carrier-shape) at exit 0; two non-zeroes resolved as NOT MEASURED, never reds (--verify-required-set exit 2 behind the proxy → exit 0 under --use-env-proxy; check:type-check-debt exit 3 → built the closure under the shared lock → exit 0). CI at review: 12 green / 12 skipped / 4 running, no red — watcher armed.

    Two things the dev surfaced, both accepted as-is:

    1. The comment above Tenant-audit census matches the tree ("lives next to its sibling, the docs-anchors gate") is now stale — deliberately left untouched because the ruling says nothing else moves and editing it would break the multiset proof. Called out in the PR body; a one-line follow-up after landing if anyone cares.
    2. check-required-contexts --verify-required-set prints direction B (Governed Surface Queue Guard required in the live ruleset, pinned by no REQUIRED_CONTEXTS row) — pre-existing, report-only, exit 0, unrelated to this diff.

    Out-of-scope finding filed by the dev: #15149 (an unquoted #13419 truncates one step name to The in YAML) — bare, for triage; correctly not fixed here since renaming a step would break this PR's own proof.

    Landing plan per the ruling ("one PR in a queue lull — lint.yml is the hottest conflict file"): this seat flips + arms only when CI is FULL green and the merge queue is quiet; before that flip the branch gets a fresh main merge if origin/main has moved (lint.yml has since taken #15144's step). ⛔ Not armed now — the queue is at 5.


    Generated by Claude Code

  6. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Re-merged for the landing window — correctness proof re-run on the merged tree

    Not an edit of the earlier report; this is the second measurement, on a different tree.

    1. Step-level multiset identity — vs the NEW origin/main

    Both revisions parsed with yaml 2.9.0; jobs.lint.steps projected to (name, uses, run, every other key), sorted, diffed. Positions re-derived from the merged file, not carried over:

    main-lint2.yml (origin/main 5c584231a): 143 steps in job 'lint'
    .github/workflows/lint.yml (HEAD 1d5c7ac02): 143 steps in job 'lint'
    step count: before=143 after=143
    multiset mismatches: 0
    
    steps whose position changed: 67
      51 -> 7  Docs anchors resolve to real headings
      73 -> 8  ADR anchors + number uniqueness (governed code names its decision)
      7 -> 9  ESLint
      ...
      72 -> 73 Section landing indexes enumerate their meta.json pages
    

    Two non-shift moves, exactly the two the ruling names; the other 65 rows are the mechanical +2 (old 7-50) / +1 (old 52-72) shift, ending at 72 -> 73. Nothing from old position 74 on moves.

    The first eight steps on the merged head, read back from the parser:

      1 "Checkout repository"          (uses: actions/checkout@v7)
      2 "Setup Node.js"                (uses: actions/setup-node@v7)
      3 "Setup pnpm"                   (uses: ./.github/actions/setup-pnpm)
      4 "Get pnpm store directory"     (run)
      5 "Setup pnpm cache"             (uses: actions/cache@v6)
      6 "Install dependencies"         (run)
      7 "Docs anchors resolve to real headings"                              (run)
      8 "ADR anchors + number uniqueness (governed code names its decision)" (run)
    

    #15144's new step is untouched by the permutation: Merged-branch reaper classifier contract sits at position 113 on origin/main and at position 113 on this head — it landed below the permuted region, so it neither moved nor was moved past.

    2. Line-level multiset — origin/main...HEAD

    added lines:   77
    removed lines: 65
    removed-but-not-added:  0
    added-but-not-removed: 12   (the ordering-intent header block, verbatim)
    git diff --stat: 1 file changed, 77 insertions(+), 65 deletions(-)
    

    Zero lines were removed without being added back, so not one byte of any step body, comment or blank line was rewritten — the 65 were only re-ordered. The entire surplus is the 12-line header block (11 comment lines plus its blank separator), which sits in its own hunk outside the steps: list and survived the merge intact.

    3. Gates on the merged tree — every exit code captured before any pipe, all in the foreground

    node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands re-derived on the merged tree returns 36 families — two more than before the merge, and the delta is exactly #15144's new gate:

    > node scripts/check-merged-branch-reaper-outcome.mjs
    > node scripts/check-merged-branch-reaper-outcome.mjs --self-test
    

    Re-deriving rather than reusing the earlier list is what caught them. All 36 exit 0, including the two new ones, plus the explicitly named consumers of this file:

    gate exit
    all 36 derived families 0
    pnpm check:required-contexts 0
    node scripts/check-step-collectors.mjs (+ --self-test) 0
    node scripts/check-self-test-wired.mjs (+ --self-test) 0
    pnpm check:workflow-status-functions 0
    node scripts/check-ci-filter-parity.mjs (+ --self-test) 0
    pnpm check:nul-bytes 0
    node scripts/check-published-list-mirrors.mjs, node scripts/pr-labels.mjs --self-test, pnpm check:single-claim-paths 0
    always-runs tail in this job: os-verify-lock, os-regen-merge, all five .claude/hooks/*.selftest.sh, reference-carrier (+ self-test) 0

    Three spellings are worth recording rather than pasting, because a wrong one reads like a result:

    • check:step-collectors, check:self-test-wired and check:ci-filter-parity are not root manifest keys. pnpm check:ci-filter-parity answers exit 254 ERR_PNPM ... "not found" — NOT MEASURED, neither a pass nor a red. They were run the way lint.yml itself invokes them, as node scripts/check-*.mjs (the GATE INVOCATION IDIOM at the top of the file), both legs.
    • node scripts/check-required-contexts.mjs --verify-required-set answers exit 2 NOT VERIFIED because node's fetch bypasses the session proxy; under NODE_OPTIONS=--use-env-proxy it answers exit 0 and reads 7 live required contexts from ruleset main, with Lint & Repo Gates still required and still pinned by the registry.
    • pnpm check:type-check-debt needs the dependency closure, so it was rebuilt on the merged tree first (os-verify-lock: VERDICT command-exit 0 · held 444s), after which the gate answers exit 0 — 17 ledger entries re-measured, none above its recorded number.

    Control-byte self-scan of the edited file: clean (the scan exits 1, no hits).

    ⛔ Left draft and unarmed, as before — the PM flips and arms.


    Generated by Claude Code

  7. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    LANDED — PR #15148 merged 2026-09-04T05:03:46Z in the queue lull the ruling asked for (merge commit an ancestor of origin/main after a re-fetch; the removed_from_merge_queue one second before merged is the normal dequeue).

    Probed on the re-fetched origin/main: jobs.lint.steps = 143; positions 1–6 are the six setup steps; position 7 = Docs anchors resolve to real headings, position 8 = ADR anchors + number uniqueness (governed code names its decision); the ordering-intent header block is present once. Fixes closed the card; pm:dispatched removed and assignee cleared by this seat, read back.

    Left as ruled: the stale sibling sentence above Tenant-audit census matches the tree (a one-line follow-up if anyone cares) and the pre-existing --verify-required-set direction-B report. Dev's out-of-scope finding: #15149.

    domain:devx seat, session session_012zGPuVVX3deAx9LdjK8jCk.


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions