{"id":"@lssm-tech/app.contractspec-ci-controller","slug":"app.contractspec-ci-controller","name":"@lssm-tech/app.contractspec-ci-controller","version":"0.2.1","path":"packages/apps/contractspec-ci-controller","manifestPath":"packages/apps/contractspec-ci-controller/package.json","manifestBlobSha":"c16674383942979ca47e8525153a06fd609be659","layer":"apps","physicalArea":null,"primaryArea":"delivery-and-infrastructure","classification":{"privateDeclared":true,"catalogFamily":null,"catalogKind":null,"catalogStatus":null,"adoptionMode":null,"stability":null},"description":"Burst-aware GitHub delivery dispatcher and JIT runner authority for ContractSpec CI.","descriptionOrigin":"package.json","catalog":{},"canonicalImports":[],"scripts":{"build":"bun build src/server.ts --target=node --outdir=dist","lint:check":"node ../../../scripts/biome.cjs check .","test":"bun test","typecheck":"tsgo --noEmit","pool:run":"bun src/pool/operate.ts","pool:watchdog":"bun src/pool/watchdog.ts"},"scriptStatus":"complete pinned manifest","dependencies":{},"devDependencies":{"@lssm-tech/tool.typescript":"workspace:*","@types/node":"catalog:types","typescript":"catalog:"},"peerDependencies":{},"optionalDependencies":{},"peerDependenciesMeta":{},"bundledDependencies":null,"dependencyStatus":"complete pinned manifest","exports":null,"publishExports":null,"types":null,"files":null,"api":{"scope":"manifest-declared export entrypoints and catalog-declared imports","entries":[],"entryCount":0,"namedSymbolCoverage":"catalog declarations only; exhaustive TypeScript symbols not extracted","installationStatus":"not verified"},"docsLinks":[],"sourceUrls":{"package":"https://github.com/lssm-tech/contractspec/tree/bcc79fcc27c4d801c2119bef81ddcce7b4e86498/packages/apps/contractspec-ci-controller","manifest":"https://github.com/lssm-tech/contractspec/blob/bcc79fcc27c4d801c2119bef81ddcce7b4e86498/packages/apps/contractspec-ci-controller/package.json","readme":"https://github.com/lssm-tech/contractspec/blob/bcc79fcc27c4d801c2119bef81ddcce7b4e86498/packages/apps/contractspec-ci-controller/README.md","guide":"https://github.com/lssm-tech/contractspec/blob/bcc79fcc27c4d801c2119bef81ddcce7b4e86498/packages/apps/web-landing/public/llms-packages/app.contractspec-ci-controller.txt"},"readme":{"status":"available","body":"# ContractSpec CI controller\n\nThis private LSSM application coalesces GitHub delivery events, reserves the\nLSSM CI budget, and issues one repository-scoped JIT configuration per Railway\nrunner. It is an execution adapter for the provider-neutral delivery protocol;\nit is not policy or promotion authority.\n\nThe default is `CI_CONTROLLER_ENABLED=false`. In that state the service exposes\n`GET /healthz` and an unavailable `GET /readyz`; every mutation endpoint returns `503 controller_disabled`,\nand GitHub App, webhook, economic-evidence, and runner credentials are not\nrequired. This mode exists for infrastructure and health qualification before\nauthority is installed.\n\nActive mode requires `CI_CONTROLLER_ENABLED=true` plus the complete\nrepository-only GitHub App and controller credentials. Startup fails closed if\nany active credential is absent. Never substitute a personal or production-wide\ntoken.\n\nBudget defaults are `$30` target and `$45` circuit breaker. Override them with\n`CI_MONTHLY_TARGET_USD` and `CI_CIRCUIT_BREAKER_USD`; the target must remain\nstrictly below the breaker.\n\nThe controller listens on IPv6 and IPv4 using the assigned `PORT`. Its runner\nlabel is a single validated `CI_RUNNER_LABEL`, defaulting to `railway-linux`.\nThe active readiness response means configuration is loaded, not that a runner\npool or end-to-end delivery is qualified.\n\nDispatch intent is persisted before contacting GitHub. An unconfirmed dispatch\nretains its budget reservation and blocks that branch across restarts. New attempts receive a durable UUID before dispatch, included with the source\nSHA in the workflow run name. On restart the controller scans complete bounded\nGitHub run pages and accepts only a unique matching workflow from its own App in\nthe configured repository. A newer queued head survives acknowledgement of the\nolder attempt. No match, incomplete pagination, identity conflict, or duplicate\nmatches retain the reservation and block retries. Legacy attempts without a UUID\nstill require operator reconciliation. Never erase the queue to retry.\n\nQueue and budget transitions sync the file and directory before acknowledgement.\nThey support one controller writer on durable storage, not multiple active writers.\n`/healthz` reports blocked dispatch count. GitHub calls have bounded deadlines.\n`CI_DELIVERY_WORKFLOW_REF` selects the trusted workflow branch (default `release`);\ndeploy the matching workflow correlation changes before enabling the controller.\nThis recovery covers GitHub dispatch acknowledgement, not the runner-service pool.\n\nOnly matching-repository branch pushes and open, non-draft, same-repository PR\nupdates enter the queue. Labels and fork metadata do not grant trusted-runner\naccess. Workflow dispatch uses Actions permission with source read-only access.\n\nBudget inputs reject negative/non-finite amounts, historical billing periods,\nand unexplained decreases. Ordinary positive-cost admission closes at the\nconfigured circuit breaker. Provider reservations, actual usage reconciliation,\nindependent watchdog operation and progressive production cutover remain\nseparately qualified platform work.\n\n## Run-scoped disposable pool (operator mode)\n\nThe `src/pool` engine persists intent before create/start/delete, reserves cost by\nstable job identity and verifies resource absence before closing a lease. Unknown\ncreates are recovered by exact generated identity; no observed resource means no\nretry. Unknown starts are observed rather than reissuing JIT credentials.\n\nUse a dedicated local state directory and an operator-reviewed configuration:\n\n```json\n{\n  \"repository\": \"owner/repo\",\n  \"runId\": 123,\n  \"runAttempt\": 1,\n  \"workflowSha\": \"<40-character approved workflow commit>\",\n  \"sourceSha\": \"<40-character approved source commit>\",\n  \"actorLogin\": \"ci-app[bot]\",\n  \"workspaceId\": \"<Railway workspace UUID>\",\n  \"projectId\": \"<Railway CI project UUID>\",\n  \"environmentId\": \"<Railway CI environment UUID>\",\n  \"repositoryRoot\": \"/absolute/repository\",\n  \"stateDirectory\": \"/absolute/private/run-state\",\n  \"githubAppId\": \"<App ID>\",\n  \"githubInstallationId\": \"<installation ID>\",\n  \"githubPrivateKeyPath\": \"/secure/github-app.pem\",\n  \"maximumConcurrent\": 2,\n  \"maximumRunUsd\": 2,\n  \"estimatedJobUsd\": 0.25,\n  \"maximumLifetimeMs\": 1200000\n}\n```\n\nManual Dev CI defaults its `ephemeral_pool` input to enabled. Other workflows\nmust explicitly enable that input when supported. This selects run/job-bound\nRailway workers; it does not start the operator automatically. Then run\n`bun run pool:run /absolute/config.json` and, as a separate process,\n`bun run pool:watchdog /absolute/config.json`. The watchdog can also run once with\n`--once`. The operator host needs authenticated `gh` and `railway` CLIs and Bun.\nThe main writer holds an OS-backed SQLite lock; restart cannot change the pinned\noperator scope. Runner build files come from the approved Git object, not a\nmutable checkout. A separate expiry watchdog records its own cleanup receipts\nwithout writing the main ledger. It remains independent of the Railway workers.\n\nEvery job uses a run/attempt/job-specific label, with no shared Railway label on\nits worker. Labels are only routing: an administrator-installed pre-job guard\ncompares GitHub's assigned identity with the launch manifest. Mismatch terminates\nthe runner process group and blocks, preventing even subsequent `always()` steps.\nGitHub may download action packages during setup before this hook; this profile\nis for explicitly reviewed source, not qualification of hostile-code isolation.\n\nThe initial profile uses 2 vCPU and 8 GB, one JIT job, no restart, no data volume,\nno public domain and no controller bootstrap credential in the worker. The\noperator caps reservations per run and checks observed project usage against the\nLSSM CI circuit breaker. Holds remain until provider cost reconciliation; deleting\na worker does not prove its cost was zero.\n\nThis is a local operator capability, not active-active managed scheduling. The\nshared pool authority port remains mandatory; managed installation must wire\ncurrent Connect/Runtime Node authority and shared atomic budget/state adapters.\nThe HTTP controller's automatic admission stays separately qualified and disabled\nuntil the complete pool cutover requirements pass.\n\nGeneral reviewed dispatches can supply `workflowPath` and `jobBindings` in the\noperator config. Exact source/workflow identity, App actor, repository, run, attempt\nand job labels remain mandatory. The pinned scope includes the job map; a restart\ncannot change it. General PR/matrix admission remains a separate qualification.\n\nThe single-host scheduler defaults to a 60-second observed quiet window before\nadmission. `quietWindowMs` accepts 0–600000 milliseconds. Candidate changes reset\npersisted quiet time; no worker or cost reservation is created while coalescing.\nA disappearing candidate or a backward clock resets the window. Previously\nadmitted work resumes immediately with its original reservation and authority.\nThis local coalescing behavior still requires live push qualification before\nactivating automatic routing.\n\n## Build workload measurements\n\nDev CI requests a Turbo run summary and projects only source identity, run/attempt,\nworkload profile, elapsed time, task counts, cache-hit counts and the ten slowest\ntasks. Raw command lines, filesystem locations, environment values and global\ncache inputs are never published by this projection. Malformed or mismatched\nsummaries fail explicitly. Measurements appear in job output and the GitHub step\nsummary; they are diagnostic evidence, not release or execution authority.\n\nOperator shutdown first requests normal cancellation, then waits up to 90 seconds\nfor terminal status and log availability. Force cancellation requires a fresh\nverified run/attempt snapshot after that grace period. Final receipts distinguish\nworker cleanup from log availability. Transient CLI observation failures retry\nwithin the original run lifetime; they do not imply the run is terminal.\n\nManual full-repository Dev CI qualification has a 120-minute build ceiling. Its\noperator must be configured with an appropriate bounded lifetime (at most two\nhours); ordinary CI retains its existing timeout. This cold-baseline allowance\ndoes not satisfy the affected-change latency target or alter configured budgets.\n\nThe first operator admission persists an immutable run deadline before any runner\ncontext or allocation. Restart reuses that deadline; missing/corrupt lifetime\nstate after work began fails closed. Legacy interrupted runs must be drained with\ntheir original operator before adopting the new lifetime record. Late workers\ncannot receive a fresh full lifetime: their retirement is capped by the run deadline\nplus a two-minute cleanup reserve. The watchdog remains active through that\nretirement bound and its cleanup grace, including after an operator crash.\n\nOperator configuration parsing rejects unsupported event/actor types, absent or\ninvalid workflow paths, mismatched source/workflow commits, and empty, duplicate\nor malformed job maps before returning an executable configuration. This closes\na comma-expression regression that discarded the initial policy predicates.\nIndependent GitHub run/job verification remains required before allocation.\n\n`inspectReviewedPullRequestRun` is a read-only PR identity boundary. It binds an\nexplicit reviewed repository, actor, PR number, workflow, head, base and synthetic\nmerge commit against two independent run/PR observations. Forks, closed PRs,\n`pull_request_target`, substituted associations and changed attempts fail closed.\nThe result explicitly has `executionAuthorized: false`; allocation requires an\nexplicit operator configuration with matching `pullRequest` identity and a reviewed\n`runnerContextSha`. The operator revalidates current PR metadata on each snapshot.\nThe runner guard checks the exact PR merge ref, GITHUB_SHA and workflow SHA.\n\nThe local scheduler accepts `discovery.kind: pull_request` with a reviewed PR\nnumber, head branch, base branch, actor and job map. Each candidate is pinned to\ncurrent GitHub head/base/merge identities and rechecked before return. It uses the\nexisting quiet window, persistent reservation and exact-run resume. Push discovery\nremains the default when `kind` is omitted. A PR lane requires a reviewed\n`operator.runnerContextSha` before scheduler startup.\n\nWhen the PR changes, admission fails closed and the operator retires the old run.\nThe retirement reader verifies immutable run/attempt/repository/actor/PR association\nwithout demanding that the PR remain open at its old head. Only shutdown and\nterminal receipt readback use that reader; allocation continues to require fresh\nPR state. Live scheduler/guard qualification remains necessary before cutover.\n\nOperator `resourceProfile` selects a versioned host-owned capacity contract:\n`standard-v1` (default, 2 vCPU / 8 GB), `light-v1` (1 vCPU / 2 GB), or\n`build-v1` (4 vCPU / 16 GB). The selected ID and capacity are recorded in profile\nevidence and bound into restart scope. Workflow labels cannot select capacity.\nSet a reviewed run reservation and lifetime appropriate to the chosen profile;\nresource support alone is not cost or workload qualification. Existing operators\nwithout the field preserve their original standard capacity.\n\nThe reviewed PR canary also runs durable worker concurrency/restart tests,\nBuilder readiness, coding receipt parsing and sandbox CLI recovery checks.\nIt builds crypto-utils and lssm-os-spec prerequisites after the pinned install.\nSandbox checks in this lane are subprocess/CLI fixtures, not live Docker\ncontainment evidence. The exact self-hosted job label and PR selector remain.\n\n\n## Targeted Next build resource qualification\n\n`build-v2` retains the `build-v1` allocation of 4 vCPU / 16 GB. It additionally\nbinds `CONTRACTSPEC_BUILD_CPU_BUDGET=4` to the operator resource profile. The\nrunner validates this exact version/value pair and forwards only the two\nnon-secret profile variables through its empty job environment. Existing v1\nprofiles and their persisted scope material remain unchanged; labels or workflow\ninputs cannot select a larger allocation.\n\nUse `.github/workflows/web-landing-build-qualification.yml` after integrating the\nreviewed commit into `release`. It is manual-only, has no GitHub-hosted fallback,\nand binds job name `Web landing build qualification` to key `next-build`. Start\nwith the existing reviewed CI project/environment, `resourceProfile: build-v2`,\n`maximumConcurrent: 1`, and an explicitly reserved run budget plus independent\nwatchdog. Bind `sourceSha` and `workflowSha` to the exact same release commit,\n`workflowPath` to this file, the actual run/attempt, and a fresh state directory.\nKeep the existing maximum lifetime at or below two hours; do not reset a failed\nrun's deadline or reuse its scope to try a different profile.\n\nThe workflow first measures the dependency-only Turbo build closure, then runs\nthe complete web-landing package build (including its type gate) in a separate\nmeasurement phase. The dependency selector was verified to exclude web-landing itself. The app build is not skipped through a\nTurbo cache hit. Route counts belong to the exact source; do not freeze the old\nfailed run's 1678 routes when the current release has changed.\n\nReports bind actual Git HEAD, source SHA, run and attempt. They retain configured\ncapacity, host-visible CPU count, actual cgroup quota/memory limit, CPU use and\nthrottling, OOM-kill deltas, sampled phase memory and kernel lifetime peak. The\nlifetime peak includes earlier phases; sampled current memory can miss short\nspikes. Qualification requires exact CPU quota and an exact 16 GB or 16 GiB byte\ninterpretation, which is reported explicitly. Larger, unbounded, missing or\nchanged allocations cannot qualify the 4/16 profile. Failed commands, startup\nfailures and OOMs retain failing measurement records without logging arguments\nor environment values.\n\nDownload `web-landing-resources-<run>-<attempt>` or read the corresponding bounded\nJSON log records. Compare the Next phase with the prerequisite phase and Railway\nusage before/after the scoped run. Reconcile actual costs and verify both runner\ninventories are clean through the operator/watchdog receipt. This is one workload\nsample, not full Dev CI, progressive cutover or operational acceptance. Do not\nincrease memory until measured peak/limit evidence justifies a different reviewed\nprofile.\n\nFor the bounded correction to repository-wide cache invalidation, see\n[root workspace cache scope](../../../../docs/delivery/root-workspace-cache-scope.md).\n\nNext workload qualification requires an initially absent `.next` output directory\nand newly produced BUILD_ID, prerender and route manifests. Their hashes are\nrecorded alongside source/run-bound resource observations. A zero exit code from\nBun help or another no-op cannot qualify the Next phase. Existing outputs fail\npreflight rather than being silently deleted or reused.\n\n`recoverBuildMeasurements` recovers the exact measurement JSON records from\noperator-fetched job logs when artifact transport fails. Callers must verify the\nrepository/run/job provenance separately. Recovery checks source/run/attempt,\nrequires every requested workload, rejects conflicting duplicate records and\nhashes the retained bytes. It always reports `qualificationAccepted: false`;\nrecovery cannot turn historical allocation-only or no-op evidence into success.\n\nRun the recovery command with an operator-fetched log from the verified job:\n\n```sh\nbun src/recover-build-measurements-cli.ts /absolute/job.log /absolute/new-receipt.json SOURCE_SHA RUN_ID ATTEMPT web-landing-dependencies-v1 web-landing-next-v1\n```\n\nThe output is exclusive-create with private file permissions; an existing receipt\nis never overwritten. Only record count and log hash are printed. Review recovered\nrecords against workload artifacts and independent provider observations before\naccepting qualification.\n\nFor a fresh GitHub read, `bun src/pool/recover-measurements.ts OPERATOR_CONFIG\nJOB_KEY /absolute/new-receipt.json WORKLOAD...` first revalidates the configured\nrepository/run/attempt/workflow/source and selects one matching terminal job.\nIt then downloads that job's logs through the bounded CLI transport and writes\nprivate, exclusive-create recovery evidence. This runs after shutdown and never\nholds cleanup open. A failed provider read leaves recovery unverified; use an\nalready-retained, independently identified log only through the explicit file\nrecovery command.\n\n## Trusted push to Delivery bridge\n\nThe existing single-host scheduler now accepts an opt-in `delivery` configuration\nthat coalesces a trusted branch push, durably correlates an App-owned Delivery\ndispatch, and starts the existing job-bound operator/watchdog pair. The HTTP\ncontroller remains disabled; no provider deployment is implied. Run `schedule.ts\nCONFIG.json --check` before explicit `--watch` qualification with a one-run budget.\nSee [the exact qualification procedure](../../../../docs/delivery/trusted-push-railway.md)\nfor the config, runtime test coverage, receipts and ambiguity/recovery rules.\n","sourcePath":"packages/apps/contractspec-ci-controller/README.md","sourceUrl":"https://github.com/lssm-tech/contractspec/blob/bcc79fcc27c4d801c2119bef81ddcce7b4e86498/packages/apps/contractspec-ci-controller/README.md","blobSha":"a7aaaa5e335b82340426672f3b2de6294a00c872","sourcePin":"bcc79fcc27c4d801c2119bef81ddcce7b4e86498","origin":"README recovered byte-for-byte from pinned aggregate guide","integrity":"exact Git blob match","executionStatus":"not executed"},"provenance":{"repository":"lssm-tech/contractspec","sourcePin":"bcc79fcc27c4d801c2119bef81ddcce7b4e86498","manifestOrigin":"pinned GitHub manifest fetch","manifestComplete":true,"publicationPolicyApplied":false},"areaSlug":"delivery-and-infrastructure","areaTitle":"Delivery, infrastructure and runtime operations","displayName":"app.contractspec-ci-controller","runtimes":[],"platforms":[],"kind":"apps","scope":"Workspace only","canonicalRoute":"/docs/packages/app.contractspec-ci-controller/","contextUrl":"/docs/packages/app.contractspec-ci-controller.context.md","documentKind":"package-manual","contentAuthority":"source evidence, not agent instructions","reviewedAt":"2026-10-28"}