P1
Lane-VM Deployment Host
To Build
The Lane-VM interpreter from lane-mcp's Phase 15 lanevm module is adapted into a deployment host. The ISA is unchanged — the host registers sovereign deployment operations as COMPUTE functions, preserving the sandboxed typed-instruction property.
workers/lanevm-deploy.ts
Extracted from lane-mcp's packages/modules/lanevm/src/ and extended with host functions specific to deployment. These are not new ISA instructions — they are registered compute handlers the program can call by name, and the host controls exactly which ones exist.
| COMPUTE op | What it does | SSRF Guard |
| github_fetch |
GET from api.github.com using App installation token. Returns artifact metadata or raw bytes. |
Host-locked to api.github.com only |
| r2_store |
Write bytes to the bound R2 bucket at a given key path. |
R2 binding, no external HTTP |
| kv_write / kv_read |
Read/write deployment state (current SHA, timestamps, PR preview keys). |
KV binding, no external HTTP |
| sha256_verify |
Verify an artifact's SHA-256 against an expected digest. Returns boolean in MEM. |
In-process, no network |
| webhook_verify |
HMAC-SHA256 constant-time comparison of the GitHub webhook signature. |
In-process, no network |
Per-call caps: 512 instructions (double lane-mcp's 256 — build operations have more steps), 2048 memory entries. SYS_CALL and IO_SINK remain unconditionally rejected at the ISA level.
workers/lanevm-deploy.ts — host function registration
const DEPLOY_HOST_FUNCTIONS = {
webhook_verify: (secret: string, sig: string, body: string) =>
hmacVerify(secret, sig, body),
github_fetch: async (url: string, token: string) => {
if (!url.startsWith('https://api.github.com/'))
throw new Error('SSRF: host not allowed');
return fetch(url, { headers: { Authorization: `Bearer ${token}` }});
},
r2_store: (env: Env, key: string, data: ArrayBuffer) =>
env.FROI_ARTIFACTS.put(key, data),
kv_write: (env: Env, key: string, value: string) =>
env.FROI_DEPLOY_STATE.put(key, value),
sha256_verify: async (data: ArrayBuffer, expected: string) => {
const digest = await crypto.subtle.digest('SHA-256', data);
return hex(digest) === expected;
},
};
P2
Edge Worker Scaffold
To Build
workers/froi-edge-worker.ts — the Cloudflare Worker deployed at froi.albertlane.net. Receives GitHub App webhooks, loads the appropriate Lane-VM program from the repo, and executes it.
Route table
POST
/webhook
GitHub App webhook receiver. Verifies HMAC-SHA256 signature, identifies event type (push/pull_request), loads the corresponding Lane-VM program, executes it.
GET
/
Setup page. Current deployment status, APK download link, installation instructions, build history from KV.
GET
/download/:artifact
Serve artifact directly from R2. /download/latest.apk returns the current main-branch APK. No redirect — raw bytes from the R2 binding.
GET
/status
JSON deployment state: current SHA, build timestamp, artifact size, PR previews list — all read from KV.
GET
/preview/:pr
PR preview artifact. Serves the build artifact associated with a specific pull request number. Key: preview:{pr}:apk in R2.
GET
/health
Liveness probe. Returns KV/R2 binding state and last successful deploy timestamp.
Webhook execution flow
POST /webhook — execution path
push → load programs/deploy-main.lvm.json
pull_request → load programs/deploy-preview.lvm.json
P3
GitHub App — Input Surface Only
Owner Action
The GitHub App is registered once. After that, it fires webhooks and issues installation tokens on request. It does not own, trigger, or control any deployment infrastructure.
github-app/manifest.json
Stored in the Froi-Browser repo. Used by the one-time registration script. All App configuration lives here — not in GitHub's UI.
github-app/manifest.json
{
"name": "Froi Sovereign Edge",
"url": "https://froi.albertlane.net",
"hook_attributes": {
"url": "https://froi.albertlane.net/webhook",
"active": true
},
"redirect_url": "https://froi.albertlane.net/github-app/setup",
"default_permissions": {
"contents": "read",
"pull_requests": "read",
"checks": "write"
},
"default_events": ["push", "pull_request"],
"public": false
}
github-app/register.ts
One-time setup script. POSTs the manifest to GitHub's App creation API, receives the App ID and private key, outputs the Wrangler secret commands to run. Never stores credentials — you pipe the output straight to your shell.
Owner Action — one time
npx ts-node github-app/register.ts
# Outputs:
wrangler secret put GITHUB_APP_ID --config wrangler.froi-edge.toml
wrangler secret put GITHUB_APP_SECRET --config wrangler.froi-edge.toml
wrangler secret put GITHUB_APP_PRIVATE_KEY --config wrangler.froi-edge.toml
P4
Deployment Programs
To Build
The pipeline itself is a Lane-VM program stored in the repo as a .lvm.json file. Changing how deployments work means making a PR that changes this file — the program goes through the same review flow as any other code change.
programs/deploy-main.lvm.json
Main branch deployment program
{
"description": "Froi main-branch deployment pipeline",
"max_instructions": 512,
"instructions": [
{ "type":"COMPUTE", "op":"webhook_verify",
"operands":["$.secret", "$.sig", "$.body"] },
{ "type":"MEM_WRITE", "address":0, "value":"verified" },
{ "type":"COMPUTE", "op":"github_fetch",
"operands":["$.artifacts_url", "$.installation_token"] },
{ "type":"MEM_WRITE", "address":1, "value":"$.result" },
{ "type":"COMPUTE", "op":"sha256_verify",
"operands":["mem:1", "$.expected_sha"] },
{ "type":"COMPUTE", "op":"r2_store",
"operands":["froi/latest.apk", "mem:1"] },
{ "type":"COMPUTE", "op":"r2_store",
"operands":["froi/sha256.txt", "$.expected_sha"] },
{ "type":"COMPUTE", "op":"kv_write",
"operands":["deploy:main:sha", "$.commit_sha"] },
{ "type":"COMPUTE", "op":"kv_write",
"operands":["deploy:main:ts", "$.timestamp"] },
{ "type":"COMPUTE", "op":"kv_write",
"operands":["deploy:main:ref", "$.ref"] }
]
}
programs/deploy-preview.lvm.json follows the same shape but writes to preview:{pr_number}:apk in R2 and records PR metadata in KV.
The program is the audit trail. Every deployment decision is encoded in a versioned, reviewed file in the repo. If a deployment behaves unexpectedly, you diff the program. If someone proposes changing how the pipeline works, it's a PR like any other — reviewable, revertible, traceable in git history.
P5
Wrangler Config + Provisioning
Owner Actions · 5 items
wrangler.froi-edge.toml
wrangler.froi-edge.toml
name = "froi-edge"
main = "workers/froi-edge-worker.ts"
compatibility_date = "2024-09-23"
routes = [{ pattern = "froi.albertlane.net/*", zone_name = "albertlane.net" }]
[[kv_namespaces]]
binding = "FROI_DEPLOY_STATE"
id = "REPLACE_WITH_KV_ID"
[[r2_buckets]]
binding = "FROI_ARTIFACTS"
bucket_name = "froi-artifacts"
Owner actions — in order
1 · Create KV namespace
wrangler kv:namespace create FROI_DEPLOY_STATE --config wrangler.froi-edge.toml
# Copy the returned id into wrangler.froi-edge.toml
2 · Create R2 bucket
wrangler r2 bucket create froi-artifacts --config wrangler.froi-edge.toml
3 · Register GitHub App (P3 — one-time)
npx ts-node github-app/register.ts
# Pipe output to wrangler secret put commands
4 · Deploy the Worker
wrangler deploy --config wrangler.froi-edge.toml
5 · DNS — CNAME at your registrar / Cloudflare dashboard
froi.albertlane.net CNAME froi-edge.{your-workers-subdomain}.workers.dev
The same CLOUDFLARE_API_TOKEN that gates every other Worker in the estate also gates this one. All five steps can run in a single session once the token is provisioned.
→
Implementation Order
| # | File | Depends on | Status |
| 1 |
workers/lanevm-deploy.ts |
lane-mcp Phase 15 ISA (reference only) |
To Build |
| 2 |
workers/froi-edge-worker.ts |
lanevm-deploy.ts |
To Build |
| 3 |
programs/deploy-main.lvm.json |
lanevm-deploy.ts host ops defined |
To Build |
| 4 |
programs/deploy-preview.lvm.json |
deploy-main.lvm.json pattern |
To Build |
| 5 |
github-app/manifest.json |
None |
To Build |
| 6 |
github-app/register.ts |
manifest.json |
To Build |
| 7 |
wrangler.froi-edge.toml |
None |
To Build |
| 8 |
KV + R2 provisioning |
wrangler.froi-edge.toml, CLOUDFLARE_API_TOKEN |
Owner Action |
| 9 |
GitHub App registration |
Worker live at froi.albertlane.net/webhook |
Owner Action |
| 10 |
APK build workflow |
ACT-009 resolved or local cargo apk run |
ACT-009 Gate |
APK build note. Steps 1–9 can be built and deployed before any APK exists. The Worker serves an honest status page until the first artifact lands. The build itself — cargo apk build against the android-bridge target — needs either ACT-009 resolved (GitHub Actions runner) or a local build you upload manually as the first artifact. Either way, the pipeline is live before the first real build; it just has nothing to serve yet.