Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .beads/issues.jsonl

Large diffs are not rendered by default.

30 changes: 22 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,25 +70,39 @@ jobs:
- mypy-v1-
- run:
name: Public claims gate
command: ~/.local/bin/uv run devtools verify public-claims --json
command: ~/.local/bin/uv run devtools verify public-claims --json | tee /tmp/polylogue-public-claims.json
- run:
name: Structured PR scope carrier
command: |
if [ -z "${CIRCLE_PULL_REQUEST:-}" ]; then
if [ "${CIRCLE_BRANCH:-}" = "master" ]; then
exit 0
fi
echo "quick-gate requires CIRCLE_PULL_REQUEST for every non-master build" >&2
exit 1
set -o pipefail
if [ "${CIRCLE_BRANCH:-}" = "master" ]; then
exit 0
Comment on lines +78 to +79

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not skip fork PRs named master

When a fork submits a PR from its default branch named master, CircleCI exposes the head branch name through CIRCLE_BRANCH, so this unconditional check exits before inspecting the populated CIRCLE_PULL_REQUEST. That gives the fork PR a green structured-scope step without fetching its body or executing the base validator; the previous ordering only skipped master when no PR URL existed. Restrict this bypass to a confirmed non-PR default-branch build rather than the branch name alone.

AGENTS.md reference: AGENTS.md:L390-L392

Useful? React with 👍 / 👎.

fi
if [ -n "${CIRCLE_PULL_REQUEST:-}" ]; then
~/.local/bin/uv run devtools workspace pr-scope check-ci \
--pr "${CIRCLE_PULL_REQUEST##*/}" \
--repo "Sinity/polylogue" \
--expected-head-sha "${CIRCLE_SHA1}" 2>&1 | tee /tmp/polylogue-pr-scope.log
else
~/.local/bin/uv run devtools workspace pr-scope check-ci \
--repo "Sinity/polylogue" \
--expected-head-sha "${CIRCLE_SHA1}" 2>&1 | tee /tmp/polylogue-pr-scope.log
fi
~/.local/bin/uv run devtools workspace pr-scope check --pr "${CIRCLE_PULL_REQUEST##*/}"
- run:
name: devtools verify --quick
command: ~/.local/bin/uv run devtools verify --quick
- save_cache:
key: mypy-v1-{{ .Branch }}-{{ epoch }}
paths:
- .mypy_cache
- store_artifacts:
path: /tmp/polylogue-public-claims.json
destination: diagnostics
when: always
- store_artifacts:
path: /tmp/polylogue-pr-scope.log
destination: diagnostics
when: always

