Repository navigation
F: stamp a publish outcome with the state version the publish last saw (#114) - #123
Merged
Merged
Conversation
TaskPublishing (runner/publishing.ts) publishes the task's PR with the real publisher: GhAlreadyFixedGateway, GhPullRequestGateway and a GitBranchPusher that pushes from the runner-owned repository to the configured repository on GH_HOST (never a configured URL), overwriting only ledger-owned commits. - Mode by status: a running task whose plan items have all run gets a ready PR; a task in needs human gets a draft PR with what sent it there. - When: after a start/resume run ends; when a refused start/resume moves the task to needs human; on the new `publish` runner action; once at startup, after the CLI re-verifies the lock, if a publish is owed. - Schema v10 `publish_outcomes`: the last outcome, shown as `publish` in GET /api/runner and replayed by the publish action. Tasks already running or in needs human at the upgrade are recorded as `not published`. - Shutdown: publishes are aborted in step 1 and awaited before the Store write gate closes. Start, resume and a second publish are refused while a publish runs. Demos never publish. - github.baseBranch is required with a runner block, checked before the lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…env, CLI test - A draft PR's problems name the current attempt's safety finding before a spent budget, so a budget that ran out later cannot hide the violation. - The already-fixed and PR gateways take the setup's env, the same as the push, so GH_HOST and credentials agree; recorded errors are redacted with that env's token values (RunnerAssembly.env, TaskPublishing). - A CLI test shows the startup publish runs after the lock is verified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… feat/103-publish-pr # Conflicts: # web/cli.ts
#114) A publish outcome was stamped with the task's state version at recording time. Where a GitHub call came after the publish's last Store write (a draft change that failed and left the PR ready, or one refused because drafts are unsupported), a change someone else made during that call counted as seen, and TaskPublishing.owed() could suppress a publish owed for it. Every PublishOutcome now carries a required seenVersion: the version read where its path returns, right after its last Store write or guarded read, or from before the last GitHub call when one came after that write. The type checker makes every return path decide it. Store.recordPublish stamps it and refuses a version newer than the current one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version
…103) The publish action's ID lived only in memory, so a crash after its 'publishing' reply committed and before the publish recorded an outcome left the replay saying 'publishing' for ever. A reply that still says 'publishing' now counts as unsettled: any later outcome of the task settles it (one publish runs per task), and startup settles it as 'stopped' (interrupted) when no publish is owed. Copilot review on PR #110. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version # Conflicts: # docs/implementation/pull-request-opening.md # test/runner-publishing.test.ts
…ver record at startup (#103) An automatic publish (after a run, at startup) had no durable in-flight marker: a crash after its PR opened and moved the task to in review, but before its outcome was written, left publish.last empty or stale for good, since startup then saw nothing owed and no unsettled action. Every publish now records 'publishing' before its first external call (and does not run if that write fails); startup publishes again when one is owed and otherwise settles a leftover 'publishing' record as 'stopped'. Copilot review on PR #110 (previously missed finding). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version # Conflicts: # docs/implementation/pull-request-opening.md # runner/publishing.ts # test/runner-publishing.test.ts
…lock before the lock (#103) - A task whose earlier run owes a safety escalation or a scope pause (its write failed, or the process stopped after the item settled) is not finished work, but every item has run, so no later run would pay it and a ready PR would show unreviewed out-of-scope changes. Publishing refuses it (mode), and a run's end, startup and a refused publish action have the executor pay it first (ItemExecutor.owes/payOwed). - The CLI refuses a runner with no github block before taking the lock, so the missing block is reported as itself, not as a lock conflict. Copilot review on PR #110. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version
- A ready publish needs every item of the current plan revision completed at that revision, checked in the Store's publish guard before every guarded write: a revision that adds an item during a publish's first GitHub lookups no longer gets a ready PR. - Only a publish action refused because of owed work pays it; a refusal for a stale view, a busy task or a replay starts nothing. - A task recorded as 'not published' at the v10 upgrade stays unpublished until a person asks, whatever changes since. - A failure while paying owed work no longer stops startup from settling an in-flight record or a stuck reply. - A publish refused while a lost opening settles is retried at that opening's deadline (settleRemaining). - Startup settles a reply that is the only thing stuck with the outcome on record, leaving that record (and its version stamp) as it is. - The owed-work scan runs only for a status that could publish, not on every poll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version # Conflicts: # docs/implementation/pull-request-opening.md # test/runner-publishing.test.ts
- A publish that ends with an opening still in flight (its own reply was lost, not only a refusal while one settles) is retried at the opening's deadline; a list-lag OpeningUnsettled is retried after 30 s. - 'not published' (v10) holds only until a run starts after the upgrade. - Only the publish action's own owed-work refusal pays that work; a stale request or any replay applies nothing. - A draft waits only for an owed safety finding, not an owed scope pause (which a needs-human task cannot record). - startup() leaves a publish of this process alone; the stopped record it writes keeps the interrupted publish's draft flag; the retry decision cannot throw out of run(). - owes() reads only owed findings in memory and completed out-of-scope attempts (Store.scopeFindingHeads), not every attempt, on a poll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version # Conflicts: # docs/implementation/pull-request-opening.md # runner/publishing.ts
- Bound the automatic retry (AGENTS.md: back off recurring external polling): one timer per task; a lost opening is waited for only while its deadline is ahead, so one still stuck after it (two PRs carry its marker, the repository changed, GitHub keeps failing) no longer re-publishes every second; the list-lag retry runs once per chain. - A person's start or resume that moves an upgraded task (v10 'not published') ends that hold, so a refused resume that sends it to needs human gets its draft. - Owed work is paid only where a run would (running, queued) or a draft waits on it (needs human). - The 'not published' tests space their timestamps, which are compared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version # Conflicts: # docs/implementation/pull-request-opening.md
- Retry budget per chain (until a publish settles): at most one retry at a lost opening's deadline and one short retry, so a GitHub outage that fails every open no longer republishes every settle time for ever. - An 'opened' outcome that left the PR or the task where the publish did not mean them (GitHub showed another head, so the PR was drafted and the task stayed running; a draft change failed) is recorded with `reconcile`: still owed, and given the short retry. - A refusal just as an opening's deadline passes gets the short retry. - The short retry's delay is a test seam (RunnerAssembly.shortRetryMs); tests now cover the list-lag retry, the outage bound and reconciling. - Tests that proved less than they claimed: the owed-work failure test now reaches the failure; the CLI baseBranch half holds the lock; the CLI startup test stops only after the outcome is recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version # Conflicts: # docs/implementation/pull-request-opening.md
- A run's end, a person's action or a restart starts a new retry chain with its own budget (AGENTS.md: reset on a lifecycle change or user action); a retry timer's own publish does not, so retries stay bounded. - No short retry while a lost opening's deadline is still ahead (it would only be refused again). - `reconcile` only while the task can still be published; the action reply carries it; the settleHead comment no longer overstates. - The owed-work path of a refused publish action carries the person's intent (personAsked), like start/resume. - publishOwed runs the lock check itself, first: no order of the CLI's lines can start a startup publish before the lock is verified. - Tests: the one-short-retry bound, the new chain on a person's action, and the lock check running first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version # Conflicts: # docs/implementation/pull-request-opening.md
- A person's publish action drops a retry the old chain armed, so it cannot fire later on the new chain's budget (at most two automatic publishes per chain, not three). - Tests for two round-5 fixes that no test covered: no reconcile for a task that can no longer be published, and the action reply carrying reconcile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version
- Tests for the owed-safety-finding gate: a ready publish is refused while a finding is owed, the action pays it and the draft follows; a needs-human task's owed finding is paid at startup before its draft; a replay of that refusal pays nothing. - The draft of a task whose owed finding was paid names that finding: the executor settles it in memory only, so TaskPublishing keeps the reason payOwed returned (it was lost before). - A run's end starting a new chain drops the old chain's armed retry, as a person's action does (#newChain): one timer per task, tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A paid safety finding names a draft only while the task stays in needs human on the same current attempt; a later return has its own reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version
A publish refused by a guard (a race) gets the one short retry; a draft names a finding whose save failed even after the budget ran out; a needs-human task pays only an owed finding, not a scope pause. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/codeboost into feat/114-publish-seen-version # Conflicts: # docs/implementation/pull-request-opening.md
… feat/114-publish-seen-version
… feat/114-publish-seen-version
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #114. Stacked on #110 (base
feat/103-publish-pr). Retarget it tomainonce #110 merges.What it does
#114 suspected, with low confidence, that a publish outcome could be stamped with a task state version the publish never saw. The gap is real. The publisher makes a GitHub call after its last Store write in two places:
#settleHead, a draft change that fails and leaves the PR ready (leftReady);The outcome was stamped with the version current when it was recorded. So a change someone else made during that call counted as seen, and
TaskPublishing.owed()could suppress a publish that change made owed.The fix:
PublishOutcomenow carries a requiredseenVersion. It is read where each path returns, right after that path's last Store write or guarded read. On the two paths above, it is read before the GitHub call. Because the field is required, the type checker makes every return path, including future ones, decide it.recordPublishstampsseenVersion, and refuses one newer than the current version.TaskPublishing: passes it through.pull-request-opening.mddescribes the stamped version.A refusal, failure or stop is still stamped with the current version. It stays owed either way.
Tests
test/publish.test.ts): a change lands during each of the two GitHub calls, and the outcome reports the version from before it. On the draft-unsupported path, the draft-step attempt comes first and the main path's call is the second.test/runner-publishing.test.ts): GitHub shows another head, so the draft change runs, fails, and someone cancels the task during it. The recorded outcome keeps the seen version.test/runner-lifecycle-store.test.ts): the override, and the refusal of a version newer than the current one.Typecheck passes. 267 tests pass across the publish, publishing, lifecycle-store and cli files, merged with the latest #110.
Review rounds
/code-review highseenVersionwas optional and set only at the two known paths, so a future GitHub call after the last write would silently reopen the gap/code-review highReview-lesson audit. Both the bug and the finding fall under "After the final asynchronous external validation, re-read the local generation…" (AGENTS.md). Making the field required is a one-off design choice. No new rule is needed.
🤖 Generated with Claude Code