Repository navigation
Relay: scoped clients, sealed slots, and a signed queue - #1928
Merged
Merged
Conversation
…pening, pin wire shape Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…and control characters, note keep_running Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…rename limit Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The end line carried status and message only. It now carries a bounded, shape-checked copy of the terminal's data (EndFacts), surfaced as Shown.Ending. Old logs read as before. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ormed facts Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
… report path
hunt_coverage is now either {"unknown":true} alone or a whole account
(attempted >= 1, non-null failures no longer than attempted, every row
named), matching schemas.Coverage; anything else is unknown, never
absent or zero. sec's real Unknown ending, which marshals null failures,
is kept as unknown instead of dropped. More than 16 failures is unknown,
not a cut count. The cap blurs coverage to unknown instead of dropping it.
A report name over the cap or not already one line drops the whole list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sec's errNoChanges branch alone adds nothing_to_audit:true to its ending data. EndFacts keeps it only when true and counts it in Empty; it is not on the cap's drop list. An older ending is never read from its sentence. Co-Authored-By: Claude Opus 5.5 <[email protected]>
TaskPage.program is built from the page read AppTask already makes; no second read, no new host method. Stage words follow the live step's rule, the app groups consecutive equal words, and attempts, auto and history stay unknown. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…he app read Review fixes: a relaunched program no longer wears the last run's ending, the ending must be the last shown line, stage names are redacted and bounded, and only the newest actions without detail cross to the app. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Every launch of a program now has one immutable identity, drawn by its producer before the process exists: the descendant-sweep marker delegate.Run already drew, now caller-supplied (Launch.Marker, delegate.NewLaunch). The run worker and the shell verb stamp it on the program record, on a new `start` line written to the action log before the process is spawned, on every action line, and (through modelapi.Config.Launch) on every turn. No program reads the start line, so no page shows it. The task page's current-process facts are read only from lines carrying the record's launch (delegate.LaunchActions, PlanProgram.Current). A clock that stepped back, a start equal to the old ending's time, an old line written late, or a rerun can no longer put the previous run's ending, stage or actions on the new run. A record without a launch (old logs, a relaunch before its hello) reads as unknown, and the app wire says `read: "unknown"` with no actions instead of an empty list. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The app's task read no longer reads a program's whole logs. AppTask takes the page without its program (planTaskPageFor) and reads only the record's launch through one bounded reader in internal/delegate: it looks back from a size snapshot for the launch's start line, then reads forward over that segment alone, feeding the program's own reader from the launch's first line so stateful vocabularies stay canonical. Each read is held to a fixed budget of bytes, lines and a deadline. A 64 MiB earlier history now costs under 24 MiB of allocation; the old path cost 258 MiB. What could not be read is never called empty, zero or complete. A damaged or overlong line among the launch's lines, a launch over the budget, a refused disk or the deadline give read "partial" with read_cut, and then no count, cursor, last stage or ending. Calls are counted only over the launch's own turns, and only when every line was read; otherwise they are absent. Every action is reachable: tasks.get carries the newest page and a cursor (launch.index), and the optional tasks.program_actions (App.ProgramActions over --host) pages back. A cursor from a launch the task has moved past answers conflict. The terminal's PlanTaskPage keeps its whole-log semantics, and PlanProgram loses the fields only the app used. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The final review found tasks.get and tasks.program_actions still held by a FIFO where delegate-program.json goes: the bounded ReadProgramWithin covered only the app's own read, while the plan rows, the run clocks and recovery reached the bare os.ReadFile through the same request. ReadProgram is now the bounded regular-file read (ProgramRecordCap, 1 MiB) for every caller. A page of older actions resolves only the task's program source instead of building the whole plan page. A launch read stopped early (line budget, deadline, disk error) now carries no actions, because its window is neither the newest nor next to the cursor; only a read that went past a damaged line to the launch's end keeps its window. The byte budget is checked against the launch's span, not against whole search windows. A far engine's page with an unknown read state carries nothing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e reads as none Co-Authored-By: Claude Opus 5.5 <[email protected]>
…t landing has no producer checkout.status is one read-only, bounded, paged git status of the agent's own workspace, with every configured filter driver blanked so no repository program runs. The reply always says land is unavailable (no_producer): Agent.Land lands a standing copy, not the task branches branches.list names, and binds no plan. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ist untracked files one by one A git-lfs style driver marked required failed the whole checkout.status once its command was emptied; required is now set false beside the blanks. A status cut at the byte budget could parse a trailing empty record as a rename's original path; the final terminator is now dropped before parsing. Untracked files are read with --untracked-files=all so a directory is its files, not one row. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ntracked files Entries gain optional added/removed/binary (net change against the status's base) and the reply a lines_unavailable reason. Counts use diff-index because git diff rewrites the index's stat cache; untracked files are never read; every git read of a call spends one shared budget; agreement of the two status reads is status text plus index and per-path stat stamps, which is not a content hash. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Status paths are root-relative, so from a subfolder workspace the literal pathspecs named other files and counted a changed file as 0 and 0, and attribute lookups missed filters. They now run at the repository root. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
PlanTaskBranchLand reads every fact a land depends on - the recorded branch and its commit, the target branch and HEAD, the resolved repository, the whole configuration, the hooks by their bytes, the task's own copies and git's merge-tree prediction - and refuses a dirty checkout, an ignored file in the way, a conflict, a configured merge driver or filter, and any shape it cannot merge without carrying the person's work. LandTaskBranch reads them all again under the repository's lock, refuses when anything moved, merges the planned commit with ort and no autostash on its own ceiling, and reports what it cannot prove rather than undoing it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…m in a land The wire gains the two-phase land: a single-use, 120 s plan held 8 per agent and 64 in all, taken out before anything is checked again, its revision compared in constant time, and a land the native host sends only after its own confirmation. Every git a land runs has core.fsmonitor, maintenance.auto and gc.auto off, so the merge starts no gc --auto and no pre-auto-gc hook the plan never named, and no detached gc that outlives the group kill. Each read is one process group with a capped writer, no stderr pipe and a WaitDelay, killed whole at its deadline. A refused merge is said only as far as it was read - never 'nothing was changed', since git has rewritten ORIG_HEAD - and is uncertain whenever a hook was there to run. The plan is read in phases under the complexity ceiling, the repository root comes only from repositoryRoot, and the lock hands out a closer that is nil when it is not held. Co-Authored-By: Claude Opus 5.5 <[email protected]>
repositoryRoot takes an optional asker, so the one question and the scratch refusal stay where the ground law keeps them while a land puts it through landLine: no GIT_DIR or GIT_WORK_TREE of this process's own, a deadline, and a process group killed whole. The goroutine that waited on the context-free call is gone, and so is a helper git left running behind a busy. Every other caller asks through git exactly as before. The branch counts go through the land's reader too, so an inherited GIT_DIR cannot count another repository's diff. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…lock The shared doorstep watch drains takeover, conversation-deletion and task-deletion requests from its own goroutine, while anchoring a workspace rewrites config.Place under a.mu. The three drains read config.Place.Dir without the lock, a data race the race detector reports in the anchoring tests. One locked accessor, requestDir, now hands all three the folder. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e; close failures wake once (V68b) Ask gains scopes, always_ask (acts_as_you, critical, compound) and outside. asks.answer and inbox.answer take scope (bad_scope when foreign or missing) and save exactly that pattern to tools.bashPatterns or tools.approval, on the terminal's own path. Receipts name the scope and link Settings › Permissions; a call allowed by a saved rule leaves a receipt with its why, kept on reload. Background failures that end within 2s wake the agent once; owed endings are never delayed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…its availability, and where off-map links live (V87b) Co-Authored-By: Claude Opus 5.5 <[email protected]>
… within their byte budget Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ode and owning tool Kept job rows now persist the full command, start and end times, the exit code and the owning tool call. A replayed process gets the live process's id, its command, started_at and ended_at, and its real exit code (a failed job no longer reads exit 0). Rows kept before this fill command and owner from the history entry that names the job, once per open. History bash entries carry the process they started, and processes.list after open returns the replayed set. command and started_at stay on the wire as "" and the zero time when unknown, so existing apps keep working. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…1899) * factory: one merge gate in code, and a pipeline's merge settings Nothing in the factory merged a pull request: a job followed its pull request until someone merged it on the forge. This adds the piece every merge will go through, and the settings that say when one may happen on its own. - factory.MergeGate decides from the job alone whether its pull request may merge now, for a person who pressed Merge or for the pipeline's auto-merge. It fails closed: an unread head, mergeability, checks or reviews refuses and says which. It merges only pull requests the factory opened, only the exact commit whose proof was approved, never a draft, a conflict, a failing or running check, or a branch still waiting on required reviews. Auto-merge additionally needs every claim proven and at least one passing check. - factory.yaml gains `merge: {auto: on|off, method: merge|squash|rebase}`. AUTO-MERGE IS OFF unless the file turns it on, a personal file may only turn it off, and a file that never named merge is written without the key. - Following a pull request now records its head commit, whether the forge can merge it (unknown kept apart from no), and whether checks and reviews were read at all, which are the facts the gate reads. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: the GitHub source can merge, at the exact approved commit only A merge verb for the job's own pull request, behind the gate added in the previous commit. - praf/github MergePullRequest sends PUT .../pulls/N/merge with `sha`, so GitHub itself refuses a head that moved after the gate passed (409 → ErrHeadMoved; 405/422 → ErrNotMergeable). A MERGE IS SENT ONCE: unlike other writes it is never retried, because a lost answer may hide a merge that happened; the caller re-reads the pull request to learn which. - MergeMethods reads which methods the repository allows; with no method in the pipeline the first of merge, squash and rebase it allows is used. - factory.Action gains Commit and Method. The GitHub source's "merge" refuses without a Commit, outside the watched repositories, and on a client that cannot merge (an optional interface, so existing fakes are untouched). Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: Merge in the factory, with Undo, and auto-merge only when the pipeline turned it on A job's pull request can now be merged without leaving codeaf. Every road goes through factory.MergeGate, twice. - POST /v1/jobs/{id}/merge is a person pressing Merge. The gate runs on the job as its last follow read it and refuses in its own words; the commit the person was shown may ride along and a different head is refused. A passing merge waits factory.MergeUndo (5 s) with Undo (POST .../undo), then settles: the gate runs again and lease.Doors.Merge leaves a merge signal beside the job's lease, exactly as an approval leaves a publish signal. - The runner that pulls the job (worker.stand → run.Runner.Merge) carries it out with the assignee's own forge account. It reads the pull request again (factory.Follower; the GitHub source answers it), runs THE SAME GATE on that read, and asks the forge to merge only the exact commit. A lost answer is followed by a read, never a second merge. - The runner's report carries a MergeReport; run.applyMerge accepts it only for the merge the factory queued and only with the job's own pull request, merged. A merge's done is not a publish (lease CarriedMerge): nothing is posted again. - AUTO-MERGE IS THE PIPELINE'S AND NOTHING ELSE'S. Approving the proof pins the pipeline's merge settings on the job (Item.MergePolicy). The settle pass sends an automatic merge only for a pinned policy that says on and only when the gate passes for the pipeline's own ask; until then it waits quietly. A merge that failed needs the person (reason merge_failed) and is never retried on its own. Tests pin that a job with no policy, or a policy that says off, is never auto-merged, on the factory and again on the runner. - The job wire gains merge {auto, method, run, blocked} and the pull request's head_sha, mergeable, checks_read and reviews_read; can.merge is the gate passing for the caller. The record gains merge_asked, merge_queued and merge_refused ("pressed merge", "sent merge", "refused merge"). A running job that is merging says reason merging. - A new publish clears a failed merge of the old head. Co-Authored-By: Claude Opus 5.5 <[email protected]> * codeaf app: factory.job_merge, the protocol, the manual and the change entry - appwire gains factory.job_merge {factory, job, commit?, expect}: one POST to /v1/jobs/{id}/merge, sent once, the factory's refusal carried back whole. commit is the pull request head the person saw. - PROTOCOL.md: the merge row, a Merge section (one gate, twice; Undo; auto-merge pinned at proof approval and off unless the file turns it on) and the new job fields (merge, pr.head_sha/mergeable/checks_read/ reviews_read, reasons merging and merge_failed). - The chat manual's factory page gains "merge a job's pull request — Merge, auto-merge, why it will not merge", with its refusals in the gate's words, and its opening no longer implies a job ends at an open pull request. A probe test pins that merge questions reach it. - Change entry 0-factory-merge-gate. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: can.undo covers a pressed merge inside its window The person who pressed Merge may withdraw it for five seconds; can.undo now says so, as it does for an approval and a stop. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: a step ends by reporting, and says plainly when it did not A stage conversation no longer has a goal owner, so nothing inside it can declare a step done that never called stage_result; it is reminded once, the runner owns the two-hour wall, its reads are never handed off, and its team handle is its step name. A step that fell short names rounds only when it loops and offers send it back only when there is a step to go back to. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: recover a stuck step by running a step again, with words A step rerun carries optional words, kept on the job's notes, and a step that stopped short names the finished step before it as needs_you.back_to, so try again and send it back are one route the app already carries. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: this machine's factory runs here from the start and starts nothing itself Once the app's own factory knows whose GitHub it is, the engine turns its runner and the GitHub poll on, with no switch to find. No start rule launches a job and no triage reads an item there, so an arriving issue starts nothing and a job starts only when a person presses Run. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: one model per job, changeable any time, and a job's chats for its page A job may name its own model: every step opens on it, a running step switches at its next turn, the job's agent follows it, and each step records what it ran on. factory.job_chats answers the job's agent (made when absent) and each step's latest chat from the factory's own record, held open for the app, and the factory's teams leave the crews overview. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: name this machine's factory for its person, rename any factory, quit quietly The local factory is listed as <login>'s factory, and factory.rename keeps a person's own name for a connection on the app's record of it. A queued job reads Waiting to start. Quitting cancels every request with the host and shuts the server down gracefully, so nothing writes after close. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: every new lock in the app's factory unlocks from a defer The local start, the runner check and the stage conversation's turn starts now hold their locks in small helpers that defer the unlock, as the guard law requires, so an absorbed panic cannot leave one held. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: the app's GitHub connection outlives a relaunch The app's factory held its GitHub token in memory only, so every relaunch left its runner off until setup connected GitHub again. Each way of connecting is now kept by its owner: a yes to gh in the factory's profile (gh keeps its token), a pasted token in the app's keychain, handed back with keys.set {provider: "github"} at every launch. A finished setup asks GitHub once whether its token still answers, and only a refusal sends it back to the GitHub step, so a revoked token is connected again by reopening setup. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: a run goes straight to its first step At launch the runner gave the job's manager one turn of its own to shape the run before the first stage. That turn could stall the run behind the conversation's own limits and asked nothing the person had said. A run now starts its first stage with the steps as they stand; the person changes steps themselves or asks the manager in their own turn, and `shape steps` still asks it on purpose. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: the queue never waits on GitHub for people Every queue read looked up each person the floor names on GitHub, one at a time under one lock, and never remembered a refusal; at 800 jobs that was about nine seconds a read. The queue now answers from the profiles already known and reads the rest behind it, retrying a refusal after fifteen minutes. Overlap compares running jobs only, a revision read decodes only its envelope, and one store listing serves a request. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: bring back a dropped job, check GitHub now, re-check on start - A dropped job can be brought back to Backlog (restore verb, can.restore, app action factory.job_restore); nothing starts by itself. Branch pruning now counts only a delete after the job's last drop. - factory.refresh (POST /v1/github/poll) wakes the GitHub read at once; the queue's github source carries polled only once a read went well. - A person's start re-reads that one issue or pull request first: closed or merged on GitHub, the job is done and is not started; GitHub out of reach, it starts on what is known. - The shape steps action is gone, with its manual paragraph. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: a stop that cuts a step says so; a stop before any step is backlog - A person's Stop goes through one write on every path. When it cut a running or waiting step, the step is marked failed and the job carries failure{code: stopped, detail: "Stopped by you"} until Undo, Try again on that step, or a new start clears it; no step of a stopped job reads running any more. - A Stop that cut no step (queued, landed) puts the job back in Backlog with no mark, so Start is the way on. - A stopped job is not stopped again; Reset leaves it as it is; a worker's stopped report is not recorded as a step failure. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: the app can start a stopped job over; a dropped job is not gone for good - factory.job_run_again (POST /v1/jobs/{id}/run_again) lets the app run a stopped job from its first step on the kept branch, spend from $0. - A start of a dropped job is refused with "was dropped · Bring back returns it to Backlog" (one constant, run.DroppedRefusal), and the manual no longer says Drop finishes a job for good. Co-Authored-By: Claude Opus 5.5 <[email protected]> * app: an engine whose app has gone lets go of the factory and leaves - The local factory is released first when the engine closes (runner stopped, server shut down, serve.json and its lock released), before any reply is carried on, so the next engine can resume it. - The engine leaves when the process that started it ends, and a leaving is bounded (30 s); a first SIGTERM/SIGINT/SIGHUP starts the careful wrap-up quit does, a second ends it at once. - While another live codeaf holds this machine's factory, factory.list says so on the local row (reason); a dead holder's receipt holds nothing, and the next list serves the factory here. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: Drop throws away the job's local work; Bring back is a fresh start - Drop deletes the job's worktree folder and local branch on this machine (a pull request job keeps its branch). Nothing on GitHub is touched: the old remote branch deletion is gone. - When the branch or worktree holds work, Drop refuses with has_work and a plain sentence ("…'s branch has 3 commits and uncommitted changes.") until it is sent again with discard: true. - The 7-day branch retention, its timers, locks and wire fields are removed. Drop no longer takes words. - Bring back returns the job to Backlog with no run history: no steps, nothing spent, no pull request link (named in its receipt). Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: Bring back is fresh — the job's conversations and notes go too - restore clears what was said about the old run (talk, thread, heard, notes, adapted steps, room link) and puts the recipe's steps back; identity, issue data, assignee, priority, budget, model and activity stay. - The job's own crew and its conversations (agent and every step's chat) are deleted by the identities the factory recorded, never by pattern; a crew that cannot be deleted is closed, so a fresh run makes its own room. A failed delete still restores and says so. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: step start times, round chats, and typed step lines told live - The job read carries steps[].started_at, and each finished round its started_at; factory.job_chats lists earlier rounds' conversations. - The factory's per-step lines in a job's conversation carry a typed progress mark (step, round, started/done/failed/stopped/waiting), journaled on the line and kept in a side table on the record, so a display entry does not grow; the words stay as they were. - Each such line reaches the app as a live entry.added once it is on disk, told from a desk so the run never waits on the app's read. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: Drop deletes the job's conversations; a repo turned off stops all work on it - Drop throws everything away: the branch, the folder, and now the job's conversations (agent, every step and round), unlinked from the item. Bring back re-discards anything that came back, deletes chats a job dropped before this still names, then resets it. A dropped job's edit, item and talk doors refuse it. - Turning a repo off hides its jobs from the queue and leaves the factory doing nothing on it: intake, triage, PR follow-up, starts, steps, publish and merge all refuse with "acme/api is off · turn it on in Repos to run its jobs." Jobs under way make the switch refuse with has_running until it is sent again with stop, which stops them the way Stop does. Turning it back on brings every job back as it was. - Queue repositories say truthfully whether they are watched. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: the runner clones on demand and every start refuses before it writes - A job whose repository has no known checkout is cloned by the runner on its first step, over https with the factory's GitHub token as an extraheader (never on a command line), under a per-repo lock, into the factory's own folder; a failed clone fails the round with why. - The header Start and the job chat's start take the same road; only a repository that can't be cloned is refused, as 409 needs_checkout. - startJob, run and run_again make every refusal before any write, so a refused start leaves the job's version unchanged; the job read says checkout here / will_clone / missing, and queue repos checked_out. - A 5xx keeps the factory's own sentence for the app. - A model pick no longer checks the job's version. - The job's chat (its manager) spends from the job's budget, before and after Start, and stops at it; its brief says what is said before Start is the brief and never to clone; it never works in the app's home. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: a step's work reaches the end — it always reports, its last report stands, a shortfall gets another round, and what it found reaches the next step - A step that goes quiet without reporting is retried after a failed turn, then reminded up to three times, the last on a turn that requires stage_result; failing that, the factory makes the report from its brief, last message and change ("reported for the step"). A spend limit reads as a spend stop. A stage never hands work to a quick task. - Every stage_result is accepted and the last one before the step stops is the one judged ("updated N times"); there is no two-minute cut. A self-contradictory report is answered at once with what to fix (findings still open, a claim with no evidence); an honest shortfall is held. - One default recipe for hand- and rule-started jobs, with rounds plan 2, write 2, test 3, review 3, proof 3; findings count what is still wrong after the round's fixes; proof claims need evidence; every later round reads the last round's report. - Later steps get the whole plan, real summaries of earlier steps, the plan's files (write) and every claim so far (proof); a worker pull carries each step's whole report, so a resumed run loses nothing. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: maintain issue PRs through repairs and closure with manual takeover * factory: verify check exits independently of output pipelines * factory: enforce the shared job budget across owned work * factory: default new jobs to Auto with recorded approvals * Recover interrupted factory steps with bounded automatic retries * Sanitize factory publication and ingest PR conversation reviews * Review factory drafts and record ready-for-review transitions * Explain Follow PR work through ordinary conversation updates * Preserve independent PR monitoring facts and recover readiness * Recover factory final reviews automatically and preserve readiness intent * factory: redact only absolute local paths in public PR text Repository directories named users, home or root and API routes such as /users/:id were rewritten to [local home] in PR titles, bodies and comments. Redaction now starts where an absolute path starts, including after an option such as -I or a file:// scheme. Co-Authored-By: Claude Opus 5.5 <[email protected]> * manual: describe the final review without harness vocabulary TestNoChatPageSpeaksTheHarnessesOwnVocabulary rejected "verified result" in the ready-for-review section. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory/api: name the two server-only readiness fields the wire leaves out TestEveryFieldRoundTrips failed on factory-solo since CheckRun.AppID and PullRequest.ReadinessContext were added without a wire mapping or a Lost entry. Both are read only on the server: each poll re-reads check-run app ids from GitHub before matching required checks, and a queued final review carries the readiness material to the worker in FollowUp.Details. Co-Authored-By: Claude Opus 5.5 <[email protected]> * praf/github: assert that handled PR feedback queues no repair after restart Since 02ee7a5 monitoring transitions are independent of repair deduplication, so a restored job may settle from watching to healthy and QueueCIFollowUp reports that change. The test read any change as the same feedback repeating. Assert the real invariant instead: no queued round and the same review key. Breaking both dedupe layers still fails. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: keep repair-push PR titles and commit messages inside the public boundary Write sanitized the title when a PR was created, but UpdatePRBody sent the raw item title on every repair push, and the approved commit was pushed with the raw title as its message. A terminal-created job's title can carry a local home path. Apply PublicText to both. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: an incomplete feedback read starts no Follow PR repair round When the reviews, review-policy or feedback read failed, FollowPR still returned the partial PR. Its review key changed, so a paid repair round was queued, and changed back on recovery, queuing a second. Skip the review trigger while any of those reads is incomplete, and keep the last complete review key if CI or a conflict starts a round meanwhile. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory: settled Auto approvals no longer block answers or spin the job timer A plan approved automatically stayed on the item as Automatic, so after a switch to Manual (or at the budget) a person's later yes was refused as "Auto approval stopped". Only an in-flight approval is checked now. An automatic approval that became disallowed between preparation and its time stayed approving with a past deadline: nobody could approve it and the job timer re-armed every millisecond. It now returns to waiting with the reason, for the person to decide. Co-Authored-By: Claude Opus 5.5 <[email protected]> * praf/github: read every page of commit statuses and check runs The completeness check refused any commit whose combined status listed more than GitHub's default 30 contexts or whose check runs exceeded one page of 100, so a busy matrix left CI permanently unreadable: no CI repair and no readiness. Page both lists (up to 1000 each) before judging completeness. Co-Authored-By: Claude Opus 5.5 <[email protected]> * factory/github: clip stored PR read errors on character boundaries A refresh failure reason (700) and a read problem's detail (2000) were cut by bytes, and an API error carries GitHub's raw body. A cut inside a character or an invalid byte became U+FFFD in the store, so the stored text never equalled the next poll's and the job was rewritten every poll. Co-Authored-By: Claude Opus 5.5 <[email protected]> * cmd/codeaf: bring factory tests in line with live job budgets Five factory tests failed on factory-solo: - The lend tests built the stage maker with no floor and opened stages with no job, so since e04e313 every stage refused before spending. Build it as app_factory.go does, with the store and the job. - The one-model test opened stages with no job for the same reason. - The two-people actions test still asserted that a budget can never be lowered; budgets may now go down to what the job has spent. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Harden paged CI reads and verify partial-feedback repair recovery * Add bounded contextual reply suggestions for app conversations * Track execution models and explicit model-selection policy * Keep remote reply suggestions off the ordered send lane * Keep factory policy controls nonfatal and require current repair evidence * Correct five thinking chip manual cross references * Recover factory budgets and persisted Plan decisions * Preserve factory decisions across status reads and recovery races * Keep durable factory status routine in Auto and verify its manager contract * Recover factory verification and monitor already published repairs * Credit the shared harness co-author on factory-created commits * Give each desktop conversation a main and fallback model * Preserve closed factory work and explicit conversation model choices * Fence paid conversation work while a factory job is closed * Recover factory verification checkpoints and track PR review follow-up reliably * Keep factory status questions out of steering and hide duplicate echoes * Keep factory verification live and recover its recorded work * Preserve factory recovery assessments and unsaved results * Reconcile factory verification for fresh and upgraded jobs * Recover completed factory reports and default new jobs to five dollars * Expose factory capacity and default new computers to fifteen jobs * Keep queued capacity counts private to their owner --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
Brings every dev commit since 6d95a9d onto the app line. Where both lines changed the same thing, app/v3's superset behaviour is kept and dev's fixes are added to it: SQLite file URIs use internal/sqlitefile (dev's internal/sqliteuri fixed the same Windows spelling and is dropped), the team start id is recorded as both start_entry and start_id, the daily ceiling binds task and errand turns and still stops a turn whose limit or ledger cannot be read, receipts keep the attempt token and add dev's per-count presence, and dev's /processes and /helpers rows join the shared slash catalogue. Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts: # internal/manual/chat/keys.md # internal/manual/chat/models-and-cost.md
Second merge of dev onto the app line after app/v3 gained the factory work and dev gained the wormhole fabric. Dev's fabric, Windows Git Bash hardening, executor seats and setup turn come in whole; where app/v3 had a richer version of the same code it is kept and dev's change is added to it: the profile writer keeps app/v3's expected-rows check inside dev's decide seam, service rows keep the credential record, jobs keep per-folder starts and pipefail on dev's executor road, media records also seal into the place, the daily ceiling keeps unreadable-limit stops, and the slash catalogue gains dev's pair, devices, atlas, setup, processes and helpers rows. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Dev PR 1910 wins over the old raw-spelling contract: a path holding %, #, ? or spaces is opened at exactly that name. internal/sqlitefile escapes the filename on every platform (relative names stay relative); plandb readers and writers use it; automation, store and workspace get regression tests for the same names. Co-Authored-By: Claude Opus 5.5 <[email protected]>
… catalog media, receipt test shape Dev routes every tool loop through the guarded transport; the app/v3 observed-attempt refusal is kept in front of it. Catalog media requests use the shared redirect policy. The torn-answer receipt test now expects the attempt road recorded as unknown. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…door pins, manual and chord Memory and team-dissolve argument refusals are marked as rejected; rejected calls neither record nor recall a fix. The app credential guard now runs inside the locked profile writer against the held reading, so computed changes (UpdateSources) are guarded too. The Unix door pins accept app/v3's -o pipefail argv. The services manual page gains a heading its name reaches; the chord registry knows the shared effort chord; the selector snapshot test compares picks by value. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ure, DisplayEntry bound 304 to 328 Both branches' receipt provenance is needed: app/v3's person mark and tool process identity and dev's CompletionTasks slice (24 bytes). Packing RoutineCompletion with the other bools recovers 8 bytes of merge padding, so the bound moves 304 to 328 and PERF.md says why. Person occurrences no longer inherit a runtime note's presentation mark or text hash; the entry clone copies CompletionTasks; the cost counts it. The daily-ceiling test seeds its own ledger instead of reading a shared one, and the dismiss and job-budget tests keep app/v3's stopped state and join the usage writer. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…in media test matches the 328 bound Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 35b78409ac78d662636160dc2294ee0c39c096f5)
Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit e939b691050c9481ac9e50733f2b56c2504b35df)
Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 12587f97e97d36ce58a5662be849512cc36823b5)
Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit b8e343d8c5e6c8f76bf3a7abb705dafa00b9a946)
Keep the pending Worker fabric implementation explicitly skipped. Bracket slot timestamps using the relay clock so the same assertion works with fixed and live time. Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit f1bc836422582787e6a29403b4a76a4e56a6d597)
Add scoped client registration, per-client slots, queue append/list/claim/ack, and revoke cleanup in the Worker relay. Enforce client-only fabric access and preserve request proof fields for queue entries. Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit 88723e237f84d1cdf56aa608ac6b5501a6de55cd)
Remove the worker-only fabric skip flag and run the fabric suite in the worker conformance track. Update relay client contract docs to reflect Worker support. Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit c293c97bf48d3cbfd07d4407f805f653ae5976c5)
The relay pull request needs a change entry. Scoped clients, sealed slots, and the signed queue are now on both relays. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Worker implements the same client, slot, and queue protocol. The chat manual still said it did not. Co-Authored-By: Claude Opus 5.5 <[email protected]>
A full device can register a scoped client. The client reads only its own sealed slots and appends signed calls only to its own queue. Registration, scope and revocation commit through clusterstate as a RegisterClient mutation. Slots and the queue stay in relay-host storage behind a linearizable Raft authority read, and a revoke or a retirement purges them. A client is refused on every other route. Every new field is omitempty and is written only when /v1/clients/* is called, so existing callers see no change. Hosted JSON responses end in a newline, as the Go relay's already do. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The app/v3 history above stays as the record; this tree is the port onto today's Raft-backed relay. Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.
Ported from app/v3 onto
devafter app/v3 landed (#2561). This branch keeps the original commits in its history; the last commit is the port onto today's Raft-backed relay.What you can do now
A full device can register a scoped client on the relay. The client has its own key and a method allowlist. It can read its own sealed slots and append signed calls to its own queue. It cannot read the directory, the blob store or other clients, and it cannot change leases or permissions. The Go relay and the hosted Worker implement the same protocol, specified in
docs/relay-clients.md.How it fits dev's Raft relay
When this PR was written, the relay still used its SQLite directory. Dev has since moved to
clusterstate, so the port splits the work:PUT /v1/clients/{device}becomes aRegisterClientmutation committed through Raft. It verifies the client's certificate against the identity key, allows at most 64 methods and a 256-byte name, and answers409if the device already exists.Devicegainskindandmethods, bothomitempty. A device's ownPUT /v1/dir/devices/{id}, and join approval, are refused with400if they try to set either field.<root>/fabric/; on the Worker, in thefabric_*tables ofIdentityDO. Raft's 64 KiB command cap and 8 MiB state cap cannot hold 64 KiB slots. Every slot and queue request is authorised by a linearizable Raft read. A revoke or a retirement purges that client's slots, queue and sequence numbers.401): directory commands and reads, staged uploads, the blob store, watch and hold renewal, the handoff, personal-cluster enrolment, and every hosted route other than/v1/clients/*. A client's requests never advance the directory version (DeviceSeenis skipped).Existing deployments
omitemptyand is only written once someone calls/v1/clients/*. No codeaf build calls it today; the phone app that will call it is not in this repository. Every existing relay test and conformance suite passes with no assertion changed.SchemaVersionis 1), and this follows the same rule as earlieromitemptyadditions: every member must run a build that knows the fields before any caller registers a client. A replica or WASI core older than this, meeting aregister_cliententry or a clientDevice, fails to decode it (DisallowUnknownFields) and stops applying that identity's log until it is upgraded.json.Encoder) already does. Clients already parse both forms.caf-fabric-devonly.caf-fabricwaits for a release withRELAY_DEPLOY_PRODUCTION. The deploy job runs its post-deploy proof and rolls the Worker back on failure. To roll back by hand, revert this squash commit on dev; the next Release redeployscaf-fabric-devfrom the reverted tree. An identity that registered a client in between keeps akindfield that the older build cannot decode, and only conformance identities do that today.Verification
internal/relayserve/fabrictest(the conformance suite),internal/relayserve/fabric_test.goandinternal/relayconf/fabric_test.go. Newclusterstatetests cover registration, the409, the400on self-set scope, and refusal of clients.go test -count=1oninternal/clusterstate,internal/raftabi,internal/raftdirectory,internal/directory,internal/relayserve/...andinternal/manualall pass. The hosted unit tests pass:npm test, 273 passed.scripts/raft-proof-build.shbuiltcore.wasmfrom this branch. The hosted Raft suites, which drive real Go sync clients throughHostedIdentityon localworkerd, all pass:hosted-go(the client journey),hosted-readpath(11),hosted-personal(7) anddirectory. That proves the existing hosted routes through the changedauthorize.scripts/relay-conformance.sh, Go relay. Every in-process suite passes, thefabricsuite included (8 of 8), and the live Go track passes as well,fabric8 of 8 included. The one exception is directoryC7ConditionalForcedTakeOneWins, which fails the same way on dev7b1bea20f: the live suite still expects a legacy blind force-take to win, and the Raft relay refuses it./v1/clients/*routes end to end. The script's Worker track answers 503 on almost every suite on unchanged dev7b1bea20ftoo, so the localwrangler devharness is broken on this machine regardless of this change. The deploy job's post-deploy proof does not reach these routes either. Nothing calls them yet.🤖 Generated with Claude Code