lab-policies:
docker:
Expand Down
9 changes: 9 additions & 0 deletions .claude/agents/lane.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ Open a PR (branch off `master`, conventional commit-style subject,
rejected if there was a real fork.
- **Verification** — the exact commands you ran and the output line that
matters, not "tests pass".
- **Bead disposition matrix** — one whole-Bead disposition per assigned ID,
typed evidence refs, and an existing open successor for every residual
outcome.

Before publishing the PR as non-draft, render the versioned embedded carrier
with `devtools workspace pr-scope render --input <scope.json>`, put that exact
comment beside the human matrix, and validate the published PR with
`devtools workspace pr-scope check --pr <N>`. Never infer a disposition from
Bead acceptance prose or invent a successor ID.

Reference any bead with neutral wording only (`Ref polylogue-xxxx` /
`Ref #N`). **Never use GitHub resolver keywords** (closes/fixes/resolves)
Expand Down
12 changes: 6 additions & 6 deletions .codex/agents/narrow-worker.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ write to Beads, do not merge or push, do not touch /realm/db/polylogue.
Commit coherent checkpoints. Return exact changed files, commands run and
their output, an acceptance-criteria match table, and residual uncertainty.

For a lane that publishes a PR, emit the versioned PR-scope carrier before the
non-draft PR is opened: render it from assigned Bead IDs, typed whole-Bead
dispositions, evidence refs, and open successors for residual scope using
`devtools workspace pr-scope render`. Embed the result in the PR body and run
`devtools workspace pr-scope check --pr <N>`. Never derive a disposition by
parsing acceptance prose or fabricate a successor Bead ID.
Provide the coordinator with a JSON scope input containing assigned Bead IDs,
typed whole-Bead dispositions, evidence refs, and open successors for residual
scope. The coordinator owns rendering the versioned carrier, embedding it in
the PR body, validating the published non-draft PR, and opening or updating the
PR. Never derive a disposition by parsing acceptance prose or fabricate a
successor Bead ID.
"""
15 changes: 7 additions & 8 deletions .codex/agents/worker.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,11 @@ changed files; the exact commands you ran and their output; an
acceptance-criteria match table against the bead(s) you were assigned;
residual uncertainty; and the commit hash(es).

Before publishing a non-draft PR, create a JSON scope input that names every
assigned Bead, one whole-Bead disposition per ID, typed evidence refs, and an
existing open successor for every partial/deferred/superseded outcome. Render
the embedded carrier with `devtools workspace pr-scope render`, put that exact
comment in the PR body beside the human disposition matrix, and validate the
published PR with `devtools workspace pr-scope check --pr <N>`. Do not ask a
machine to infer Bead acceptance from prose and do not invent missing Bead IDs;
report missing IDs to the coordinator.
Provide the coordinator with a JSON scope input that names every assigned Bead,
one whole-Bead disposition per ID, typed evidence refs, and an existing open
successor for every partial/deferred/superseded outcome. The coordinator owns
rendering the embedded carrier, putting it in the PR body, validating the
published non-draft PR, and opening or updating the PR. Do not ask a machine to
infer Bead acceptance from prose and do not invent missing Bead IDs; report
missing IDs to the coordinator.
"""
71 changes: 0 additions & 71 deletions .github/workflows/pr-state-guard.yml

This file was deleted.

8 changes: 7 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,13 @@ workflow, not optional conveniences — use them at the point named, every time:
it in the PR body beside the human whole-Bead disposition matrix, then run
`devtools workspace pr-scope check --pr <PR>`. The carrier binds the exact
head SHA, canonical Bead records, typed dispositions, evidence refs, and
open successors for residual work; it never parses acceptance prose.
open successors for residual work; it never parses acceptance prose. After
the final commit is created, regenerate the carrier for that exact SHA and
update the PR body before pushing; CircleCI does not rerun for a body-only
edit.
CircleCI uses `pr-scope check-ci`, resolves PR metadata through public GitHub
REST when `CIRCLE_PULL_REQUEST` is absent, and executes the validator from
the PR base revision so a PR cannot weaken its own scope gate.
- **Immediately after spawning a worktree-isolated lane, not after it reports
back**: `devtools workspace verify-worktree <path> --expect-branch
<branch>` — confirms the worktree is real and isolated before the lane has
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ The repository should stay aligned with the workflow above:

- protect `master` against direct pushes
- require pull requests for normal changes
- require the `CI`, `Nix`, and `PR State Guard` checks before merge
- require the authoritative CI checks before merge
- keep squash merge enabled and leave merge-commit and rebase-merge disabled
- enable automatic deletion of head branches after merge
- allow Update branch for stale PRs
Expand Down
4 changes: 2 additions & 2 deletions devtools/command_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,7 @@ class CatalogBypassSite:
examples=(
"devtools workspace pr-scope render --input .agent/pr-scope.json > /tmp/pr-scope.md",
"devtools workspace pr-scope check --pr 3517",
"devtools workspace pr-scope check-ci --pr 3517 --repo Sinity/polylogue --expected-head-sha $(git rev-parse HEAD)",
"devtools workspace pr-scope check --body-file pr-body.md --head-sha $(git rev-parse HEAD)",
),
),
Expand Down Expand Up @@ -829,8 +830,7 @@ class CatalogBypassSite:
"Replace a bare `gh pr merge --squash` with this at the actual merge boundary "
"(polylogue-ct3r2 / polylogue-t6iga: duplicate filings of the same finding -- "
"`merge-gate record/check` and the one-full-verify-per-train rule both existed but "
"fired only if a coordinator remembered to invoke them). `merge <PR>` auto-records a "
"validates the non-draft PR's structured scope carrier and auto-records a merge-gate receipt if none is fresh for the current head sha (running `--command`, "
"fired only if a coordinator remembered to invoke them). `merge <PR>` validates the non-draft PR's structured scope carrier and auto-records a merge-gate receipt if none is fresh for the current head sha (running `--command`, "
'default "devtools verify"), runs `merge-gate check` and refuses to merge on any '
"BLOCK, strips a doubled `(#N) (#N)` squash-subject suffix (the 2026-07-12/13 "
"incident), then runs the actual `gh pr merge --squash`. `--dry-run` runs every check "
Expand Down
12 changes: 11 additions & 1 deletion devtools/merge_boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,17 @@ def cmd_merge(
return 0

merge_result = subprocess.run(
["gh", "pr", "merge", str(pr), "--squash", "--subject", clean_title],
[
"gh",
"pr",
"merge",
str(pr),
"--squash",
"--match-head-commit",
head_sha,
"--subject",
clean_title,
],
capture_output=True,
text=True,
timeout=120,
Expand Down
32 changes: 21 additions & 11 deletions devtools/merge_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,17 +222,6 @@ def cmd_record(pr: int, command: str) -> int:
info = _gh_json(["pr", "view", str(pr), "--json", "headRefOid,headRefName,body,isDraft"])
head_sha = info["headRefOid"]

scope = pr_scope.validate_pr_body(
info.get("body") or "",
head_sha=head_sha,
is_draft=bool(info.get("isDraft")),
)
if not scope.ok:
print(f"REFUSING to record: PR #{pr} has an invalid structured pr-scope carrier:", file=sys.stderr)
for reason in scope.reasons:
print(f" - {reason}", file=sys.stderr)
return 2

local_head = _git_head_sha()
if local_head != head_sha:
print(
Expand All @@ -251,6 +240,17 @@ def cmd_record(pr: int, command: str) -> int:
)
return 2

scope = pr_scope.validate_pr_body(
info.get("body") or "",
head_sha=head_sha,
is_draft=bool(info.get("isDraft")),
)
if not scope.ok:
print(f"REFUSING to record: PR #{pr} has an invalid structured pr-scope carrier:", file=sys.stderr)
for reason in scope.reasons:
print(f" - {reason}", file=sys.stderr)
return 2

argv = shlex.split(command)
if not argv:
print("REFUSING to record: --command is empty after shell splitting.", file=sys.stderr)
Expand Down Expand Up @@ -461,6 +461,16 @@ def cmd_check(
verdict.reasons.append(
"receipt pr_scope_digest does not match the current carrier -- re-record after scope changes"
)
if receipt.get("pr_scope_beads_digest") != scope.beads_digest:
verdict.ok = False
verdict.reasons.append(
"receipt pr_scope_beads_digest does not match the current canonical Bead records -- re-record"
)
if receipt.get("pr_scope_assigned_beads") != scope.assigned_beads:
verdict.ok = False
verdict.reasons.append(
"receipt pr_scope_assigned_beads does not match the current carrier -- re-record"
)
age_s = time.time() - receipt.get("recorded_at", 0)
if age_s > max_age_s:
verdict.ok = False
Expand Down
Loading