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
18 changes: 9 additions & 9 deletions .trinity/seals/queen_QueenApp.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
{
"built_by": "t27c-bootstrap@0.5.2+cc19d1fcd",
"built_by": "t27c-bootstrap@0.5.2+9448ab4be",
"closure": [],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:6826f83e8df029b2611772eb05ae83f6f74f2753878234aecadde730f25d3aea",
"gen_hash_rust": "sha256:59377e2be1ac583fdca7c92f2b9167be28cfbf649feae60ef60dbcc316e4f03b",
"gen_hash_verilog": "sha256:ac673fb18c41bc3710d4cf89d2de9274aad10daa109765ed9350bada4d61e883",
"gen_hash_zig": "sha256:b7164f9a42b17ff9f13866b5c46281d6d2f5584a69dae33c70ce3f2ec507c23f",
"gen_hash_c": "sha256:5c99dce3207e8ce0ec9d806e6260dd0564d1d63bea63b352eb3ba89e7c1bf753",
"gen_hash_rust": "sha256:96960c99d5f0b870bb694b8e2f4afbbf3df3a672bd9552636bff5f7c66a3579a",
"gen_hash_verilog": "sha256:925f962b7acfa382f9bfcf8281c0f28997595f1a779326a9a4aa3f062bdd533c",
"gen_hash_zig": "sha256:ab6e6e5f41b1dd79f553ca3572b64f66748a9c3cd8dbdf39a6c2db526c1e48f7",
"module": "QueenApp",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-11T10:29:36Z",
"sealed_at": "2026-10-18T13:27:28Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:854770015052ccdf70a7e8b7b831ed878a3100b028e2b3ff39010c3cbf8eccbe",
"spec_hash": "sha256:6909de6d8fbda29f8aaa41612838c7c9818aecec78a142b94888e54908890802",
"spec_path": "specs/queen/app.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 19,
"total": 19,
"passed": 24,
"total": 24,
"vacuous": 0
},
"toolchain": {
Expand Down
99 changes: 96 additions & 3 deletions specs/queen/app.t27
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,24 @@ pub const GE_CHECK_RERUN : u8 = 10;
pub const GE_CI_DONE : u8 = 11;
pub const GE_MERGE_GROUP : u8 = 12;
pub const APP_EVENTS_CI : u8 = 13;
/// The issue events (#10399), appended after APP_EVENTS_CI so that no earlier code moves: a supervisor
/// built before them reads them as codes app_reaction and ci_reaction ignore. dispatch_reaction is what
/// answers them, so a new or relabelled issue is a dispatch on the webhook and the poll is the backstop.
/// ISSUE_OPENED an issue was opened (issues.opened)
/// ISSUE_REOPENED a closed issue was reopened (issues.reopened)
/// ISSUE_LABELED a label was added to an issue (issues.labeled)
/// ISSUE_CLOSED an issue was closed (issues.closed)
pub const GE_ISSUE_OPENED : u8 = 13;
pub const GE_ISSUE_REOPENED : u8 = 14;
pub const GE_ISSUE_LABELED : u8 = 15;
pub const GE_ISSUE_CLOSED : u8 = 16;
pub const APP_EVENTS_ISSUES : u8 = 17;

/// The webhook as the supervisor hands it over: "<X-GitHub-Event>.<action>". Entry n of EVENT_PAIRS
/// is GE code EVENT_CODES[n]. A reopened pull request is an opened one; a suspended installation is
/// an uninstalled one until it is unsuspended. Anything not listed is GE_OTHER.
pub const EVENT_PAIRS : str = "installation.created,installation.unsuspend,installation.deleted,installation.suspend,installation_repositories.added,installation_repositories.removed,pull_request.opened,pull_request.reopened,pull_request.synchronize,pull_request.ready_for_review,pull_request.closed,issue_comment.created,check_run.rerequested,workflow_run.completed,merge_group.checks_requested,$";
pub const EVENT_CODES : [15]u8 = [0, 0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 8, 10, 11, 12];
pub const EVENT_PAIRS : str = "installation.created,installation.unsuspend,installation.deleted,installation.suspend,installation_repositories.added,installation_repositories.removed,pull_request.opened,pull_request.reopened,pull_request.synchronize,pull_request.ready_for_review,pull_request.closed,issue_comment.created,check_run.rerequested,workflow_run.completed,merge_group.checks_requested,issues.opened,issues.reopened,issues.labeled,issues.closed,$";
pub const EVENT_CODES : [19]u8 = [0, 0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16];

/// What the Queen does about it.
/// REGISTER remember the installation's repositories (they may now be served)
Expand Down Expand Up @@ -283,7 +295,7 @@ pub fn is_t27_path(path: []const u8, len: usize) -> bool {
/// tools/bees/manifest.json subscribes only pull_request and issue_comment: a workflow_run webhook from
/// every CI run of every installed repository, read by a supervisor that ignores it, is load for nothing.
pub const CHECK_NAME : str = "t27-bees review$";
pub const APP_SUBSCRIBED_EVENTS : str = "pull_request,issue_comment,check_run,workflow_run,merge_group,$";
pub const APP_SUBSCRIBED_EVENTS : str = "pull_request,issue_comment,check_run,workflow_run,merge_group,issues,$";
pub const APP_PERMISSIONS : str = "pull_requests:write,issues:write,checks:write,actions:read,contents:read,metadata:read,$";

/// What the Queen does about a CI/CD event.
Expand Down Expand Up @@ -320,6 +332,27 @@ pub fn ci_reaction(event: u8, from_a_bot: bool, served: bool, paused: bool, ci_f
return CR_IGNORE;
}

/// What the Queen does about an issue event (#10399).
/// DISPATCH the issue is work to hand out: put it in front of the dispatcher now, not on the next poll
/// DROP the issue was closed: forget a dispatch that is still pending for it
/// IGNORE nothing to do
pub const DR_IGNORE : u8 = 0;
pub const DR_DISPATCH : u8 = 1;
pub const DR_DROP : u8 = 2;

/// The reaction to an issue event; `from_a_bot` and `served` mean what they mean for app_reaction. A
/// bot's issue or label is never dispatched (the swarm files and labels issues itself, and answering
/// that would loop), and a repository nobody installed the app on is never read. Opened, reopened and
/// labeled are one answer because a dispatch is keyed by the issue, not by the event that raised it:
/// asking for the same issue twice is the same dispatch. Any event that is not an issue event is IGNORE.
pub fn dispatch_reaction(event: u8, from_a_bot: bool, served: bool) -> u8 {
if (from_a_bot) { return DR_IGNORE; }
if (served == false) { return DR_IGNORE; }
if (event == GE_ISSUE_CLOSED) { return DR_DROP; }
if (event == GE_ISSUE_OPENED or event == GE_ISSUE_REOPENED or event == GE_ISSUE_LABELED) { return DR_DISPATCH; }
return DR_IGNORE;
}

/// The gate. What is checked decides it; what is opined never does. A changed .t27 file the compiler
/// calls broken (compiler_verdict), or a spec whose tests passed without one runtime assert (a vacuous
/// pass, read from the CI run's t27c test-report artifact, not run here), fails the check. The model's
Expand Down Expand Up @@ -544,6 +577,66 @@ test a_failure_red_on_the_base_is_not_the_pull_requests {
assert triage_owner(false) == TO_PULL_REQUEST;
}

test the_issue_events_are_read_by_their_event_and_action {
assert app_event_of("issues.opened", 13, false) == GE_ISSUE_OPENED;
assert app_event_of("issues.reopened", 15, false) == GE_ISSUE_REOPENED;
assert app_event_of("issues.labeled", 14, false) == GE_ISSUE_LABELED;
assert app_event_of("issues.closed", 13, false) == GE_ISSUE_CLOSED;
// the actions the Queen does not act on are other, and so is the pull request pair of the same name
assert app_event_of("issues.edited", 13, false) == GE_OTHER;
assert app_event_of("issues.assigned", 15, false) == GE_OTHER;
assert app_event_of("issues.open", 11, false) == GE_OTHER;
assert app_event_of("issues.closedx", 14, false) == GE_OTHER;
}

test an_issue_is_dispatched_on_the_webhook_and_a_closed_one_is_dropped {
assert dispatch_reaction(GE_ISSUE_OPENED, false, true) == DR_DISPATCH;
assert dispatch_reaction(GE_ISSUE_REOPENED, false, true) == DR_DISPATCH;
assert dispatch_reaction(GE_ISSUE_LABELED, false, true) == DR_DISPATCH;
assert dispatch_reaction(GE_ISSUE_CLOSED, false, true) == DR_DROP;
}

test a_bot_issue_or_a_stranger_repo_is_never_dispatched {
// negative control: a bot sender is ignored for every issue event, closing included
assert dispatch_reaction(GE_ISSUE_OPENED, true, true) == DR_IGNORE;
assert dispatch_reaction(GE_ISSUE_REOPENED, true, true) == DR_IGNORE;
assert dispatch_reaction(GE_ISSUE_LABELED, true, true) == DR_IGNORE;
assert dispatch_reaction(GE_ISSUE_CLOSED, true, true) == DR_IGNORE;
// a repository nobody installed the app on is never read
assert dispatch_reaction(GE_ISSUE_OPENED, false, false) == DR_IGNORE;
assert dispatch_reaction(GE_ISSUE_CLOSED, false, false) == DR_IGNORE;
// events that are not issue events have no dispatch
assert dispatch_reaction(GE_PR_OPENED, false, true) == DR_IGNORE;
assert dispatch_reaction(GE_COMMENT, false, true) == DR_IGNORE;
assert dispatch_reaction(GE_OTHER, false, true) == DR_IGNORE;
assert dispatch_reaction(APP_EVENTS_ISSUES, false, true) == DR_IGNORE;
}

test a_supervisor_from_before_issues_ignores_the_issue_events {
assert app_reaction(GE_ISSUE_OPENED, false, true, false, false, 5) == AR_IGNORE;
assert app_reaction(GE_ISSUE_REOPENED, false, true, false, false, 5) == AR_IGNORE;
assert app_reaction(GE_ISSUE_LABELED, false, true, false, false, 5) == AR_IGNORE;
assert app_reaction(GE_ISSUE_CLOSED, false, true, false, false, 5) == AR_IGNORE;
assert ci_reaction(GE_ISSUE_OPENED, false, true, false, true, 5) == CR_IGNORE;
assert ci_reaction(GE_ISSUE_CLOSED, false, true, false, true, 5) == CR_IGNORE;
}

test an_old_webhook_keeps_its_code_after_the_issue_pairs_are_appended {
assert app_event_of("installation.created", 20, false) == GE_INSTALLED;
assert app_event_of("pull_request.closed", 19, false) == GE_PR_CLOSED;
assert app_event_of("merge_group.checks_requested", 28, false) == GE_MERGE_GROUP;
assert app_event_of("issue_comment.created", 21, true) == GE_COMMENT;
assert app_event_of("issue_comment.created", 21, false) == GE_OTHER;
}

invariant the_issue_codes_follow_the_ci_codes {
assert GE_ISSUE_OPENED == APP_EVENTS_CI and GE_ISSUE_REOPENED == 14 and GE_ISSUE_LABELED == 15;
assert GE_ISSUE_CLOSED == 16 and APP_EVENTS_ISSUES == 17;
assert EVENT_CODES[15] == GE_ISSUE_OPENED and EVENT_CODES[18] == GE_ISSUE_CLOSED;
assert EVENT_CODES[14] == GE_MERGE_GROUP and EVENT_CODES[0] == GE_INSTALLED;
assert DR_IGNORE == 0 and DR_DISPATCH == 1 and DR_DROP == 2;
}

invariant the_ci_codes_never_move_either {
assert GE_CHECK_RERUN == APP_EVENTS and GE_CI_DONE == 11 and GE_MERGE_GROUP == 12;
assert APP_EVENTS_CI == 13;
Expand Down
2 changes: 1 addition & 1 deletion tools/bees/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ SHA and **only** the bot's label.

| File | What it is |
|------|------------|
| `manifest.json` | The app, as GitHub's manifest flow reads it: public since 2026-10-08, webhook to the supervisor (`https://api.t27.ai/queen/app/webhook`, BrowserOS#543) for `pull_request` and `issue_comment`, five permissions (pull requests write, issues write for labels, contents/checks/metadata read). The CI/CD gate's events and permissions are in `specs/queen/app.t27` (APP_SUBSCRIBED_EVENTS, APP_PERMISSIONS) and join this file when the supervisor acts on them (#7834). |
| `manifest.json` | The app, as GitHub's manifest flow reads it: public since 2026-10-08, webhook to the supervisor (`https://api.t27.ai/queen/app/webhook`, BrowserOS#543) for `pull_request`, `issue_comment` and `issues` (a new, reopened, relabeled or closed issue is a dispatch reaction, `dispatch_reaction` in `specs/queen/app.t27`, #10399; the App's event subscription in its GitHub settings is separate from this file), five permissions (pull requests write, issues write for labels, contents/checks/metadata read). The CI/CD gate's events and permissions are in `specs/queen/app.t27` (APP_SUBSCRIBED_EVENTS, APP_PERMISSIONS) and join this file when the supervisor acts on them (#7834). |
| `bee-app` | Owner, once: `create` registers the app from the manifest and saves the key locally; `convert CODE` is the manual fallback. |
| `bee-token` | A bee: prints a one-hour installation token scoped to one repository. |
| `bees.py` | All of the above; `python3 tools/bees/bees.py self-test` needs no network and no real secret. |
Expand Down
3 changes: 2 additions & 1 deletion tools/bees/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"default_events": [
"pull_request",
"issue_comment"
"issue_comment",
"issues"
]
}
Loading