Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-gatekeeper/README.md

Snapshot at 34e3175

Containing directory

10,050 bytes · textView raw
1# Agent fork gatekeeper
2
3**Local S7/S9 verified; live installation remains separate.** This package connects an enrolled agent to its own
4Git forks and embeds the accepted dashboard in the pinned OS. Product names come
5from root `brand.json`. Core identity/token support from FH-013 is integrated;
6FH-016 deployment integration is complete. The full popup UX remains a manual check.
7
8## Account and Git authority
9
10The private Worker implements the OS vendor, account and fork capabilities. Its
11connection form accepts a public HTTPS Core origin, project and already-enrolled
12agent credential. It checks `/me` and rejects owner credentials. A single-use,
13ten-minute nonce and the pinned kit's handoff bind connection completion to the OS
14user. Credentials remain in `UserAccount` Durable Object storage. Reconnection
15stages credentials and retains the same origin, project and agent identity.
16
17Discovery and the resource picker expose only that agent's forks. `getHead()`
18records an observation and advertises the exact commit to the OS Git cache, so
19`createWorktree()` can pull it through the pinned kit transport. `push()` accepts a
20full commit ID on the default branch, requires a fast-forward, and queues an OS
21approval action. Only applying that exact action builds its scoped pack and
22performs a compare-and-swap push with the recorded old SHA. Changed heads fail;
23there is no force push or history-rewriting revert. A successful push whose reply
24was lost can be recognized by its exact new head.
25
26Before each Git request the Worker rechecks ownership and requests a repo-scoped,
2760-second read or write token. Raw Git HTTP uses a git-style User-Agent and refuses
28redirects. Neither credentials nor tokens are returned to the browser or agent.
29Interrupted queue submissions retain their action ID for retry; duplicate OS
30approval rows cannot cause duplicate Git writes. Canonical repository writes are outside this package. Disconnect deletes the local
31connection and stops its capabilities; it does not revoke other clients' Core
32credentials or all tokens for that repository. Already issued tokens expire.
33
34## Contribution workflow
35
36The fork session exposes `context()`, `join()`, `claim(packet, repoName?)`,
37`release(packet)`, `requestReview(packet, repoName, sha)`, `readReview(review)` and
38`readReceipts(review)`, using the accepted shared Stable types and credential
39provider. `context()` combines raw core state and the authenticated `/me` work
40summary. Receipt reads use the agent-visible review summary; the owner-only full
41receipt route is not called. Code Mode gets self-contained declarations composed
42from the actual Stable records/contracts and the gatekeeper interfaces.
43
44Every write queues an action and returns `void` before approval; `awaitDecision`
45asks the host to stop the turn until its decision. This is a queue acknowledgement,
46not an applied core result. After approval, call `context()` to observe the result.
47`join()` creates a fresh fork with a 60-second Git grant, discards its token-bearing
48response, and never returns, stores or logs the token. Attach the new fork before
49editing it. Do not repeat join to resume existing work.
50
51Claim, race entry, join, push, request review and release have separate eligible
52action kinds. The OS decides whether each action needs approval using its own
53per-type rules; this package neither creates nor modifies those rules. Claim versus
54race entry is derived from core's packet mode, not from an agent-provided label.
55A mode change before apply refuses the action. Pending Git and workflow actions
56block each other so an agent cannot request review while its push is unapplied.
57
58Queue submission retries retain the same action ID. After an uncertain write,
59fresh context reconciles the stored intent: a matching live claim, retained race
60entrant, newly created agent fork, exact-commit review, or removed claim proves
61the desired result. Push uses the exact remote head. Reconciliation and writes
62share the serial queue, so a read begun before a write cannot authorize a retry.
63
64Join compares the agent's fork names before and after the request: one new fork
65proves success; existing forks alone do not. Multiple new forks, a changed claim,
66a divergent push head, unavailable context, or missing evidence keep the fence
67with a message naming what to inspect. Authoritative absence allows one retry of
68the same intent; a second uncertain attempt cannot retry again. An omitted claim
69fork is resolved before approval and stays fixed across retries. No read resends
70a write; the same approved action must be applied again, or the same call can
71resubmit its approval request. Later positive evidence can clear an exhausted
72fence. Approval-time calls recheck the connected account and obtain bearer
73credentials through the Stable provider. Existing queued pushes keep their IDs.
74
75Human approval copy names the agent/project for join, the entrant/packet/fork for
76race entry, and seven-character commits for push and review. Stored operations
77and pushed-commit payloads retain full commit IDs.
78
79## Sidebar and resource picker
80
81`AgentAccount.startAppUi()` supplies the built sidebar and a read-only native RPC
82capability. Core `/state` is projected through the accepted dashboard's `stateView`
83and rendered by its unchanged `mountDashboard`. Authorization is checked before
84reads and disclosures. Core polling has one request in flight and waits 250ms
85between reads; snapshot publication coalesces at 250ms. Disposal/revocation stops
86polling and releases subscribers.
87
88The sidebar uses the pinned OS private MessagePort handshake, `host.ui` and
89`host.openPrompt`. Ask an agent prefills the concrete contribution workflow without sending
90it. Account cards include the verified agent name. Resource descriptions and the
91picker use the Stable address helper with the connected origin and project. Section links scroll within the srcdoc frame. Approve/Promote are disabled,
92capture-blocked, and independently rejected by the transport. Project switching is
93disabled because the capability is bound to one connection.
94
95Push uses two inbound startup calls and no per-batch refresh calls. One receiver,
96one latest pending snapshot and one long poll bound backlog. A silent/unsupported
97push channel falls back to a 25-second long poll. Four updates per second fit the
98pinned limits of 8 concurrent calls, 600/minute and 128 pending calls. These are
99browser capability limits; Core authorization/state requests are separate traffic.
100
101The resource picker lists owned forks, supports an owned initial selection, and
102returns only the selected resource URL. The separate draft configurator remains a
103clearly labeled fixture for format checks; it cannot establish a connection.
104
105## Builds and checks
106
107```sh
108pnpm install --frozen-lockfile
109pnpm --dir cloudflare-os install --frozen-lockfile
110pnpm --filter forkhorse-gatekeeper build
111pnpm --filter forkhorse-gatekeeper test
112pnpm --filter forkhorse-gatekeeper test:local
113pnpm test
114pnpm lint
115```
116
117The build produces three self-contained HTML documents and `dist/worker.js`.
118`test:workerd` builds those three HTML assets before starting Vitest, so `test`,
119`test:run` and root `pnpm test` work on a fresh checkout without a prior build.
120The shared `build:assets` step also runs during production builds; `dist/` stays
121untracked.
122Bundle checks exclude the offline entrypoint and its synthetic credentials/data
123from production. Worker types use native RPC validation. The production config has
124no public Workers URL; the generated deployment supplies its private base URL and
125Durable Object migrations. It replaces the starter sample in build/deploy order
126and bindings. The template wires the vendor by default, without provisioning an
127account; disabling it provides no sample fallback. Only this package's lockfile
128importer is changed relative to accepted main.
129
130The suite includes browser/MessagePort tests and native workerd tests for account
131lifecycle, ownership, approval, stale heads, Git transport and sidebar disposal.
132`test:local` uses a temporary real bare Git repository, actual `git http-backend`,
133the unchanged pinned OS kernel and a native workerd approval queue. It creates a
134worktree from the advertised fork head, edits/commits, verifies that the remote does
135not move before approval, applies the approval and verifies the remote file. It
136also races a real ref update against receive-pack and verifies CAS rejection.
137Core enrollment and tokens in this test are explicitly fixtures. The runner removes
138its temporary repository and generated modules and never edits upstream source.
139
140## Local browser acceptance and limits
141
142- `dev:fixture` hosts a visibly synthetic sidebar/draft configurator.
143- `dev:local-sidebar` uses the actual pinned `SandboxedGatekeeperApp` component,
144  router and limiter with a synthetic feed. This is a component harness.
145- `vite.run-local.config.ts` builds `dist/offline-worker.js` for a disposable copy
146  of pinned upstream `run-local`. Its fixture entrypoint supplies only synthetic
147  `/me` and `/state`, and refuses every other outbound request. It must never be
148  deployed. Production tooling consumes `dist/worker.js` only.
149
150The full local OS has also executed the actual agent createWorktree/edit/commit,
151approval and Git push flow using a deterministic local model-protocol fixture.
152Its actual native account/sidebar capabilities passed the sustained update and
153prompt checks. Reproduce this with the [local harness guide](test/local/README.md).
154This remains
155local fixture evidence, not live Core/Artifacts or installed owner acceptance.
156See `HANDOFF.md` for exact coverage and outstanding gates.
157
158Negative native RPC tests emit expected exceptions. A plain returned RpcTarget
159control reproduces a hung-request warning in the Vitest worker pool while still
160returning the rejection, responding to a later ping and disposing correctly. The
161standalone Miniflare control and successful pinned-kernel Git acceptance do not
162emit that warning. Logs remain unsuppressed; the underlying pool/runtime cause is
163not proven.
164