Skip to content

v4.0.0: events, minions, and trackers other than GitHub - #40

Merged
Param-Harrison merged 24 commits into
mainfrom
release/v4.0
Oct 12, 2026
Merged

Param-Harrison merged 24 commits into
mainfrom
release/v4.0

Conversation

@Param-Harrison

Copy link
Copy Markdown
Contributor

Events, minions and the tracker split. See CHANGELOG.md v4.0.0 and research/v4/PLAN.md for the plan.

  • CloudEvents bus with signed outbox delivery, inbound POST /events that only wakes the poll, on: event workflows.
  • Minions: issue-less jobs on events or cron, triage-level tools, shared spend caps, fenced event data.
  • TrackerPort / CodeHostPort split; http tracker speaking Tracker API v1 (docs/tracker-api.md); labels mapping.
  • Security review findings (trust bypass, spend cap, prompt injection, auth bypass) fixed, each with a test and a mutation.

Gate: make check 1684 pass, 0 fail (was 1593).

🤖 Generated with Claude Code

Param-Harrison and others added 24 commits October 11, 2026 22:49
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…racker, labels map

ScmPort is now TrackerPort & CodeHostPort. Every new GitHub() in the CLI and
the dashboard goes through makeScm(config), which pairs config.tracker (github
or http, Tracker API v1) with GitHub as the code host and applies config.labels
at the tracker boundary. With a non-GitHub tracker the PR is found by head
branch, closing keywords are rewritten, the PR URL is commented on the tracker
issue and a merge closes it. Doctor reports the tracker kind and its auth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
core/event.ts holds the envelope, EVENT_TYPES and the x-kaas-signature HMAC.
engine/bus.ts writes each emit to a SQLite outbox and delivers it to
config.events.sinks with exponential backoff. runWorkflow, the pr step,
processReadyIssue and both merge paths emit through emitEvent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tations

MemoryTracker and a Bun.serve reference server in tests/fixtures; one
TrackerPort contract run against both; code, server and docs routes held
equal; branch-mode linking and an e2e pickup on the http tracker. Two
mutations (bearer header, closing-keyword rewrite). Docs and the config
example name tracker and labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An adapter may not import another adapter (tests/hexagon.test.ts); the
resolver composes the GitHub and http adapters, so it lives beside github.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /events takes HMAC-signed CloudEvents from other KaaS apps (only
external types), deduped on id. A workflow's `on: event:` files a ready
issue per event; `.factory/minions/*.yml` are checked at boot and run with
no issue and no PR, emitting factory.minion.completed and their own type.
`factory minion list|run` and `factory events tail|replay`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…runs

Another app's event (inbound, or a minion's answer) files its issue with no
factory:ready, so a maintainer labels it and pickup checks their role. A
minion respects paused intake and the repo and machine daily caps through one
resolver (spendCapHit, shared with the poll), records spend on the shared
ledger, runs with triage's tools and allow-list, and gets the event fenced as
untrusted data. Mutations for the signature, inbound and each of these.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every external event wakes the poll, which applies the label path's trust
gate, intake and caps; no code reads an issue or actor from event data. A
test posts a correctly signed event naming an issue an untrusted user
labelled ready and asserts nothing runs, with mutations for both paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
resumeInReview only looked at a PR's merge under merge.policy auto or dry-run,
and findPrByHead lists open PRs only. A PR a human merged on GitHub with policy
off (the default) left an http tracker's issue open in factory:in-review
forever, and factory.pr.merged and the merged retro never fired on any tracker.

- CodeHostPort.mergedPrByHead: the newest merged PR on a head (gh pr list
  --state merged), passed through ComposedScm with branch-mode linking.
- resumeInReview checks it first: a merged PR closes the issue, takes
  in-review off, emits factory.pr.merged by "human", runs the retro and
  returns "shipped". A `<!-- factory:merged pr=N -->` comment makes it once
  per PR; a PR closed without merging does nothing.
- The factory's own merge goes through the same recordMerge (by "factory"),
  so a later poll never reads it back as a human merge.
- docs/events.md and docs/tracker-api.md say what the code does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spend ledger comment showed the worker id (hostname and pid) and dollar
totals on customer-facing tracker cards, and the budget park comment showed
the spend amounts. Both now show neutral text; the numbers stay in the hidden
markers the factory reads back.

FakeGitHub and MemoryTracker now refuse any write whose visible text has the
host name, a .local host, a dollar amount or "model". tests/tracker-comments
walks every tracker write call site in src/ and pins the test that drives it
through one of those fakes. Four new mutations cover the checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#42)

Verify could not check "removing the guard makes the tests fail": its Edit on
the issue worktree is denied, so kaas-suite card 15 parked as uncertain twice.

template/.factory/mutate.sh copies HEAD's tree to a temp dir, runs the named
tests there unmutated and then with one literal replacement, prints a
MUTATION: line, appends the result to proof.json, and deletes the copy. The
worktree never changes. What runs is fixed by the script and config: the
test gate's cmd as argv on named test files, a source target outside
.factory/.claude/protected/build files, a replacement from a fixed operator
list, a minimal env, and dependency dirs copied rather than linked.

Verify's allow-list gains the script; its only Edit rule stays the run dir.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
listPrs now asks gh for isCrossRepository and drops PRs from other repositories. Every
lookup by head branch or by issue goes through it, so a fork can no longer pass as the
factory's PR by naming its branch factory/issue-N or writing "Closes #N" in its body.
A test and a mutation pin the filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A runner that cannot start, exits 126/127 or is killed, a timeout, a
non-zero exit with no failed test (a crash or compile error), a no-op
replacement, and any unexpected exception are status=error, exit 2, never
bites or survives. The fixture repo disables git gc and maintenance so a
background run cannot touch .git while the tree is hashed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n sets)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d mutation sets)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(watch): notice a PR a human merged, whatever merge.policy says
Keep operator detail out of tracker comments (#44)
Ignore pull requests from forks when finding the factory's own PR
fix(verify): run a plan's mutation on a throwaway copy with mutate.sh (#42)
@Param-Harrison
Param-Harrison merged commit 8130873 into main Oct 12, 2026
3 checks passed
@Param-Harrison
Param-Harrison deleted the release/v4.0 branch October 14, 2026 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant