ops/ax/probe-placement.sh scales ousast-pool to one worker, applies probe-pool (1 x 2 CPU / 3Gi, the already-built ateom-gvisor image, no ko build), runs one model-free repo-facts Task requesting 2560Mi (limit 3Gi) and one requesting 256Mi, records `kubectl ate get workers -o json` after each resume, and removes everything it created. Live on kind: both actors landed on the 1536Mi ousast-pool worker (status.allocated.actors=1 there, 0 on probe-pool) -> placement: any. design.md section 4 records the outcome and the fallback group 5 must take (an engine atespace with engine-pool as its only pool if Substrate scopes pools by atespace; else a plain Kubernetes Job on k3s). Evidence under benchmarks/measurements/2026-10-02-k8s-01-profile/probe/. Gates (tree identical to 1.4 in src/ and tests/; ops/ gains the probe script, which the literal scan covers): pytest 2010 passed, 42 skipped, exit 0; ruff check 0; ruff format 0; mypy 0. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
22 KiB
Implementation Plan
One task group per increment of design.md section 7, in that order; a later group never starts before the
earlier group's kind proof is recorded. Every task ships with its tests; a task is done when its tests pass in
the host suite, ruff and mypy are clean, and nothing in src/, plane/, ops/ (minus ops/ax/up.sh and
ops/k8s/profiles/) or docs/ names kind-ousast, localhost:5001, 172.19. or a port-forward address
(task 1.3 makes that a test). Nothing is deleted before its replacement passes its proof. Each group ends with
its kind proof and the record it writes under benchmarks/measurements/<date>-k8s-NN-<slug>/record.json
(commit, images, the instrument checks, the numbers). Steps that call a model are marked paid: needs a budget
go-ahead with a ceiling; steps that need a hand on a cluster that does not exist yet are marked manual.
-
1. Profile and literals (design section 1; open-question checks 2, 3, 7)
-
1.1
PlaneProfilesrc/openultrasast/plane/profile.py: frozen dataclass with the fields of design section 1;load_profile()readsops/k8s/profiles/<OUSAST_PLANE_PROFILE>.toml(a name or a path; defaultkind), then appliesOUSAST_<FIELD>overrides; rejects an unknown key, a secret-looking key (*_KEY,*_SECRET), anexecoutsidelocal|remote, animagesfile without digest pins.ops/k8s/profiles/kind.tomlandk3s.tomlcommitted with the design's values;ops/k8s/profiles/kind-images.jsonfrom~/.cache/ousast/ax-src/runner-image.tests/test_plane_profile.py: defaults; file then env precedence; each rejection names the field; both committed profiles load; no field holds a value matching a credential pattern.- Requirements: 1.1
-
1.2 Literals out of
doctor.py,egress.py,router.py,generate.pydoctor(profile): context fromprofile.kube_context; the registry check resolves theimagesfile's runner reference (docker manifest inspectfor a remote registry,/v2/_catalogfor a local one);open_storeverifiesprofile.memory; prints every configured address (Req 1.2).Egress(context=...),open_router(context=...)andreceiver_cluster_ip(context)take the profile's context;generate.ATESPACEbecomesprofile.atespace.cli.py:ousast plane doctor|run|workspaces|harvestaccept--profile.tests/test_plane_reconciler.py,test_plane_router.py,test_plane_egress.py: the fakes receive the profile's context, never a built one.- Evidence:
grep -rn "kind-" src/openultrasast/planematches only comments citing the kind profile. - Requirements: 1.1, 1.2
-
1.3 Host-literal scan test
tests/test_plane_literals.py: pattern built at runtime (astests/test_removed_plane_references.pydoes) oversrc/,plane/,ops/minusops/ax/up.shandops/k8s/profiles/, anddocs/, forkind-ousast,localhost:5001,172.19.,127.0.0.1:andport-forwardoutsiderouter.pyand lines taggedax-tunnel; fails listingpath:line.- Evidence: the test passes on this commit and fails when a literal is reinserted (shown once in the record).
- Requirements: 1.3
-
1.4
up.shby profileops/ax/up.sh:KO_DOCKER_REPOand the context fromkind.toml(read withpython -m openultrasast.plane.profile --print registry|kube_context); writeskind-images.jsoninstead ofrunner-image;generate.repin_templatesaccepts that file (runner key only until task 4.3).- Evidence:
ops/ax/up.shreruns idempotently on kind;ops/ax/smoke-run.shpasses. - Requirements: 1.1, 2.4
-
1.5 Placement probe (open question 2, decided before the engine work) -- result:
placement: anyops/ax/probe-placement.sh: applies a second WorkerPoolprobe-pool(1 replica, 3 Gi) from the design's engine template, then one model-freerepo-factsTask withresources.requests.memory: 2560Mi, and records withkubectl ate get workerswhich worker took the actor; then the same Task with 256Mi. Removed after.- Evidence:
record.jsonsays whether Substrate places by resource fit (placement: fit|any).anyselects the design's fallback for group 5 and is written intodesign.mdsection 4 before group 5 starts. - Requirements: 4.1
-
1.6 Reconciler bound (open question 7)
- Record
wc -l src/openultrasast/plane/reconciler.py(477 at17ac7ad) and the lines task 2.4 may add (receiver wiring removed:-12,Deliverycalls:+8estimated). If the estimate crosses 500, the bound intests/test_plane_reconciler.py:803moves to 550 in task 2.4 with the reason in the assertion message. - Evidence: the numbers in
record.json. - Requirements: 2.1
- Record
-
1.7 gvisor tarball reachability (open question 3) manual
docs/deployment.md(B.1, planned): the k3s node must fetchgs://gvisor/releases/nightly/2026-09-02/<arch>/gvisor.tar.zstd(substrate/manifests/ate-install/sandboxconfig-gvisor.yaml) or a mirrored copy named in a copied SandboxConfig; the check iskubectl -n ate-system logsof the worker pod's atelet fetch line, run by hand on the day k3s exists. Labelled planned until then.- Requirements: 7.2
-
1.8 Kind proof and record 01
ousast plane doctor --profile kindpasses every line;ousast plane doctor --profile k3sfails only on the unreachable context (the expected line);test_plane_literals0 offenders; the probe of 1.5; the bound of 1.6.benchmarks/measurements/<date>-k8s-01-profile/record.json.- Requirements: 1.1, 1.2, 1.3, 7.1
-
2. Store delivery, receiver-less egress (design section 3)
-
2.1 Presigned URLs and the store run directory
src/openultrasast/plane/delivery.py:Delivery(store, run)mints presigned PUT forruns/<run>/<task>/output.tarand GET forruns/<run>/<producer>/<artifact>(boto3generate_presigned_urlonS3Client.sdk, expiryOUSAST_TASK_TIMEOUT + 600);delivered(task)HEADs the tar;collect(task)downloads, extracts under the run dir with the member checksReceiver.accepthas, re-puts declaredoutputs[]as single objects, writesdone.json;put_input(name, path)for inputs no task produces;state.jsonmirrored toruns/<run>/state.jsonand seeded from it on a rerun.tests/test_plane_delivery.py: against a local S3-shaped fake (PUT/GET/HEAD by key); URLs never appear in logs; expiry; member rejection; declared-only re-put; state seeding.- Requirements: 3.1, 2.1
-
2.2 Runner:
deliveryin the start bodysrc/openultrasast/plane/runner.py:StartGate.offervalidates an optionaldelivery {put, inputs}(https, hostname, no echo);_deliver_outputPUTs the tar todelivery.put(same retry andDeliveryErrorsemantics);fetch_inputsGETs fromdelivery.inputs[name];OUSAST_ARTIFACT_URLandOUSAST_ARTIFACT_DIALstay honoured until task 2.6 so both paths run in the same build.tests/test_plane_runner.py: accepted body withdelivery; 400 on anhttpor IP URL; PUT to a local server; inputs fetched from presigned-shaped URLs; the marker and retry-only boot unchanged.- Requirements: 3.1
-
2.3 Egress without the receiver
src/openultrasast/plane/egress.py:policy_for(task, workspaces, model, store_host)emits onetlsPassthroughrule on 443 over the sorted union of store host, Git hosts, Model hosts; nohttprule; an IP store host raises namingS3_ENDPOINT.tests/test_plane_egress.py: for every committed template underplane/tasks/the rendered policy is the expected set; the IP refusal.- Requirements: 3.2
-
2.4 Reconciler on
Deliverysrc/openultrasast/plane/reconciler.py:run()opens the store first (no fallback), buildsDelivery, passesdeliverytorouter.starter/start_task(body extended inrouter.py),_awaitonDelivery.delivered, outcome from the collectedsummary.json; the receiver thread is kept behindOUSAST_DELIVERY=receiverfor this group only. Line bound per task 1.6.tests/test_plane_reconciler.py: the ax fake run completes through the delivery fake; a rerun from an empty results root resumes from the store'sstate.json.- Requirements: 2.1, 3.1
-
2.5 Kind proof and record 02
ops/ax/smoke-run.shwithOUSAST_DELIVERY=store:facts.jsonandsummary.jsonarrive through the store; the actor's policy read back has one TLS rule naming the store host;ousast plane status ax-e2e-smokeprints the table. Instrument: the tar's byte size from the store equals the extracted files' total.benchmarks/measurements/<date>-k8s-02-store-delivery/record.json: bytes, PUT-to-poll seconds, rules.- Requirements: 3.1, 3.2, 7.1
-
2.6 Remove the receiver (last; after 2.5 is recorded)
- Delete
Receiver,_Handler,receiver_address,receiver_cluster_ip,RECEIVER_HOST,RECEIVER_PORTfromegress.py;OUSAST_ARTIFACT_URL/DIAL/HOST/PORTfromrunner.pyandreconciler.py; theOUSAST_DELIVERYswitch;ops/ax/receiver-service.yaml.tmpland theup.shreceiver step; the EndpointSlice from kind (kubectl delete). Docs:docs/plane.mdsequence diagram,docs/deployment.md,ops/ax/README.md. - Tests using
OUSAST_ARTIFACT_HOSTmove to the delivery fake;test_plane_literalscovers172.19.. - Evidence:
ops/ax/smoke-run.shpasses again on the build without the receiver; recorded in 02's record asreceiver_removed: <commit>. - Requirements: 3.1, 1.3
- Delete
-
3.
--exec remotefrom a CI-shaped principal (design section 2; open-question check 1) -
3.1 Execution mode and RBAC manifests
cli.py:ousast plane run --exec local|remote(defaultprofile.exec);reconciler.Axpasses the profile's context toax(its context flag, read fromax --helpand pinned in a test);remoterefuses afile://store.ops/k8s/base/rbac-ci.yaml: SAousast-ciinax-system; Roles and RoleBindings inax-systemandate-systemwithpodsget/list,servicesget,pods/portforwardcreate, nothing else.tests/test_plane_rbac.py: fakekubectl,ax,kubectl-aterecord argv over a full fake Run; the verbs implied (port-forward->pods/portforwardcreate +podsget +servicesget) equal the rendered Role's rules exactly.- Requirements: 2.1, 2.2
-
3.2 Substrate's authorization of the SA token (open question 1)
ops/ax/probe-ci-token.sh:kubectl create token ousast-ci --duration=2hinto a kubeconfig;kubectl ate --token-file - get workersandcreate egress-policyon a throwaway actor with that kubeconfig. If refused, readsubstrate/docs/api-guide.mdfor the grant (atespace role or token audience) and add the manifest toops/k8s/base/rbac-ci.yaml; if no grant exists for a non-admin token, record it and the design's remote mode uses the maintainer's admin kubeconfig for the proof, with the gap listed in the runbook.- Evidence:
record.jsonnames the grant used (grant: <kind/name>oradmin-kubeconfig). - Requirements: 2.1
-
3.3 CI-shaped container
ops/k8s/ci-shell.Dockerfile:python:3.12-slim+ this package +kubectl,kubectl-ate,ax(built from the checkouts); no.env; runs with-e KUBECONFIG=/run/kc -e DEEPSEEK_API_KEY -e S3_*only..github/workflows/plane-remote.yml(dispatch only, no schedule): the same image runningousast plane run --exec remote --profile k3swith repository secrets; disabled until k3s exists (labelled in the file).- Requirements: 2.2, 2.3
-
3.4 Kind proof and record 03 paid: needs a budget go-ahead, ceiling 0.50 USD
- From the container of 3.3 against kind (
kind.tomlwithexec = "remote"): the smoke Run (model-free), then oneverifypass over one case ofplane/runs/validation-46.yaml(budget{usd: 0.5, calls: 60}) so the credential path is exercised remotely. Kube audit log (kind apiserver--audit-policyof the SA's verbs) lists only the Role's verbs. benchmarks/measurements/<date>-k8s-03-remote-exec/record.json: audit verbs vs Role, grant of 3.2, run status, usd spent.- Requirements: 2.1, 2.2, 2.3, 2.4, 7.1
- From the container of 3.3 against kind (
-
4. Images on GHCR (design section 8)
-
4.1
images.yml.github/workflows/images.ymlon tagsv*:docker/build-push-actionforplane/Dockerfile.runner->ghcr.io/norandom/ousast-runner:<tag>and the rootDockerfile->ghcr.io/norandom/ousast-engine:<tag>; digests as job outputs and as the release assetimages.json({"runner": "...@sha256:...", "engine": ...});GITHUB_TOKENwithpackages: write.- Evidence: a dry run on a
v*-rctag publishes both and the asset (the engine build is ~2.8 GB: record the build minutes). - Requirements: 1.1, 4.2
-
4.2 Engine image under the runner contract
- Root
Dockerfile: theax-task-runnershim and/workspaceasplane/Dockerfile.runner:25-29; thes3extra is not needed in either image (the runner PUTs by URL). tests/test_plane_images.py: both Dockerfiles declare the shim and entrypoint (text check).- Requirements: 4.2
- Root
-
4.3
repinfor two imagesgenerate.repin(plane, images):engine.yaml(added in 5.1; until then absent is fine) takesimages["engine"], every other templateimages["runner"];ousast plane repin images.json;--runner-image FILEkeeps the single-image file working.ops/k8s/profiles/k3s.tomlnamesimages.jsondownloaded from the release.tests/test_plane_increment.py: repin with a two-key file; a template with two image lines is refused.- Requirements: 1.1, 4.2
-
4.4 Kind proof and record 04
- Pools' pod template takes
imagePullSecretfrom the profile (ops/k8s/base/workerpool-*.tmpl, task 6.1 brings the full template; here the field only); kind pullsghcr.io/norandom/ousast-runner@sha256:...withghcr-pullif the package is private; the smoke Run passes on the GHCR image. benchmarks/measurements/<date>-k8s-04-ghcr-images/record.json: image sizes, pull seconds, visibility.- Requirements: 1.1, 4.2, 7.1
- Pools' pod template takes
-
5. The engine as a plane task (design section 4)
-
5.1
cpg/dump.pyandtasks/engine.py- Lift
benchmarks/push/finding_dump.py's scan intosrc/openultrasast/cpg/dump.py(the script becomes a thin caller;ops/README.md:78command unchanged).src/openultrasast/plane/tasks/engine.py: envOUSAST_WORKSPACE_DIR,OUSAST_FIXED_DIR,OUSAST_INPUT_CASE, pins,OUSAST_FAMILIES,OUSAST_DEADLINE; per pin the quick scan (tasks.alerts.scan) and the engine in-process; rows viaengine_alerts.engine_rows, the instrument checks viaread_record's rules;summary.jsonasengine_alerts.producewrites it withhost: false.plane/tasks/engine.yaml:command: ["engine"], requests 3Gi/1, limits 4Gi/2, image fromimages["engine"].generate.TEMPLATESgainsengine;--loopemitsengineinstead ofalertsfor a case needing the engine when the images file has an engine key. tests/test_plane_engine_task.py: a fixture record -> rows and summary equal toproduce's shape; the unread-input and zero-question failures; the generator's choice.- Requirements: 4.1
- Lift
-
5.2
engine-poolops/k8s/base/workerpool-engine.yaml.tmplrendered byplane/k8s.py(task 6.1 generalises; here the engine values): kind replicas 1, 4Gi/2; if 1.5 recordedany, apply the design's fallback and write it here.- Evidence:
kubectl -n ax-system get workerpool engine-poolready on kind withousast-poolat 1 replica. - Requirements: 4.1, 5.1
-
5.3
alerts-enginemarked development-onlycli.pyhelp,docs/plane.mdtask table,ops/README.md: "development only (needs Docker on the host); the plane task isengine". No code removed.- Requirements: 4.3
-
5.4 Kind proof and record 05
- The froxlor case of
validation-46as anenginetask on kind (model-free, deadline 1800 s per pin): rows equalbenchmarks/measurements/2026-09-30-php-engine-alerts/(6/6 per pin), placement onengine-poolshown bykubectl ate get workers, peak memory fromkubectl top. benchmarks/measurements/<date>-k8s-05-engine-task/record.json: rows diff, seconds per pin, peak memory, placement, image size and pull seconds.- Requirements: 4.1, 4.2, 7.1
- The froxlor case of
-
6. Pools, autoscaling, node checklist (design section 5)
-
6.1
plane/k8s.pyand the manifestssrc/openultrasast/plane/k8s.py:render(profile, out)writesworkerpool-default.yaml,workerpool-engine.yaml,ax-server-snapshots.yaml(patch withprofile.snapshots_bucket),rbac-ci.yaml; fork3salsoimagepullsecret.yaml,hpa-default.yaml,hpa-engine.yaml,prometheus-adapter.yaml(copied fromsubstrate/demos/autoscaled-workerpool, namespaceax-system, metricate_workerpool_workers{state=at_capacity},averageValue 0.7).ousast plane manifests --profile P --out DIR;ops/k8s/render.sh.ops/ax/workerpool.yaml.tmpldeleted onceup.shuses the renderer.tests/test_plane_k8s.py: both profiles render; kind has no HPA and no pull secret; k3s has both; engine pool limits >=engine.yamllimits; every image digest-pinned; no host literal in the output.- Requirements: 5.1
-
6.2 Node checklist and footprint
docs/deployment.md: the gVisor correction (no RuntimeClass;ateom-gvisorrunsrunscfrom the SandboxConfig tarball), PodSecurity for the worker pod's securityContext (recorded from kind), local-path for/var/lib/ate, the measured footprint per task type (verify,repo-facts,enginefrom 05) and the queueing consequence of one actor per worker with--workers.- Requirements: 5.2, 7.2
-
6.3 Kind proof and record 06 (HPA: render only; live is k3s-only, manual later)
render.sh kindapplied on kind replaces the hand template with identical pools (kubectl diffempty but for labels); worker podsecurityContextcaptured;render.sh k3svalidates withkubectl apply --dry-run=clientagainst kind's API (CRDs present).benchmarks/measurements/<date>-k8s-06-pools/record.json: footprint table, securityContext, dry-run result.- Requirements: 5.1, 5.2, 7.1
-
7.
ousast plane scan(design section 6) -
7.1 Workspaces and candidates from URL + commits
generate.scan_cases(url, base, head, cache, *, all=False): shallow clone into the case cache, two pinned Workspaces, head-side hunks viafix_ranges(side="new"), functions viarepo_facts.enclosingoverproduct_files, quick alerts viatasks.alerts.scanon the changed files,candidates.jsonandfunctions.jsonas store inputs (Delivery.put_input);--alltakes every product function.tests/test_plane_scan.py: a two-commit fixture repository yields exactly the changed functions; a rename and a deleted function are handled;--allcount.- Requirements: 6.1
-
7.2 Run composition and the command
src/openultrasast/plane/scan.py+cli.py:ousast plane scan URL --base C --head C [--all] [--ceiling USD] [--exec] [--wait S | --no-wait]: per setfacts -> va, vb -> agree -> vc -> final -> features -> remember,enginewhen a changed file's language needs it,rolesonce per repository when memory has norolesrows forrepo_key(url); budgets fromverify_budget; Run annotatedopenultrasast.io/population: scan,split: <repo_dir>; after the Run,memory.ingest, then the attribution table and the plane's agreement (final), the decision engine's verdict only when adopted.--no-waitprints the Run name and exits 0.tests/test_plane_scan.py: the rendered Run for the fixture; budgets sum under the ceiling;rolesincluded only when the store lacks rows; the report's verdict line.- Requirements: 6.1, 6.2
-
7.3 Hook opt-in
ops/pre-push: withOUSAST_PLANE_SCAN=1, after the blocking check,ousast plane scan <remote url> --base <old> --head <new> --exec remote --no-waitand the Run name in the report; the hook's exit code never depends on it.ops/README.mdanddocs/deployment.md: the latency split.tests/test_push_hook.py: the wrapper calls the scan only with the variable set and ignores its failure.- Requirements: 6.3
-
7.4 Kind proof and record 07 paid: needs a budget go-ahead, ceiling 3.00 USD
- A PHP pair from the development corpus (pmpro's CVE commit and its parent, never a v2 or v3 case) scanned on
kind: candidates, the Run through
final,enginefor the PHP files, rows underrepos/<host>__<owner>__<name>/<head>.jsonlon the store, the report; thenousast plane status. benchmarks/measurements/<date>-k8s-07-plane-scan/record.json: candidates, usd, calls, agreement, rows, seconds from submit to verdict, whether the known CVE site is among the agreed.- Requirements: 6.1, 6.2, 6.3, 7.1
- A PHP pair from the development corpus (pmpro's CVE commit and its parent, never a v2 or v3 case) scanned on
kind: candidates, the Run through
-
8. Runbook (design section 7, Req 7)
-
8.1
docs/deployment.mdas the runbook- Sections: prerequisites for k3s (Substrate, ax with the snapshots patch, GHCR and the pull secret, buckets,
the CI SA and its grant from 3.2, the node checklist of 6.2), the profile switch,
--exec remotefrom a laptop and from CI, and the checklist whose every item names its requirement and its record 01-07; items without a recorded run (HPA live, 1.7's fetch) labelled planned.ops/ax/README.mdandops/README.mdpoint to it; the "moving to a separate cluster" tables are replaced by the checklist. - Requirements: 7.2
- Sections: prerequisites for k3s (Substrate, ax with the snapshots patch, GHCR and the pull secret, buckets,
the CI SA and its grant from 3.2, the node checklist of 6.2), the profile switch,
-
8.2 Spec closure
design.mdopen questions updated with the outcomes of 1.5, 1.6, 3.2 (1.7 stays open until k3s); thekind-toolingmemory and.kiro/steering/overview.mdcurrent-state line updated;/kiro-spec-status.- Evidence: every record 01-07 linked from the runbook's checklist.
- Requirements: 7.1, 7.2