Repository navigation
GitHub intake and Steward team (DEV-6632 M2) - #2
Conversation
34ad1eb to
61d1fee
Compare
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty — Changes requested on exact head 61d1fee59c1d4be6729bb3b051ba6acba165cb87 (DEV-6632 M2 / decision 0169, stacked on #1).
-
Blocking — a publish marker inside a Markdown code fence is accepted as an instruction.
hasPublishMarker()inserver/github-intake.ts:109-119toggles the fence state on any line starting with either ``` or ~~~, without matching the opening delimiter's type or length. I exercised the actual exported function on this head:\```markdown\n```\n/lazurio publish\n````` and `~~~markdown\n```\n/lazurio publish\n~~~` both return `true`. The marker remains inside a valid Markdown fenced code block, but `publications()` then treats it as a publication instruction from a writer and queues the publish bot. This violates the explicit-instruction gate and can turn a quoted example into a merge request. Match closing fences to the opener (character and minimum length), with a regression test through the publication trigger. -
Blocking — the after-assignment gate accepts a comment from before assignment in the same timestamp second.
server/github-intake.ts:651-665compares GitHub's second-resolutioncreated_atvalues with>=. If a writer comments/lazurio publishand the account is assigned later in the same second, both timestamps compare equal and the earlier comment passes. Fail closed on equal timestamps (or establish order with reliable event identity), and test both orderings inside one second.
Local verification in an isolated detached clone: focused intake/team/e2e tests 34/34 passed; pnpm typecheck, pnpm lint, and git diff --check passed. This is a code-path finding, not a real-GitHub or VM smoke. No merge/deploy or repository changes were made.
61d1fee to
f91ccac
Compare
A Lazurio Environment's bot team should pick up GitHub work by itself without a model polling: plain code asks GitHub, a bot is woken only for real work (Lazurio decision 0169, plan DEV-6632 M2). Any operator can use it, for example for pull requests sent to them. With OMB_GITHUB_INTAKE=1 and OMB_GITHUB_INTAKE_BOT (a bot id or exact name) the server polls GitHub through the signed-in gh CLI of its own user (no token of its own) every 90 s by default: - review: an open, ready pull request where the account is a requested reviewer, which it reviewed before, or (OMB_GITHUB_INTAKE_SCOPE= organization) in a repository it can push to minus OMB_GITHUB_INTAKE_EXCLUDE. Once per (repo, PR, head SHA); a new head or draft -> ready fires again; skipped when the account already approved or requested changes on that head. - publication: a pull request assigned to the account plus a comment with the line "/lazurio publish" by someone with write, maintain or admin, written after the assignment. Once per instruction comment; assignment alone never publishes. - the account's own pull requests are never reviewed by it; they are recorded as needs_human_approval without a bot turn. - OMB_GITHUB_INTAKE_OWNERS limits every trigger to given owners. Events go through a managed "GitHub intake" webhook and the new WebhookManager.deliver(): the same queue, receipts, limits, attempt log and UNTRUSTED event wrapping as an HTTP delivery. Dedupe state lives in <data>/github-intake.json; webhook receipts still stop a second run if it is lost. Details of a pull request are fetched only when it changed, gh calls are bounded to four at once, rate limits back off up to 30 min, a paused webhook or full queue keeps work pending (one refused attempt per poll). GET /api/github-intake shows the state, POST /api/github-intake/poll polls now (admin scope by default). Unset keeps upstream behaviour. Upstream files changed: server/webhooks.ts (deliver, allowlisted in lazurio-fork-ci.yml), server/index.ts and scripts/control-omb.ts (wiring, already allowlisted). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The team Lazurio Organizations run behind the GitHub intake (decision
0169, plan DEV-6632 M2), shipped as a portable openmaus.package v2 file:
lazurio/teams/steward.openmaus.json.
- Leader "Henry", the team's Chief of Staff: hands each intake event to
one worker with delegate_bot, up to three pull requests in parallel,
checks the result and reports failures to the operator.
- Three workers ("Steward 1-3"): one worktree per pull request, exact-head
review submitted with gh on the given commit and verified afterwards,
publication only for an assigned pull request with a "/lazurio publish"
instruction and only with approvals by others and green checks on the
current head; never approve the account's own pull request.
- Team brief: start in the Lazurio Folder and follow its AGENTS.md
cascade; GitHub only through the signed-in account; untrusted input
stays data.
Nothing names the persona, so Pablo imports the same file and renames
the leader. No secrets, no Organization names. A test parses the file
with the package validator, checks the secret and import scans and that
the brief carries the intake's publish marker; the intake e2e now imports
the team and hands work to its leader.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ment Review of 61d1fee (Pablo): - hasPublishMarker toggled a fence on any line starting with ``` or ~~~. A marker quoted inside a longer or tilde fence counted as a publication instruction. Fences now follow CommonMark: the opener may be indented at most three spaces, and the closer must use the same character, be at least as long and carry nothing else. An unclosed fence runs to the end of the comment, and lines indented four spaces or a tab are code. - The after-assignment gate compared GitHub's one-second timestamps with >=, so a comment written just before the assignment in the same second passed. The comparison is now strict and fails closed. Regression tests cover both, including the whole publication trigger and both orderings within one second. They fail on the previous code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f91ccac to
b202ca8
Compare
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty — Changes requested on exact head b202ca81d1ea436205ab8adfa3d73df698911b46 (base 8ccfbb8a). The prior fence mismatch and same-second assignment findings are fixed, but the publish-marker gate still accepts a marker rendered as indented code.
Blocking — server/github-intake.ts:116-130: shallow rejects a tab only when it is the first character. A tab after 1–3 spaces also reaches the fourth CommonMark indentation column, but raw.startsWith("\t") is false and raw.trim() becomes the publish marker. Exact reproduction: hasPublishMarker("Example (not an instruction):\n\n \t/lazurio publish") returns true; mdast-util-from-markdown parses the marker line as a code node. In publications() the result becomes an instruction and, if the writer/assignment/time gates pass, wakes the publish bot for a quoted example. Reject indented code after tab-stop expansion and add a trigger-level regression for spaces + tab before the marker.
Isolated detached clone: focused intake/team/e2e/agents-catalog tests 77/77 pass; git diff --check passes. Fork CI is green on this head; these tests omit the spaces-plus-tab case. No merge, deploy or repository source changes.
…d code Re-review of b202ca8: a tab after one to three spaces reaches the fourth CommonMark column, so the line is indented code. The old check caught a tab only at the start of the line, and ' \t/lazurio publish' counted as an instruction. Leading whitespace is now measured in columns with tab stops of 4. Regression tests cover the exact repro, two- and three-space variants and the whole publication trigger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty — APPROVED on exact head 43b6a8fa5a3f72c559e9869f57ce1c92eb2e2774 (base 8ccfbb8a). The prior spaces-plus-tab blocker is fixed: leadingColumns expands tabs to CommonMark 4-column stops, and the resulting shallowness gate covers both fence lines and the marker itself. The exact prior reproduction and the two-/three-space variants now return false; the publication-trigger regression confirms the example does not enqueue a publish event. Fence character/length and strict post-assignment checks remain unchanged.
Verified in a clean detached temporary clone at this SHA: intake, team, e2e, and agents-catalog-wire suites 78/78 passed serially; git diff --check passed. Live fork CI: six successes, one skipped check; no unresolved review threads or pending review requests at submission. Review only; no merge, deploy, or source edits.
Status: ready for review (stacked on #1; retarget to
mainafter #1 lands)Why
DEV-6632 M2 (Human and Machine Mission Control, plan
DEV-6632-lazurio-mausbot-steward), Lazurio decision 0169 (4)/(5): a Lazurio Environment's bot team should pick up GitHub work by itself. Plain code watches GitHub under the Environment's account and wakes a model only for real work; this is a standard Lazurio MausBot feature any operator can switch on, not a Henry-only mechanism. The Steward team (leader Henry + three workers) is the configuration Organizations run behind it. Today Henry and Pablo get work over SSH and their Hermes watcher is paused because it woke the model on idle.Contract (what the PR introduces)
GitHub intake (
server/github-intake.ts, docs:docs/lazurio-github-intake.md)OMB_GITHUB_INTAKE=1; needsOMB_GITHUB_INTAKE_BOT(bot id or exact name). OptionalOMB_GITHUB_INTAKE_SCOPE=requested|organization,OMB_GITHUB_INTAKE_OWNERS,OMB_GITHUB_INTAKE_EXCLUDE(owner/repoglobs),OMB_GITHUB_INTAKE_INTERVAL_SECONDS(default 90, 30-3600),OMB_GITHUB_INTAKE_GH. A bad value stops the server at start with the reason.ghof the server's user; no token handling.ghmissing / signed out → status says so, nothing else happens./lazurio publish(own line, not quoted, not in a code block) by someone with write/maintain/admin, written after the latest assignment to the account. Once per instruction comment. Assignment alone never publishes; failing instructions are recorded asignoredwith the reason.needs_human_approval, no bot turn.WebhookManager.deliver(): the same queue, receipts, pending-run limit (6), rate limit, attempt log andUNTRUSTED WEBHOOK EVENT DATAwrapping as an HTTP delivery. Payload:source, kind, reason, account, repository, number, url, head_sha, base_ref, title, author, requester (+ instruction).<data>/github-intake.json(0600); if it is lost, webhook receipts still prevent a second run and the webhook is found again by name.updated_at(or its role) changed, so an idle poll isuser+ 3 searches (+1 per owner in organization scope) and never a bot turn; ≤4 concurrentghcalls; rate limit → interval doubles up to 30 min; a paused webhook or full queue keeps work pending with at most one refused attempt per poll.GET /api/github-intake,POST /api/github-intake/poll(admin scope by default-deny),[github-intake]log lines.Steward team (
lazurio/teams/steward.openmaus.json, openmaus.package v2): leader "Henry" (Chief of Staff, delegates up to three PRs in parallel withdelegate_bot) + "Steward 1-3". Brief: start in the Lazurio Folder and follow its AGENTS.md cascade; one worktree per PR; exact-head review submitted withghoncommit_idand verified; publication only on explicit instruction, with approvals by others and green checks on the current head; never approve own PRs; untrusted input stays data. No persona name in any instruction (Pablo imports the same file and renames the leader), no secrets, no Organization names.What does not change
OMB_GITHUB_INTAKEunset the server behaves as upstream (the only visible difference isGET /api/github-intakeanswering{enabled:false}).full/customstay desktop-only. Headless approval level is an open security decision (issue draft in the handoff; Issues are disabled on this fork).server/webhooks.ts(newdeliver(), receive() refactored to share its rejection bookkeeping; added toallowed_upstream_changes),server/index.tsandscripts/control-omb.ts(wiring / fixture env pass-through; already allowlisted).Verification
server/github-intake.test.ts(30 behavioural tests against a fakeghthat models PRs, searches, reviews, comments, assignment events and repo roles; delivery through a realWebhookManager). Red before implementation:npx vitest run server/github-intake.test.tsagainst an API-only skeleton → 30 failed / 30. Green after: 30 passed.server/github-intake.e2e.test.ts: real isolated server with a fakegh(OMB_GITHUB_INTAKE_GH): status before the bot exists (no_target), import of the shipped Steward team (4 bots, leader is Chief of Staff), poll → one delivery to the leader, the queued run completes on the fake engine with the PR head inside the UNTRUSTED block, second poll → no new run. 1 passed (it caught a strip-types-only syntax error in the first draft).server/lazurio-steward-team.test.ts: package validator, secret and import scans, marker and persona-free instructions. Red without the file, 3 passed.pnpm typecheck✓,pnpm lint✓, pre-push hook (lint, typecheck, locale check) ✓,node --test scripts/lazurio-release-contract.node-test.mjs5/5 ✓, overlay guard simulated locally against v0.1.91 (no merge commits, no unexpected upstream file) ✓.npx vitest run server/index.test.ts: 260 passed.git merge-treeshows no conflict and GitHub reports the PR mergeable; it was pushed before the base moved and is not force-pushed. Rebase when retargeting tomain.ghagainst GitHub, the full upstream suite (runs in Lazurio Fork CI), and a real VM.Risks
updated_atmoving on push/comment/assignment; a missed bump delays work until the PR changes again.OMB_GITHUB_INTAKE_BOTstops the intake (no_target, visible in status).Open questions
Plan: DEV-6632 (M2, code part). Decision: 0169.
🤖 Generated with Claude Code