Compare commits

...
Author SHA1 Message Date
jason.woltje 635c10924d publish: a retry after publish-timeout pushes once; the bus holds the decision until its push ends and records a late push (row 242, #1748) 2026-10-20 22:43:57 -05:00
jason.woltje 8fede5fc6a sessions: Sage, row 210 landed f5753c0e8, queue revs 2604-2617 2026-10-20 21:51:47 -05:00
jason.woltje c084d8d423 queue: revs 2604-2617 (row 210 done, landed f5753c0e8; rows 241 and 242 added and briefed, 241 claimed by Megavolt; row 234 briefed)
Sage: rows 241 and 242 added and briefed (#1747, #1748), the row 210 round 3 request as jarvis, the row 210 landing note and done move, row 234 queued to briefed. Gizmoduck: row 210 r5 start and in-review round 3 (2607-2611). Duckworth: row 210 round 3 record, approve (2614). Megavolt: row 241 claim (2606).
2026-10-20 21:50:59 -05:00
6 changed files with 700 additions and 17 deletions

No files matched your search

+105
View File
@@ -0,0 +1,105 @@
# Row 242 build: a retry after publish-timeout pushes once
Row 242, #1748, P19 440. Brief: docs/plans/2026-10-20_agent-credentials.md, "Brief: row 237
follow-up N1, a retry after publish-timeout pushes once" (9d165be8c). Base: origin/refactor
8fede5fc6 (row 210 landed f5753c0e8).
## The defect
The git socket polls publishResult until publishDeadlineMs, then refuses publish-timeout
(packages/cli/src/git-socket.mjs:433) while the bus process's push (runtime publishPush) can
still be running. The socket then calls publishRefused, which went straight to
broker.publishRefused: it filed the refusal and deleted the ticket. checkPublish no longer saw an
open ticket on the decision, so a retry got a new ticket and pushed a second ref while the first
push was still on its way to the fork.
## Fix chosen: hold the decision until the push ends, then record a push that reached the fork
packages/bus/src/runtime.mjs, `publishRefused` and `settle`:
- If the bus has no push for the ticket, publishRefused goes to the broker as before.
- If the push is still running, the refusal code is held on the entry and the ticket stays open.
checkPublish refuses another publish on the decision with publish-busy (row 237's check),
the same as when the socket is still polling.
- When the push ends, `settle` runs:
- A push that reached the fork is recorded with broker.recordPublish, which spends the
decision. The bus logs `pushed after the git socket refused <code>; recorded`.
- A push that failed is filed as publishRefused with the socket's code.
- If recordPublish refuses with a BusError, that code is filed instead.
- If the push has already ended when the refusal arrives, it's settled at once. The sage-1440 test
relies on this: its push fails fast, so publish-timeout is filed once, immediately, as before.
Why hold rather than kill. Killing the git client doesn't undo a push the fork has already
received: receive-pack can update the ref after the client dies. After a kill the bus would have
to ls-remote the fork to learn what landed, and the result could still race the server. Holding
waits for the push's own outcome. Each git run in publishRemote already has its own limit
(publishRemote defaults to checkout.mjs GIT_LIMITS), so a hold is bounded. Holding also reuses the existing publish-busy
state, and the refusal text is unchanged.
Why record and not only hold. If the hold just filed publish-timeout once the push ended, the
ticket would close and the decision would be free again with a ref on the fork. A retry would then
push a second ref. Recording the late push spends the decision, so the retry gets
decision-consumed.
The publish-timeout refusal text is unchanged (brief condition).
## Test
packages/cli/tests/publish-socket.test.mjs:302, "row 242: a retry after publish-timeout refuses
publish-busy while the bus process's push runs; the late push is recorded and the decision pushes
once".
- publishDeadlineMs 150 and a store token. The fork's pre-receive hook writes `held`, then loops
until the test writes `release`.
- The first publish gives publish-timeout. The test waits for `held`.
- A retry to branch `other` gives publish-busy (:322). Refused events: ["publish-busy"]. No
git.push.remote allowed event.
- The test writes `release` and waits until there is exactly one allowed event, with ref
refs/heads/coder/feat and the work commit.
- The next retry gives decision-consumed (:330).
- Exactly one git-receive-pack request reached the fake Gitea, and the fork has only
refs/heads/coder/feat. Refused events: ["publish-busy", "decision-consumed"]. No TOKEN appears in
events or logs.
Fail fast (sage-1681): the body is wrapped in try/finally, and the finally writes `release`. Under
the first mutant run, the assert at the retry failed and the hook was never released. setup's
teardown waits for the fake Gitea, which waited for the held push, so the file hung until I stopped
it. Now a failed assert ends the hook and the run. Each wait is bounded at 10 s with an assert.fail.
## Mutants
Script: /mnt/storage/scratch/megavolt-r242/mutants.sh. Each mutant: save, one-line sed, run
publish-socket.test.mjs whole, restore, cmp.
| Mutant | Change | Expected kill |
|---|---|---|
| M1 | export `publishRefused: (ticket, args) => broker.publishRefused(ticket, args)` again (removes the fix) | :302 at :322: the retry gets a new ticket and its own push, so publish-timeout, not publish-busy (seen in the stopped 03:06Z run) |
| M2 | `settle` files publishRefused without trying recordPublish (hold only) | :302 at the wait for one allowed event, then :330 |
Results (Sage's slot, sage-1686, 2026-10-21T03:35:00Z to 03:35:15Z): both killed, both restored
(cmp ok).
- M1: publish-socket 8/1, the row 242 test fails at :322 in 0.5 s.
- M2: publish-socket 8/1, the row 242 test fails at :327 (the bounded wait at :311 for one allowed
event) in 10.3 s.
## Suites
On Sage's slot (sage-1686), on base 8fede5fc6 plus this row:
- bus `node --test tests/*.test.mjs`: 499/499, 03:35:20Z to 03:35:34Z. This is one fewer than
Sage's 241 gate (500) because the base predates row 241's land.test case.
- cli `node --test tests/*.test.mjs`: 566/566, 03:35:34Z to 03:43:47Z. That's 565 plus the
row 242 test.
Singles before the slot: publish-socket.test.mjs 9/9, bus publish.test.mjs 13/13. The seat suite
and scripts/test-*.sh are left for Sage's landing gate.
## Residuals
- The agent was told publish-timeout, but the push is recorded later and the run isn't
notified. The allowed event and the bus log line are the only trace. A later publish on the
decision gets decision-consumed, which tells the agent the decision was used. Whether the run
should get a RESULT for a late record is a follow-up question for Sage.
- The hold lives in the bus process's memory, like the broker's open tickets. A bus restart during
a hold drops both, the same as today for any publish in flight.
+1
View File
@@ -1393,3 +1393,4 @@ are never rewritten or removed; corrections are new entries.
2026-10-21T01:28:05Z | Darkwing (Opus 5.5, 7525c8e4) | row 232 I1 build (sage-1558, sage-1561 ruling a) | rebased onto cb5fdb064 (row 200), gate green and 17/17 mutants killed on Sage's slot; in review round 1, manifest 7a3b4ea1 at 84f1a91e6 on darkwing/row232-r1, comment 29825, revs 2595-2596; Filbert woken
2026-10-21T01:34:13Z | Filbert (Opus 5.5, 048f3c63) | row 232 installer I1 review r1 (#1734, manifest 7a3b4ea133cc) | APPROVE, Gitea 29829, queue rev 2600 (left for Sage's queue-commit); static review, gate and mutants are Darkwing's receipts; 3 non-blocking notes
2026-10-21T01:49:55Z | Sage (Opus 5.5, 1ef1e4f8) | row 237 landed d7d943199 (#1743, gate green, M1 killed); PRD 1.7+1.8 landed 0adf8ddfe (Honker 2884ee496, Duckworth approve bf52628f9, tasks 97/0 bus 478/0 on the tip, DECISIONS Q192); rows 239 (#1745) and 240 (#1746) added; 233 briefed; queue revs 2540-2603 committed 584e2bd82 | landed, push next
2026-10-21T02:51:46Z | Sage (Opus 5.5, 1ef1e4f8) | row 210 landing (r5 0485384e9, Duckworth r3 29851) | landed f5753c0e8, patch-id 723b1c83 = r5 diff, manifest 688d38b9 OK; gate 2796/0 packages, 429/0 scripts; mutants 52/53 (M22b equivalent); #1707 closed (29853), Vikunja 424 comment 429; queue revs 2604-2617 c084d8d42; row 234 briefed; Megavolt has the slot for 241 (sage-1668)
+5 -3
View File
@@ -33,7 +33,7 @@ Gaps found while working go to `docs/plans/DEFERRED.md`, not here.
<!-- mosaic-queue:begin -->
Generated from `docs/plans/queue.json` revision 2603 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
Generated from `docs/plans/queue.json` revision 2617 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
| # | Piece | Owner | Issues | State | After | Gate | Brief | Note |
|---|---|---|---|---|---|---|---|---|
@@ -246,7 +246,7 @@ Generated from `docs/plans/queue.json` revision 2603 by `queue render`. Do not e
| 207 | 193c: mosaic access user verbs and --level viewer (#1691) | morgana; reviewers duckworth | #1705 | queued | 202 done | Duckworth approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193c, mosaic access user verbs and --level viewer | — |
| 208 | 193d: Console shared list, share controls and viewer read-only (#1691; also after FED R6 when filed) | morgana; reviewers duckworth | #1706 | queued | 192 done, 206 done | Duckworth approves the manifest; webui suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193d, the Console's shared list, share controls and viewer read-only | — |
| 209 | FED R8: the hub window on 10.1.1.54, a recorded acceptance (#414, r4.11 b93f098f0; live window run by Jason and Mos, after the Case 2 install #416, Bean and Chet live and both Hermes gateways stopped) | sage; reviewers duckworth | — | queued | — | Every entry check's output is in the R8 record with who ran it; Jason signs in to the hub and sends one message, Melanie revokes, his next request is refused; Duckworth checks the record (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R8, the hub window on 10.1.1.54 | Stays queued until its entry gates near: Case 2 install on .54 (#416, by 2026-10-27), Bean and Chet live, both Hermes gateways on .54 stopped, R7, #412 phase 2, #419 answer. Filed per mos-1073 after duckworth-0034 (Sage) |
| 210 | Agent host access with sudo through the credential store: grant per agent and host, the stack does ssh and sudo, the agent never sees the key (#1707, Vikunja #424, Q183/Q184, due 10-27; chained on row 199's candidate) | gizmoduck; reviewers duckworth | #1707 | in-review, round 2, request posted | — | Duckworth approves DESIGN.md, then the manifest; bus, business, cli suites and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_host-credentials.md` § Brief: agent host access with sudo through the credential store (#424) | Sage gate 2026-10-21 on 92fac9904 (c8bc5cf4c + 3cf63efb4): mutants 52/53, M22b equivalent (no await broker.mjs:1741 to credentials.mjs:358; coverage moves to row 235, gizmoduck-0088). Gate STOP pkg-bus 497/1: B1 broker.mjs:7 imports business/src/hosts.mjs (not in RUNNER_FILES, nor errors.mjs/util.mjs); project-runtime.test.mjs:12 pin fails; seat sandbox.test.mjs 6/4 vs base 10/0. r4 needed (sage-1617). |
| 210 | Agent host access with sudo through the credential store: grant per agent and host, the stack does ssh and sudo, the agent never sees the key (#1707, Vikunja #424, Q183/Q184, due 10-27; chained on row 199's candidate) | gizmoduck; reviewers duckworth | #1707 | done | — | Duckworth approves DESIGN.md, then the manifest; bus, business, cli suites and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_host-credentials.md` § Brief: agent host access with sudo through the credential store (#424) | Landed f5753c0e8 on refactor (base 9d165be8c), pushed. Patch-id 723b1c83 matches git diff ad95c888c..0485384e9 on the 23 manifest paths; landed files pass r5 manifest 688d38b9. Duckworth approve r2 rev 2592, r3 29851. Mutants 52/53, M22b equivalent (rev 2584). Gate on ad95c888c+patch: packages 2796/0, scripts 429/0; landed blobs equal the gate tree. |
| 211 | Force-push guard: scripts/git-hooks/pre-push refuses non-fast-forward and delete pushes from every worktree, and scripts/slot-busy.sh exits 1 on BUSY (#1709, Vikunja #426, mos-1078/1083) | rocko; reviewers dewey | #1709 | briefed | — | Dewey approves the manifest; the new or changed scripts/test-*.sh and every other scripts/test-*.sh green on Sage's gate; the mutants killed; hook installed in the canonical checkout with a scripted refused force-push (sage) | `docs/plans/2026-10-20_force-push-guard.md` § Brief: a pre-push hook that refuses force-pushes, and a busy check that fails on BUSY (#426) | — |
| 212 | Case 2 C2-01 packages/library: mapping, scope, reader, resolver, R-rules, renderItems, survey | gosalyn; reviewers webby | #1713 | in-review, round 1, request posted | — | Webby approves the manifest. The library tests and the cli suite are green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01 packages/library: mapping, scope, reader, resolver, R-rules, renderItems, survey | — |
| 213 | Case 2 C2-08 creative roles, persona file and block, M1, M2, M6, M10, prompt passed by file | morgana; reviewers webby | #1714 | in-review, round 2, request posted | — | Webby approves the manifest. The business, bus and harness suites, `scripts/test-config.sh` and every `scripts/test-*.sh` are green on Sage's gate, and the `models` mutant is killed. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-08 creative roles, persona file and block, M1, M2, M6, M10, prompt passed by file | — |
@@ -270,13 +270,15 @@ Generated from `docs/plans/queue.json` revision 2603 by `queue render`. Do not e
| 231 | Case 2 C2-01b survey escapes, list item lines and attachment embeds (row 212 N1-N3) | gosalyn; reviewers webby | #1733 | briefed | 212 done | Webby approves the manifest. The library tests are green on Sage's gate, along with every scripts/test-*.sh. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01b survey escapes, list item lines and attachment embeds | — |
| 232 | Installer I1: stack-release external units and stack-copy bundle copies (MVP, Q187, Vikunja #432) | darkwing; reviewers filbert | #1734 | in-review, round 1, request posted | — | Filbert approves the manifest. The cli suite and every scripts/test-*.sh are green on Sage's gate, with the mutants killed. (sage) | `docs/plans/2026-10-20_installer-rows.md` § I1 stack-release external units and bundle copies | — |
| 233 | Installer I3: stack-upgrade.sh root driver and the ops-01 host run sheet (MVP, Q187, Vikunja #432) | darkwing; reviewers filbert | #1735 | briefed | 232 done | Filbert approves the manifest. The test file, the cli suite and every scripts/test-*.sh are green on Sage's gate, with the mutants killed. (sage) | `docs/plans/2026-10-20_installer-rows.md` § I3 the upgrade command and the host run sheet | — |
| 234 | Machine grant row a: host enrollment, store records and migration 3i-3j (#436a, design r6 f9039682e, lands by 10-25 before the .53/.54 installs) | gizmoduck; reviewers duckworth | #1740 | queued | 210 done | Duckworth approves the manifest. The bus, business and cli suites and every scripts/test-*.sh are green on Sage's gate. The design's section 10 row a tests and mutants pass, the migration test included. (sage) (sage) | `docs/plans/2026-10-20_host-credentials.md` § Machine grant row a: enrollment, store records and migration 3i-3j | — |
| 234 | Machine grant row a: host enrollment, store records and migration 3i-3j (#436a, design r6 f9039682e, lands by 10-25 before the .53/.54 installs) | gizmoduck; reviewers duckworth | #1740 | briefed | 210 done | Duckworth approves the manifest. The bus, business and cli suites and every scripts/test-*.sh are green on Sage's gate. The design's section 10 row a tests and mutants pass, the migration test included. (sage) (sage) | `docs/plans/2026-10-20_host-credentials.md` § Machine grant row a: enrollment, store records and migration 3i-3j | — |
| 235 | Machine grant row b: raise binding, executor, reconcile and revoke (#436, design r6 f9039682e, C1, N1, G27, G28) | gizmoduck; reviewers duckworth | #1741 | queued | 234 done | Duckworth approves the manifest. The bus, business and cli suites and every scripts/test-*.sh are green on Sage's gate. Section 10's row b tests and mutants pass, G27, G28 and the upsert mutant included. (sage) (sage) | `docs/plans/2026-10-20_host-credentials.md` § Machine grant row b: raise binding, executor, reconcile and revoke | — |
| 236 | #436b Gitea grant path build: seat accounts and tokens through an approved grant, schema 3j-3k (design r5 1be535aca, due 10-27 at risk; live waits for Jason's section 8 approval and bootstrap step) | megavolt; reviewers dewey | #1742 | queued | 210 done, 234 done | Dewey approves the manifest. The bus, business and cli suites and every scripts/test-*.sh are green on Sage's gate. Section 9's mutants are killed, N9's restart test included. (sage) (sage) | `docs/plans/2026-10-20_agent-credentials.md` § Brief: #436b Gitea grant path, build (schema 3j-3k) | — |
| 237 | Row 199 follow-up N1: one decision publishes once (publish-busy or #spent recheck) | megavolt; reviewers duckworth | #1743 | done | 199 done | Duckworth approves the manifest. The bus suite and every scripts/test-*.sh are green on Sage's gate, and the mutant is killed. (sage) (sage) | `docs/plans/2026-10-20_agent-credentials.md` § Brief: row 199 follow-up N1, one decision publishes once | Landed d7d943199 on refactor (local, push pending), patch-id c5f4beb4 = e42415935, manifest bb777cb0 blobs checked. Sage gate 01:31-01:44Z on 816d48b03+4 paths: M1 killed (bus 1/13, cli 1/8), every package suite and scripts/test-*.sh green (bus 478, cli 564). |
| 238 | row 200 follow-up N1: the sso-config hint carries --instance NAME (#1744) | launchpad; reviewers webby | #1744 | in-review, round 1, request posted | 200 done | Webby approves the manifest; stack-release.test.mjs whole, cli suite and every scripts/test-*.sh green on Sage's gate; the mutant killed (sage) | `docs/plans/2026-10-20_stack-release-sso.md` § Brief: row 200 follow-up N1, the sso-config hint names the instance | — |
| 239 | Row 194 follow-up: the credential revoke runs under the writer's lock, refusals after a revoke say so, and a stale-revision fire test (duckworth-0074, mos-1158) | honker; reviewers duckworth | #1745 | queued | 194 done | Duckworth approves the manifest. The bus, business, cli and webui suites and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) (sage) | `docs/plans/2026-10-20_agent-lifecycle.md` § Brief: row 194 follow-up, the revoke under the writer's lock and a fire test | — |
| 240 | Installer I4: hostcheck, the bwrap profile check, pins and boot unit, from r6 B2 (MVP, Q187, Vikunja #432; split from I3) | darkwing; reviewers filbert | #1746 | queued | 233 done | Filbert approves the manifest. The test file, the cli suite and every scripts/test-*.sh are green on Sage's gate, with the mutants killed. (sage) (sage) | `docs/plans/2026-10-20_installer-rows.md` § I4 hostcheck: the bwrap profile check, pins and boot unit | — |
| 241 | Row 237 follow-up: one decision lands once, a two-ticket broker land test and three mutants on the landing guards (megavolt-0069, sage-1651, P19 441) | megavolt; reviewers duckworth | #1747 | in-progress | 237 done | Duckworth approves the manifest. The bus, seat and cli suites and every scripts/test-*.sh are green on Sage's gate, and the three mutants are killed. (sage) (sage) | `docs/plans/2026-10-20_agent-credentials.md` § Brief: row 237 follow-up, one decision lands once, with tests at the broker | — |
| 242 | Row 237 follow-up N1: a retry after publish-timeout pushes once; hold the decision or kill and wait for the push, a slow-push retry test and a mutant (duckworth-0069, P19 440) | megavolt; reviewers duckworth | #1748 | briefed | 237 done | Duckworth approves the manifest. The bus and cli suites and every scripts/test-*.sh are green on Sage's gate, and the mutant is killed. (sage) (sage) | `docs/plans/2026-10-20_agent-credentials.md` § Brief: row 237 follow-up N1, a retry after publish-timeout pushes once | — |
<!-- mosaic-queue:end -->
+515 -11
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"canonicalRoot": "/mnt/storage/src/mosaic-stack",
"revision": 2603,
"revision": 2617,
"rows": [
{
"id": 1,
@@ -21383,7 +21383,7 @@
"closes": [
1707
],
"state": "in-review",
"state": "done",
"previousState": null,
"required": false,
"requiredSince": null,
@@ -21507,18 +21507,69 @@
"candidate": "0ed4d5350a163003b0a0662e83e99f7e7f75ab000f31035159e676d5577bb3ef"
}
]
},
{
"n": 3,
"op": "gizmoduck-210-inreview-3",
"by": "gizmoduck",
"at": "2026-10-21T01:55:49.221Z",
"issue": 1707,
"candidate": {
"kind": "manifest",
"digest": "688d38b96b3de80f7bbbca857130ef36f68ec65009a3df51c5084aef7cb96788",
"text": "8fa8a321933e75315babe21f49ba434a61dbeffbfcfded6933ebfaf6c73d3aee packages/business/src/business.mjs\ne72f0898b5f373c5a76d4c41fbe504904f55e18b183768260a6423510625683e packages/business/src/credentials.mjs\n6339bf85c946a41a5cbe8e94d51b264e298a9d16900e5dfe46b01b803e76ca41 packages/business/src/hosts.mjs\nab5f7dc38a2ca5978b02d257068f4f82706db847cf0d6400f7cd548f8f6abe43 packages/business/src/resolve.mjs\n636916b9868b3a3e7f962894cbfa1e91629b5713a568f55f4716f942dbcd4910 packages/business/src/role.mjs\n77c432e879f8611a077feb2fb7c9b67e826ade558e41dcf01433fc2cdab2e0fa packages/business/src/vocabulary.mjs\n6a2c9b371fc9236bb0730ed108cbd770e9487b8ba733dbebccd11891ccba6f17 packages/business/tests/hard-waits.test.mjs\n9ef8b8be3c290f072e7c943b176a1ec688fda03fab4ba114d31213caf2216e42 packages/business/tests/hosts.test.mjs\n610af0c4207bbd805a2a8826daa32b8cdb423937f793c32fd4241f6addfabfda packages/bus/src/broker.mjs\nff927da3cf22fbacc9ace77b2e8b803bffc2953fb58cf7ab4e5f6f6493732996 packages/bus/src/business.mjs\n27d2ae155a2919788d7ad02da10a7056c23f5b71228e9af406b1b8aa0de55efc packages/bus/src/credentials.mjs\n86b4321dd436b97e389101873197dbab95b3d2202e5ef7da712c4978f4f9159f packages/bus/src/credential-verbs.mjs\n5d227edec1d33cb58607fae4e43784a0decc8aaf55cad6bc4318cec4eda6fbea packages/bus/src/host-run.mjs\n3721190045a4f114d67c9c7d32a448cf3d5dbbc6d4554dbc348e9f6fd428b5dc packages/bus/src/runtime.mjs\n3ef86e4f0b77cc513bdd58ec7eb984ceb02af6625f39d1be5254f2c0b0e99734 packages/bus/src/server.mjs\n58e45b7a0d0118be8827bd71d1a427ff84d0ecac46c22adece36dec36e6d521d packages/bus/tests/hard-waits.test.mjs\n28ad8ce2a29722768820ad916273f2100eb79281255c6a3689964cd24564237c packages/bus/tests/host-run.test.mjs\nbe4e1eb88b689e8e4f859de75218af151feae8dce2c1409b726cb9c3dcd5ffe9 packages/bus/tests/spec-fixtures.mjs\n19755f7bb002d8455990c5dc7c458e64aeb95252ec7231a2ed2fd9c638fcb511 packages/cli/src/credentials.mjs\n152a3e57f7241db45bf8262fbf86ee029cf3b98f0d75503990abd46ac151a4b2 packages/cli/tests/credentials.test.mjs\nbdf88833cdb0c5ef76acec50c3c983b55c6053987a5c427cbc400ebd5f3c7110 packages/bus/tests/project-runtime.test.mjs\ne222a5fc3c117f62dbc854ef7397f7b5589e6f5d52e45398db0deeb43c53a9d5 roles/chief-of-staff.md\nd1016f8c34ee04601ab751c7514a20dfbeec0359d46dba8f94c44538dcc16133 roles/pm.md\n"
},
"request": "comment",
"attempts": [
{
"op": "gizmoduck-210-inreview-3",
"by": "gizmoduck",
"at": "2026-10-21T01:55:49.221Z",
"state": "failed",
"comment": null,
"transport": {
"outcome": "failed",
"status": null,
"comment": null,
"detail": "pre-send: the credential or account check failed"
},
"resolutions": []
},
{
"op": "sage-210-request-3",
"by": "sage",
"at": "2026-10-21T01:57:09.243Z",
"state": "posted",
"comment": 29849,
"transport": {
"outcome": "posted",
"status": 201,
"comment": 29849,
"detail": "created"
},
"resolutions": []
}
],
"duplicateRisk": false,
"receipts": [
{
"reviewer": "duckworth",
"op": "duckworth-r210-record-3",
"at": "2026-10-21T02:00:33.347Z",
"verdict": "approve",
"comment": 29851,
"candidate": "688d38b96b3de80f7bbbca857130ef36f68ec65009a3df51c5084aef7cb96788"
}
]
}
]
},
"claim": {
"seat": "gizmoduck",
"op": "gizmoduck-210-start"
},
"note": "Sage gate 2026-10-21 on 92fac9904 (c8bc5cf4c + 3cf63efb4): mutants 52/53, M22b equivalent (no await broker.mjs:1741 to credentials.mjs:358; coverage moves to row 235, gizmoduck-0088). Gate STOP pkg-bus 497/1: B1 broker.mjs:7 imports business/src/hosts.mjs (not in RUNNER_FILES, nor errors.mjs/util.mjs); project-runtime.test.mjs:12 pin fails; seat sandbox.test.mjs 6/4 vs base 10/0. r4 needed (sage-1617).",
"claim": null,
"note": "Landed f5753c0e8 on refactor (base 9d165be8c), pushed. Patch-id 723b1c83 matches git diff ad95c888c..0485384e9 on the 23 manifest paths; landed files pass r5 manifest 688d38b9. Duckworth approve r2 rev 2592, r3 29851. Mutants 52/53, M22b equivalent (rev 2584). Gate on ad95c888c+patch: packages 2796/0, scripts 429/0; landed blobs equal the gate tree.",
"blockedReason": null,
"createdAt": "2026-10-20T20:13:12.208Z",
"updatedAt": "2026-10-21T01:20:56.136Z",
"updatedBy": "duckworth"
"updatedAt": "2026-10-21T02:49:02.158Z",
"updatedBy": "sage"
},
{
"id": 211,
@@ -22697,7 +22748,7 @@
"closes": [
1740
],
"state": "queued",
"state": "briefed",
"previousState": null,
"required": false,
"requiredSince": null,
@@ -22722,7 +22773,7 @@
"note": null,
"blockedReason": null,
"createdAt": "2026-10-20T23:24:50.778Z",
"updatedAt": "2026-10-20T23:42:21.559Z",
"updatedAt": "2026-10-21T02:49:40.096Z",
"updatedBy": "sage"
},
{
@@ -23073,6 +23124,85 @@
"createdAt": "2026-10-21T01:39:04.213Z",
"updatedAt": "2026-10-21T01:39:04.213Z",
"updatedBy": "sage"
},
{
"id": 241,
"piece": "Row 237 follow-up: one decision lands once, a two-ticket broker land test and three mutants on the landing guards (megavolt-0069, sage-1651, P19 441)",
"owner": "megavolt",
"issues": [
1747
],
"closes": [
1747
],
"state": "in-progress",
"previousState": null,
"required": false,
"requiredSince": null,
"gate": "Duckworth approves the manifest. The bus, seat and cli suites and every scripts/test-*.sh are green on Sage's gate, and the three mutants are killed. (sage)",
"gateOwner": "sage",
"brief": {
"path": "docs/plans/2026-10-20_agent-credentials.md",
"anchor": "Brief: row 237 follow-up, one decision lands once, with tests at the broker",
"blob": "f32ce3e00a4fd21a8297eb48ef296e7eb5d44a66"
},
"after": [
{
"id": 237,
"when": "done"
}
],
"reviewers": [
"duckworth"
],
"review": null,
"claim": {
"seat": "megavolt",
"op": "megavolt-241-start-1"
},
"note": null,
"blockedReason": null,
"createdAt": "2026-10-21T01:52:55.772Z",
"updatedAt": "2026-10-21T01:54:02.058Z",
"updatedBy": "megavolt"
},
{
"id": 242,
"piece": "Row 237 follow-up N1: a retry after publish-timeout pushes once; hold the decision or kill and wait for the push, a slow-push retry test and a mutant (duckworth-0069, P19 440)",
"owner": "megavolt",
"issues": [
1748
],
"closes": [
1748
],
"state": "briefed",
"previousState": null,
"required": false,
"requiredSince": null,
"gate": "Duckworth approves the manifest. The bus and cli suites and every scripts/test-*.sh are green on Sage's gate, and the mutant is killed. (sage)",
"gateOwner": "sage",
"brief": {
"path": "docs/plans/2026-10-20_agent-credentials.md",
"anchor": "Brief: row 237 follow-up N1, a retry after publish-timeout pushes once",
"blob": "9259ba2a190601af975401f628a8a965bd9bc161"
},
"after": [
{
"id": 237,
"when": "done"
}
],
"reviewers": [
"duckworth"
],
"review": null,
"claim": null,
"note": null,
"blockedReason": null,
"createdAt": "2026-10-21T01:54:53.103Z",
"updatedAt": "2026-10-21T01:55:17.835Z",
"updatedBy": "sage"
}
],
"log": [
@@ -92108,6 +92238,380 @@
"receipt": "ok sage-237-done rev 2603 row 237 in-review→done round 1"
},
"viewSha": "6d9823b5944aef42d6c6e841b4a402e464217a61a061339b91c3422197173e34"
},
{
"rev": 2604,
"op": "sage-1747-add",
"verb": "add",
"args": {
"piece": "Row 237 follow-up: one decision lands once, a two-ticket broker land test and three mutants on the landing guards (megavolt-0069, sage-1651, P19 441)",
"gate": "Duckworth approves the manifest. The bus, seat and cli suites and every scripts/test-*.sh are green on Sage's gate, and the three mutants are killed. (sage)",
"brief": "docs/plans/2026-10-20_agent-credentials.md#Brief: row 237 follow-up, one decision lands once, with tests at the broker",
"issues": [
1747
],
"note": null,
"owner": "megavolt",
"gateOwner": "sage",
"after": [
{
"id": 237,
"when": "done"
}
],
"reviewers": [
"duckworth"
],
"required": null
},
"by": "sage",
"at": "2026-10-21T01:52:55.772Z",
"semantics": 2,
"result": {
"row": 241,
"from": null,
"to": "queued",
"brief": {
"path": "docs/plans/2026-10-20_agent-credentials.md",
"anchor": "Brief: row 237 follow-up, one decision lands once, with tests at the broker",
"blob": "f32ce3e00a4fd21a8297eb48ef296e7eb5d44a66"
},
"receipt": "ok sage-1747-add rev 2604 row 241 none→queued"
},
"viewSha": "b30f76b72408774916e633cf9deda3f75949321be467ecda32a195f720eb6939"
},
{
"rev": 2605,
"op": "sage-241-briefed",
"verb": "move",
"args": {
"id": 241,
"to": "briefed",
"reason": null,
"candidate": null,
"evidence": null,
"issue": null
},
"by": "sage",
"at": "2026-10-21T01:53:25.329Z",
"semantics": 2,
"result": {
"row": 241,
"from": "queued",
"to": "briefed",
"round": null,
"issue": null,
"candidate": null,
"receipt": "ok sage-241-briefed rev 2605 row 241 queued→briefed"
},
"viewSha": "fc9d64b957740a1af01acaffff3be9620467c3ed09b8b9205a91c881ea8bcc18"
},
{
"rev": 2606,
"op": "megavolt-241-start-1",
"verb": "move",
"args": {
"id": 241,
"to": "in-progress",
"reason": null,
"candidate": null,
"evidence": null,
"issue": null
},
"by": "megavolt",
"at": "2026-10-21T01:54:02.058Z",
"semantics": 2,
"result": {
"row": 241,
"from": "briefed",
"to": "in-progress",
"round": null,
"issue": null,
"candidate": null,
"receipt": "ok megavolt-241-start-1 rev 2606 row 241 briefed→in-progress"
},
"viewSha": "6bf0ea4954ca79ddd4ed9fda5e38fc62a0ec64138871f04c5c4d06c3a70edbb4"
},
{
"rev": 2607,
"op": "gizmoduck-210-start-5",
"verb": "move",
"args": {
"id": 210,
"to": "in-progress",
"reason": null,
"candidate": null,
"evidence": null,
"issue": null
},
"by": "gizmoduck",
"at": "2026-10-21T01:54:26.989Z",
"semantics": 2,
"result": {
"row": 210,
"from": "in-review",
"to": "in-progress",
"round": null,
"issue": null,
"candidate": null,
"receipt": "ok gizmoduck-210-start-5 rev 2607 row 210 in-review→in-progress"
},
"viewSha": "ad7f5d2b8dbf204ea6996b70aa1487c2143da84e3ecf9c77f272e61c460a1b15"
},
{
"rev": 2608,
"op": "sage-1748-add",
"verb": "add",
"args": {
"piece": "Row 237 follow-up N1: a retry after publish-timeout pushes once; hold the decision or kill and wait for the push, a slow-push retry test and a mutant (duckworth-0069, P19 440)",
"gate": "Duckworth approves the manifest. The bus and cli suites and every scripts/test-*.sh are green on Sage's gate, and the mutant is killed. (sage)",
"brief": "docs/plans/2026-10-20_agent-credentials.md#Brief: row 237 follow-up N1, a retry after publish-timeout pushes once",
"issues": [
1748
],
"note": null,
"owner": "megavolt",
"gateOwner": "sage",
"after": [
{
"id": 237,
"when": "done"
}
],
"reviewers": [
"duckworth"
],
"required": null
},
"by": "sage",
"at": "2026-10-21T01:54:53.103Z",
"semantics": 2,
"result": {
"row": 242,
"from": null,
"to": "queued",
"brief": {
"path": "docs/plans/2026-10-20_agent-credentials.md",
"anchor": "Brief: row 237 follow-up N1, a retry after publish-timeout pushes once",
"blob": "9259ba2a190601af975401f628a8a965bd9bc161"
},
"receipt": "ok sage-1748-add rev 2608 row 242 none→queued"
},
"viewSha": "15c0abe46bd3babb27205228cc7954425a120a4add3df53c9b4993db25e11ddf"
},
{
"rev": 2609,
"op": "sage-242-briefed",
"verb": "move",
"args": {
"id": 242,
"to": "briefed",
"reason": null,
"candidate": null,
"evidence": null,
"issue": null
},
"by": "sage",
"at": "2026-10-21T01:55:17.835Z",
"semantics": 2,
"result": {
"row": 242,
"from": "queued",
"to": "briefed",
"round": null,
"issue": null,
"candidate": null,
"receipt": "ok sage-242-briefed rev 2609 row 242 queued→briefed"
},
"viewSha": "7faf432c1a837367367fd4034d9320be9e1d6185960a9e00639b12c9228bf781"
},
{
"rev": 2610,
"op": "gizmoduck-210-inreview-3",
"verb": "move",
"args": {
"id": 210,
"to": "in-review",
"reason": null,
"candidate": "/mnt/storage/scratch/gizmo-210d/agents/gizmoduck/work/queue-210/candidate-manifest.sha256",
"evidence": null,
"issue": null
},
"by": "gizmoduck",
"at": "2026-10-21T01:55:49.221Z",
"semantics": 2,
"result": {
"row": 210,
"from": "in-progress",
"to": "in-review",
"round": 3,
"issue": 1707,
"candidate": {
"kind": "manifest",
"digest": "688d38b96b3de80f7bbbca857130ef36f68ec65009a3df51c5084aef7cb96788",
"text": "8fa8a321933e75315babe21f49ba434a61dbeffbfcfded6933ebfaf6c73d3aee packages/business/src/business.mjs\ne72f0898b5f373c5a76d4c41fbe504904f55e18b183768260a6423510625683e packages/business/src/credentials.mjs\n6339bf85c946a41a5cbe8e94d51b264e298a9d16900e5dfe46b01b803e76ca41 packages/business/src/hosts.mjs\nab5f7dc38a2ca5978b02d257068f4f82706db847cf0d6400f7cd548f8f6abe43 packages/business/src/resolve.mjs\n636916b9868b3a3e7f962894cbfa1e91629b5713a568f55f4716f942dbcd4910 packages/business/src/role.mjs\n77c432e879f8611a077feb2fb7c9b67e826ade558e41dcf01433fc2cdab2e0fa packages/business/src/vocabulary.mjs\n6a2c9b371fc9236bb0730ed108cbd770e9487b8ba733dbebccd11891ccba6f17 packages/business/tests/hard-waits.test.mjs\n9ef8b8be3c290f072e7c943b176a1ec688fda03fab4ba114d31213caf2216e42 packages/business/tests/hosts.test.mjs\n610af0c4207bbd805a2a8826daa32b8cdb423937f793c32fd4241f6addfabfda packages/bus/src/broker.mjs\nff927da3cf22fbacc9ace77b2e8b803bffc2953fb58cf7ab4e5f6f6493732996 packages/bus/src/business.mjs\n27d2ae155a2919788d7ad02da10a7056c23f5b71228e9af406b1b8aa0de55efc packages/bus/src/credentials.mjs\n86b4321dd436b97e389101873197dbab95b3d2202e5ef7da712c4978f4f9159f packages/bus/src/credential-verbs.mjs\n5d227edec1d33cb58607fae4e43784a0decc8aaf55cad6bc4318cec4eda6fbea packages/bus/src/host-run.mjs\n3721190045a4f114d67c9c7d32a448cf3d5dbbc6d4554dbc348e9f6fd428b5dc packages/bus/src/runtime.mjs\n3ef86e4f0b77cc513bdd58ec7eb984ceb02af6625f39d1be5254f2c0b0e99734 packages/bus/src/server.mjs\n58e45b7a0d0118be8827bd71d1a427ff84d0ecac46c22adece36dec36e6d521d packages/bus/tests/hard-waits.test.mjs\n28ad8ce2a29722768820ad916273f2100eb79281255c6a3689964cd24564237c packages/bus/tests/host-run.test.mjs\nbe4e1eb88b689e8e4f859de75218af151feae8dce2c1409b726cb9c3dcd5ffe9 packages/bus/tests/spec-fixtures.mjs\n19755f7bb002d8455990c5dc7c458e64aeb95252ec7231a2ed2fd9c638fcb511 packages/cli/src/credentials.mjs\n152a3e57f7241db45bf8262fbf86ee029cf3b98f0d75503990abd46ac151a4b2 packages/cli/tests/credentials.test.mjs\nbdf88833cdb0c5ef76acec50c3c983b55c6053987a5c427cbc400ebd5f3c7110 packages/bus/tests/project-runtime.test.mjs\ne222a5fc3c117f62dbc854ef7397f7b5589e6f5d52e45398db0deeb43c53a9d5 roles/chief-of-staff.md\nd1016f8c34ee04601ab751c7514a20dfbeec0359d46dba8f94c44538dcc16133 roles/pm.md\n"
},
"receipt": "ok gizmoduck-210-inreview-3 rev 2610 row 210 in-progress→in-review round 3 on #1707; request gizmoduck-210-inreview-3 requesting"
},
"viewSha": "1f4701f68f26f103a4f655de274a759df9adcafcbbdda64af0bb1ae1702687be"
},
{
"rev": 2611,
"op": "gizmoduck-210-inreview-3.outcome",
"verb": "review-outcome",
"args": {
"id": 210,
"attempt": "gizmoduck-210-inreview-3",
"outcome": "failed",
"status": null,
"comment": null,
"detail": "pre-send: the credential or account check failed"
},
"by": "gizmoduck",
"at": "2026-10-21T01:56:13.435Z",
"semantics": 2,
"result": {
"row": 210,
"round": 3,
"attempt": "gizmoduck-210-inreview-3",
"state": "failed",
"receipt": "ok gizmoduck-210-inreview-3.outcome rev 2611 row 210 round 3 request gizmoduck-210-inreview-3 failed"
},
"viewSha": "a73e91df61b17634059223d5b027f5f015faa91818cedd05834d8716a0dae392"
},
{
"rev": 2612,
"op": "sage-210-request-3",
"verb": "review-request",
"args": {
"id": 210
},
"by": "sage",
"at": "2026-10-21T01:57:09.243Z",
"semantics": 2,
"result": {
"row": 210,
"round": 3,
"attempt": "sage-210-request-3",
"state": "requesting",
"receipt": "ok sage-210-request-3 rev 2612 row 210 round 3 request sage-210-request-3 requesting"
},
"viewSha": "32fa155b01294254dd4196c74221ac357ca9bdb94279ecdc9cd74902e3cca7c5"
},
{
"rev": 2613,
"op": "sage-210-request-3.outcome",
"verb": "review-outcome",
"args": {
"id": 210,
"attempt": "sage-210-request-3",
"outcome": "posted",
"status": 201,
"comment": 29849,
"detail": "created"
},
"by": "sage",
"at": "2026-10-21T01:57:34.756Z",
"semantics": 2,
"result": {
"row": 210,
"round": 3,
"attempt": "sage-210-request-3",
"state": "posted",
"receipt": "ok sage-210-request-3.outcome rev 2613 row 210 round 3 request sage-210-request-3 posted comment 29849"
},
"viewSha": "a2b6007e12f3fe22b303e1c6c87235d4d7d4a62abf77bc0e02b2d92a7c7004a7"
},
{
"rev": 2614,
"op": "duckworth-r210-record-3",
"verb": "review-record",
"args": {
"id": 210,
"verdict": "approve",
"comment": 29851,
"candidate": "688d38b96b3de80f7bbbca857130ef36f68ec65009a3df51c5084aef7cb96788"
},
"by": "duckworth",
"at": "2026-10-21T02:00:33.347Z",
"semantics": 2,
"result": {
"row": 210,
"round": 3,
"verdict": "approve",
"receipt": "ok duckworth-r210-record-3 rev 2614 row 210 round 3 approve by duckworth (comment 29851)"
},
"viewSha": "c2cca6915f0c016d03b7f1d9ff3b8c1b69723855d8f570639d214c9df32642e7"
},
{
"rev": 2615,
"op": "sage-210-landing-note",
"verb": "note",
"args": {
"id": 210,
"text": "Landed f5753c0e8 on refactor (base 9d165be8c), pushed. Patch-id 723b1c83 matches git diff ad95c888c..0485384e9 on the 23 manifest paths; landed files pass r5 manifest 688d38b9. Duckworth approve r2 rev 2592, r3 29851. Mutants 52/53, M22b equivalent (rev 2584). Gate on ad95c888c+patch: packages 2796/0, scripts 429/0; landed blobs equal the gate tree."
},
"by": "sage",
"at": "2026-10-21T02:48:07.687Z",
"semantics": 2,
"result": {
"row": 210,
"field": "note",
"receipt": "ok sage-210-landing-note rev 2615 row 210 note"
},
"viewSha": "b6999d759893673147691750eb59d7fba3aec0e2cf3a7d9baa8c2c068a97ff34"
},
{
"rev": 2616,
"op": "sage-210-done",
"verb": "move",
"args": {
"id": 210,
"to": "done",
"reason": null,
"candidate": null,
"evidence": null,
"issue": null
},
"by": "sage",
"at": "2026-10-21T02:49:02.158Z",
"semantics": 2,
"result": {
"row": 210,
"from": "in-review",
"to": "done",
"round": 3,
"issue": null,
"candidate": null,
"receipt": "ok sage-210-done rev 2616 row 210 in-review→done round 3"
},
"viewSha": "baf7307b32b70416a29fac41e93919432ac41a1deb34a90239d21b1c146e7ab7"
},
{
"rev": 2617,
"op": "sage-234-briefed",
"verb": "move",
"args": {
"id": 234,
"to": "briefed",
"reason": null,
"candidate": null,
"evidence": null,
"issue": null
},
"by": "sage",
"at": "2026-10-21T02:49:40.096Z",
"semantics": 2,
"result": {
"row": 234,
"from": "queued",
"to": "briefed",
"round": null,
"issue": null,
"candidate": null,
"receipt": "ok sage-234-briefed rev 2617 row 234 queued→briefed"
},
"viewSha": "99921e9a304f3fec10159d53cb04451ab1fd1a56a0bf4683e88f490aca94d5c8"
}
]
}
+35 -2
View File
@@ -257,9 +257,42 @@ export async function startBroker({
if (!(e instanceof BusError)) process.stderr.write(`bus: publish git-failed: ${JSON.stringify({ name: e?.name, code: e?.code })}\n`);
Object.assign(entry, { state: 'refused', code: e instanceof BusError ? e.code : 'git-failed' });
},
);
).then(() => {
if (entry.held) settle(ticket, entry, entry.held);
});
return { state: 'running' };
}
// Row 242 (#1748): the git socket gave up on a publish (publish-timeout) whose push it never
// collected. Its publishRefused waits for the push: until that ends the ticket stays open, so
// checkPublish refuses another publish on the decision with publish-busy (row 237). A push that
// reached the fork is then recorded, which spends the decision; one that didn't is filed with
// the socket's code. Either way the decision can't push a second ref.
function settle(ticket, entry, code) {
remotes.delete(ticket);
try {
if (entry.state === 'pushed') {
try {
broker.recordPublish(ticket, { ref: entry.ref, commit: entry.commit, previous: entry.previous });
process.stderr.write(`bus: publish ${ticket}: pushed after the git socket refused ${code}; recorded\n`);
return;
} catch (e) {
if (!(e instanceof BusError)) throw e;
code = e.code;
}
}
broker.publishRefused(ticket, { code });
} catch (e) {
process.stderr.write(`bus: publish ${ticket}: not settled: ${JSON.stringify({ name: e?.name, code: e?.code })}\n`);
}
}
function publishRefused(ticket, args) {
const entry = remotes.get(ticket);
if (!entry) return broker.publishRefused(ticket, args);
if (!/^[a-z-]{1,64}$/.test(args?.code ?? '')) throw new BusError('invalid-request');
if (entry.state === 'running') entry.held = args.code;
else settle(ticket, entry, args.code);
return args.code;
}
function publishResult(ticket) {
const entry = remotes.get(ticket);
if (!entry) throw new BusError('publish-unknown');
@@ -296,7 +329,7 @@ export async function startBroker({
publishPush,
publishResult,
recordPublish: (ticket, pushed) => broker.recordPublish(ticket, pushed),
publishRefused: (ticket, args) => broker.publishRefused(ticket, args),
publishRefused,
landHeld: (record) => broker.landHeld(record),
// Row 156 (#1651): the launcher's landing pass.
landHolders: (business) => broker.landHolders(business),
+39 -1
View File
@@ -5,7 +5,7 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { chmodSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { connect } from "node:net";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -298,3 +298,41 @@ test("sage-1440: the publishResult poll is bounded: publish-timeout, filed once,
assert.equal(w.events("action.allowed").filter((e) => e.body.action === "git.push.remote" && e.body.operation !== "decision.raise").length, 0);
assert.ok(w.logs.some((l) => l.includes("publish-timeout")));
});
test("row 242: a retry after publish-timeout refuses publish-busy while the bus process's push runs; the late push is recorded and the decision pushes once", async (t) => {
const w = await setup(t, { publishDeadlineMs: 150 });
w.bus.credentials.issue("demo/coder", "gitea", TOKEN, "2099-01-01");
// The fork holds the push in pre-receive until the test writes `release`; `held` says it's there.
const held = join(w.root, "held");
const release = join(w.root, "release");
writeFileSync(join(w.forkRepo, "hooks", "pre-receive"), `#!/bin/sh\n: > '${held}'\nwhile [ ! -e '${release}' ]; do sleep 0.05; done\ncat > /dev/null\n`, { mode: 0o755 });
const until = async (what, ok) => {
for (const end = Date.now() + 10_000; !ok(); ) {
if (Date.now() > end) assert.fail(`timed out waiting for ${what}`);
await new Promise((r) => setTimeout(r, 20));
}
};
const allowed = () => w.events("action.allowed").filter((e) => e.body.action === "git.push.remote" && e.body.operation !== "decision.raise");
// A failed assert must still let the hook end: teardown waits for the fake Gitea, which waits for the push.
try {
const d = w.approve();
assert.deepEqual(await w.publish(d), { ok: false, error: "publish-timeout" });
await until("the push to reach pre-receive", () => existsSync(held));
// The socket gave up, but the push still runs: the decision is held, and nothing is filed yet.
assert.deepEqual(await w.publish(d, { branch: "other" }), { ok: false, error: "publish-busy" });
assert.deepEqual(w.events("action.refused").map((e) => e.body.code), ["publish-busy"]);
assert.equal(allowed().length, 0);
writeFileSync(release, "");
// The push reached the fork, so it's recorded, which spends the decision.
await until("the late push to be recorded", () => allowed().length === 1);
assert.equal(allowed()[0].body.ref, "refs/heads/coder/feat");
assert.equal(allowed()[0].body.commit, w.work);
assert.deepEqual(await w.publish(d, { branch: "other" }), { ok: false, error: "decision-consumed" });
assert.equal(w.gitea.requests.filter((r) => r.path.endsWith("/git-receive-pack")).length, 1);
assert.equal(git(w.root, `--git-dir=${w.forkRepo}`, "for-each-ref", "--format=%(refname)", "refs/heads/coder/"), "refs/heads/coder/feat");
assert.deepEqual(w.events("action.refused").map((e) => e.body.code), ["publish-busy", "decision-consumed"]);
} finally {
writeFileSync(release, "");
}
assert.equal(JSON.stringify([w.events("action.allowed"), w.events("action.refused"), w.logs]).includes(TOKEN), false);
});