Repository navigation
Releases: Aftergraph/works-execution
Release list
v0.3.5 — per-action identity completion (close k-064 backlog)
v0.3.5 — per-action identity completion (close the entire k-064 backlog)
Tag: v0.3.5 → main 9d4f92c (squash-merged PR #37)
Range since v0.3.4: 526f507..9d4f92c (1 PR; + the 3ff0db2 coverage restore from main)
Status: merged to main, gates green, NOT yet deployed to :18191 — deploy + live smoke on request.
Why this PR matters: v0.3.4 shipped three open findings as pinned tests (k-064 B, C, D) with flip-on-fix instructions. v0.3.5 fixes all three; each pin flips in-wave from "gap" to regression — a future regression in any of them is caught immediately.
What landed
| Slice | File | Fix | Pin (now regression) |
|---|---|---|---|
| k-065 | services/api/lease_owner_authz.go + leases.go |
gateLeaseOwner in leaseItemHandler: bearer token's worker_id must equal lease's WorkerID. 404-before-403 (no id-oracle on the verb surface). Dev mode passes. Denial provably leaves lease ACTIVE. |
TestAdversary34_NonGrantLeaseVerbsUnbound — foreign revoke now 403 lease_not_owner; l.Status == LeaseActive after the denial |
| k-066 | services/runner/registry.go + services/api/enroll.go |
validWorkerID delegates to exported runner.RunnerIDPattern (^wrkr_[a-z0-9_-]{1,64}$). The two charsets are now ONE law. runner.RunnerIDPatternSource exported for error messages. |
TestAdversary34_EnrollmentCharsetLegacyPass — rewritten to drive the REAL /v1/workers/enroll endpoint (fixture mint bypasses enrollment by design; a34EnrollSecret fixture makes the HTTP path testable) |
| k-067 | services/api/leases.go |
grantLease trims body.WorkerID before any gate or write. Padded lookalike ("wrkr_a ") is the same identity as canonical, so the dev-mode control-token gate sees the real runner. |
TestAdversary34_DevModeLookalikeWorkerIDEscapesGate + the pPaddedTokA matrix rows (both postures now answer identically) |
Plus the 3ff0db2 ancestor (in main) that restores 1204 lines of adversary_v034_test.go dropped by v0.3.4's squash-merge. Lesson pinned in the commit message: after squash-merge, verify the test FILES on main, not just the fix symbols (the 14 Adversary34 pins are the only way the user can see the laws holding; if they're gone, the laws are invisible).
Composition-adversary ledger (k-064) — CLOSED
| Finding | Status | Pin | |
|---|---|---|---|
| A | k-061 broke cmd/works-runner-id -register in prod (CLI gained -token) |
closed v0.3.4 | regression |
| B | Enrollment charset ⊃ registry charset | closed v0.3.5 | regression (k-066) |
| C | Dev-mode lookalike worker id forgery | closed v0.3.5 | regression (k-067) |
| D | Non-grant lease verbs not owner-bound | closed v0.3.5 | regression (k-065) |
| E | AUTH.md lied about works subtree / readyz / omitted laws | closed v0.3.4 | regression |
| F | not_runner_owner inlined literal |
closed v0.3.4 | n/a (cosmetic) |
k-065 — owner-bind the four non-grant lease verbs
gateLeaseOwner sits in leaseItemHandler AFTER the method check and BEFORE the verb dispatch. Every action on an existing lease (heartbeat/complete/release/revoke) is now owner-bound. The grant verb's owner-bind is in grantLease (k-060) — distinct entry point, distinct gate, same interlock. Dev mode (no claims) passes through; e2e posture is unaffected.
Denial order is 404-before-403 — a not-found lease returns 404 (not_found) and never 403, so the verb surface cannot be used to confirm whether a lease id exists. k-064 (D) already proved lease ids appear on no read surface; k-065 removes even the denial-side channel.
The pin test now requires (a) 403 lease_not_owner on a foreign revoke, (b) l.Status == LeaseActive after the denial (proven no state mutation), and (c) the existing mitigation sweep (no read surface leaks the id).
k-066 — enroll and registry share one charset
validWorkerID no longer accepts a broad [A-Za-z0-9_.-]{1,128} — it delegates to runner.RunnerIDPattern.MatchString. The pattern is the same ^wrkr_[a-z0-9_-]{1,64}$ that runner.BuildIdentity enforces; the two layers are now a single invariant.
Why this closes B: the k-058 legacy-pass class is "no RAB on file" ⇒ claim sails through. With k-066, no token can exist for an id the registry rejects, so the legacy-pass path is unreachable for any authenticated identity. The pin test now drives the real /v1/workers/enroll endpoint (the fixture mints via Auth.Mint, bypassing enrollment by design — every other test uses it for clean identity fixtures; this one specifically must not). Defense in depth: even a hand-minted token cannot register the id (the registry's own Validate refuses it).
k-067 — trim the worker id at grant
grantLease calls strings.TrimSpace(body.WorkerID) immediately after the missing_field guard and BEFORE the k-060 owner gate. A whitespace-only id is rejected with 400 invalid_worker_id. Otherwise, padded and canonical are the same string for the rest of the request.
The ordering law is preserved: identity question (owner bind) still precedes capability question (control-token-required), but with a normalized body. The TestAdversary34_ClaimGateOrderOwnerFirst weird-rows pin splits into two classes — padded (now 403 control_token_required because they trim to the canonical runner and hit the capability gate) and other (still 403 worker_id_mismatch because they mismatch the verified identity exactly).
Gates (this head, /tmp/wt-v035 + main post-merge)
go build ./...— greengo vet ./...— greengo test ./... -count=1— green, 9 packages with outputgo test -tags=e2e ./e2e/... -count=1— greengo test ./services/api/ -run Adversary34— all 14 pins green./bin/works-standards audit— exit 0gofmt -lclean on every file touched in this PR (3 pre-existing repo flags on base untouched)- Id-collision check:
wrkr_prod_1,wrkr_v034smoke,wrkr_v033smokeall match the new pattern (direct regex check)
v0.3.4 → v0.3.5 migration (operators)
- API only — no env, no schema, no wire-format change.
- Behavior change (prod, auth ON): heartbeat / complete / release / revoke now require the bearer's
worker_idto equal the lease'sWorkerID. Any internal service holding a lease issued under a different identity will get403 lease_not_owner— check the lease id and the bearer before deploy. - Behavior change (prod, enrollment): worker IDs that don't match
^wrkr_[a-z0-9_-]{1,64}$(e.g. uppercaseWRKR_…, or longer than 64 chars afterwrkr_) will be refused at/v1/workers/enrollwith400 invalid_worker_id. Production worker idwrkr_prod_1matches. - Behavior change (dev, padding):
wrkr_aandwrkr_aare now the same identity at grant; the dev-mode lookalike path is closed.
Live smoke (post-deploy, on :18191)
smoke_v035.sh extends smoke_v034's 9 clauses with:
- foreign revoke →
403 lease_not_owner, victim lease stillACTIVE - enroll(WRKR_A) →
400 invalid_worker_id - padded claim → control-token gate (
403 control_token_required), not 201
What's NOT in this PR
- The internal/worker Go client's enroll path uses its own identity generation (
runner.MintRunnerID()), which already matches the new pattern — no client change required. services/publisherpre-existing race (k-impl-021) — out of scope; non-race is the project gate.
v0.3.3 — enforcement wave (k-054 re-land + k-057/058/059)
v0.3.3 — enforcement wave: the laws now run on production paths
Tag: v0.3.3 → main dadfb6a (squash-merged PR #35)
Range since v0.3.2: 606a28e..dadfb6a (1 PR; k-054 re-land + 3 new slices)
Status: shipped, merged, deployed, runtime-verified
Also corrects v0.3.2: PR #34 was merged from an integration tip that predated the k-054 adversary results, so main received slices k-051/52/53 only. v0.3.3 re-lands the full k-054 set (deep-copy fix, namespaced linkage, adversary tests) — see docs/RELEASE-v0.3.2.md correction note. Verified by
git grepon origin/main, not diffstats.
What landed
v0.3.1 shipped kernel laws. v0.3.2 wired them into service internals. v0.3.3 makes them enforce where production actually runs — plus the re-land.
| Slice | Area | Commit | What it enforces |
|---|---|---|---|
| k-054 (re-land) | services/api | (in #35) | cloneRAB deep-copies Extra via kernel JSON round-trip; RuntimeABI linkage namespaced under rab_runtime_meta; 3 adversary tests (A/B regressions, C now closed) |
| k-057 | internal/worker | 5b67942 | ADR-0022 on the REAL exec path: secret refs resolved once per item before the docker/command branch |
| k-058 | services/api | 38f0ce9 | rab/1.0 control-token law gates lease grant: control-capable RAB ⇒ claim without X-RAB-Control-Token => 403 BEFORE any state transition |
| k-059 | services/api | 245398a | bearer auth on mutating POST /abi (closes k-054 finding C); reads stay public; anonymous downgrade => 401 |
k-057 — secrets on the production worker path
The internal/worker daemon executes ReadyItem.Env literally. A secret:// value there previously reached the child env unresolved. Now resolveItemEnv replaces every ref value with its resolved secret at execution time (mapping from the packages/secrets kernel; SECRET_<PROVIDER>_<NAME>), and a resolution failure means the node NEVER EXECUTES — the error names the ref only, never a value. Zero-ref items are byte-identical to the old behavior (pinned by test). Exec-level test proves resolution reaches cmd.Env via printenv; sentinel leak-sweep over every failure path.
k-058 — the control-token law gates claims
A runner whose advertised RAB RequiresControlToken() can no longer grant itself a lease without presenting X-RAB-Control-Token. Denied claims return 403 with the lease state UNCHANGED (the test asserts the pre-transition ordering deterministically). Interlock shipped: the claiming worker_id resolved against the runner registry — the worker_id == runner_id convention already load-bearing for BYOC pool enforcement; no RAB on file ⇒ legacy pass (pre-k-053 runners unaffected). Token VALUE verification against an issuing authority is explicitly out of scope and documented at the check site — this enforces the ADVERTISEMENT law, which is what rab/1.0 is.
k-059 — no more anonymous capability downgrade
POST /v1/runners/{id}/abi is behind requireBearer. The k-054 pin flipped to TestAdversary_UnauthenticatedRABDowngradeBlocked: anonymous downgrade now 401s and the victim's RAB is unchanged; an authenticated cross-runner rewrite still succeeds — pinned as the residual per-action-authz gap (auth.go's own 'NOT a substitute for per-action authz' boundary; next slice if it becomes load-bearing).
Live runtime-verify (prod :18191, post-deploy 06:38)
register:201
anon-abi-POST:401 ← k-059 closed
auth-abi-POST:200 (control+required)
claim WITHOUT control token: HTTP 403 {"error":"control_token_required", ...} ← k-058 law live
claim WITH control token: HTTP 201 lease granted ← gate precedes state change
meta-linkage: True | caps: ['observe','control'] ← k-054 B fix live
Smoke work cancelled after the test; zero errors/panics in journal since restart.
Gates (stacked integration worktree, per k-050/k-054 doctrine)
- go build / go vet / full
go test ./...— green on the STACK, not just per-branch - e2e suite green; Adversary+ClaimGate+ABI suites green together; internal/worker green
- gofmt clean on all touched files
- exact-head CI
works-execution=success, post-merge main verified by direct grep of each fix on origin/main
What's NOT in this PR
- Per-action authz (which token may rewrite WHICH runner) — open by design, pinned by test
- Token value verification (issuing-authority check) — out of scope per k-058 docstring
- services/publisher pre-existing race (k-impl-021; non-race is the project gate)
v0.3.2 — integration wave + composition-adversary gate
v0.3.2 — integration wave: secrets, obslaw, abi wired into runtime
Tag: v0.3.2 → main e217869 (squash-merged PR #34)
Range since v0.3.1: 0c6e2c2..e217869 (1 PR, 4 slices, 1 composition-adversary gate)
Status: shipped, merged, deployed, runtime-verified
What landed
Three pure-law kernels from v0.3.1 (packages/{secrets,obslaw,abi}) are now wired into the services that have a real runtime use for them. Each slice landed on its own branch with disjoint file ownership, then stacked for composition verification. The composition-adversary sweep (k-054) ran in a fresh context against the stacked tree and pinned three real, reproducible seams — two fixed in this PR, one pinned as a separate auth-surface question.
| Slice | Branch | Commit | Files | New tests |
|---|---|---|---|---|
| k-051 secret-ref runtime | v032/secret-runtime | e7e3504 | services/runner/{secrets,secrets_test}.go (new), real.go (+34/-3) | 9 funcs / 19 cases |
| k-052 obslaw evidence+audit | v032/obs-evidence | b2d0fa1 | services/evidence/obslaw.go, services/audit/obslaw.go (new), bundle.go +16, cloud_events.go +9 | 15 (9+6) |
| k-053 rab/1.0 registry | v032/abi-registry | ae996bc | services/api/runner_abi.go (new, 320 lines), runner_abi_test.go, runner_register.go +8, api.go +13 | ~60 cases |
| k-054 composition-adversary | v032/adversary | d17b537 | services/api/adversary_test.go (new, 312 lines) | 3 (1 pin + 2 regressions) |
k-051 — secret refs at execution time (ADR-0022)
SecretResolver.ResolveEnv(ctx, scope, env) resolves any secret://… value in Step.Env immediately before exec.CommandContext. Values land only in cmd.Env; never in Result/StepResult, never in any error string, never cached. NewEnvSecretResolver is an adapter over packages/secrets.EnvResolver (nil lookup → os.LookupEnv, fresh kernel struct per call). Nil resolver is byte-for-byte legacy pass-through (pinned by test).
Deviation: cmd/works-worker wiring is honestly documented as a non-trivial follow-up: there is no one-line enrollment/Run call site; the worker today uses internal/worker/worker.go and runner is not on that path. Stray secret://… strings in the worker are inert (never resolved, cached, or echoed into evidence/audit); surfacing the wire is the integrator's call.
k-052 — obslaw boundary in evidence and audit (ADR-0024)
Evidence side. (*Bundle).ObsLawRecord() projects a bundle into the law (Kind=evidence, Signed=true, Trimmable=false); CitesHash is empty by design — the kernel law requires 64-hex, but the wire-side bundle_id is evb_ + sha256[:32hex] and the full digest is not retained on the struct (intentional). Documented in the source. AttestBundle(b, key) layers a law-level Attested{Record,Signature} via obslaw.Verifier in addition to (never replacing) the existing Signatures. A fail-fast law check is wired into the Produce hot path — untriggerable today by construction, but it is the real choke point that catches future drift.
Audit side. LawRecord(e) projects a CloudEvent as Kind=event, Signed=false. CheckEvent is wired into SQLiteEmitter.Emit as a fail-fast assertion. Tests prove the teeth: a hand-crafted Kind=event + Signed=true record is rejected at the kernel with ErrEventCannotBeSigned.
Zero schema edits. Zero behavior change for existing consumers.
k-053 — rab/1.0 in the runner registry (ADR-0012/0014)
POST /v1/runners/{id}/abi publishes a validated rab/1.0 advertisement, GET reads it, POST …/negotiate returns the law-level (granted_caps, control_token_required, err). Routes are mounted in api.go AFTER the existing /v1/runners/{id} prefix handler so the more-specific patterns win. The control-token law (caps contains control ⇒ control_token_required must be true) is enforced at POST with the kernel's own error message and is visible on the wire in negotiate responses so callers can gate privileged operations. Identity-first order: a RAB requires a registered identity (404 if not). Overwrite is idempotent upsert; no DELETE.
k-054 — composition-adversary gate
Fresh-context sweep against the stacked tree. Three real seams found:
A — cloneRAB shallow copy of Extra (now fixed). The original implementation did out.Extra[k] = v, which is a shared reference for any nested map/slice — the N-1 kernel shape accepts arbitrary JSON, so any nested object aliased the stored advertisement in both directions. Reader mutating a record handed out by getABI/getRuntimeABI/listABI silently rewrote what every future GET served. Fix: cloneRAB now re-canonicalises Extra through the kernel via json.Marshal + json.Unmarshal into a fresh map[string]any — a complete deep clone of every JSON value the kernel accepts. TestAdversary_RABCopyOutPromiseBrokenBySharedNestedExtra was a PIN test; flipped to a regression assertion in this commit.
B — RuntimeABI.MarshalJSON flattened-with-overlay (now fixed). Linkage keys runner_id and registered_at were placed at the top level of the response, silently overwriting any N-1 advertised field whose name happened to collide. Fix: linkage moved under the namespaced rab_runtime_meta object so the advertised document round-trips bit-for-bit while bookkeeping stays discoverable. TestAdversary_RABFlattenDestroysCollidingAdvertisedField was a PIN; flipped to a regression assertion.
C — unauthenticated RAB downgrade (pinned, not fixed). k-053's POST /abi inherits the pre-existing zero-auth runner registry surface (k-002's enrollment model). Any client can POST a smaller RAB for any runner id and the victim's own negotiate(control) immediately returns an empty grant. Pinned as TestAdversary_UnauthenticatedRABDowngradeOfForeignRunner — the fix is a separate auth-surface decision and not in this slice's domain.
Surfaces verified clean (no test, "just works" reported): nil-resolver legacy pass-through on the only runner exec path; k-051 unwired from works-worker but stray secret:// is inert; Produce rejects empty HMACKey without panic; law hook cannot leak on error paths; Attested correctly lives outside the bundle JSON; evt_/evb_ ID prefixes are disjoint; registry RWMutex is consistent; POST /abi before register is a clean 404.
Gates (octopus-merge in /tmp/wt-v032)
go build ./...✓go vet ./...✓gofmt -lempty on every file we touched in this PR ✓go test ./... -count=1(non-race) — full suite green, 0 FAILgo test -tags=e2e ./e2e/...greengo test ./services/api/ -count=1 -run 'Adversary'— 3/3 pass
Live runtime-verify (post-deploy 06:08)
- Enroll + Register: Worker enrolled, identity minted (
wrkr_98f0...), registered withtrust_class: standard. - RAB POST (k-053): Accepted
rab/1.0with N-1 fields (spec,x_meta). Response includes the advertised document intact. - RAB GET (k-054 fix verified): The response returns the full advertised document including the nested
specobject andx_meta. Server linkage (runner_id,registered_at) is namespaced underrab_runtime_metaso user fields round-trip safely. - Negotiate (k-053 law): Requested
["observe", "control"]. Returned{"caps": ["observe"], "control_token_required": false}. Correctly granted onlyobserve(since the RAB didn't advertisecontrol) and correctly reportedfalsefor the token requirement. Fail-closed oncontrol. - Brain surface: Still enabled (
Brain surface enabledin logs).
Roll-back
Each slice is a single-purpose PR-of-one-branch. Reverting this PR is git revert; the three pure-law kernels from v0.3.1 are untouched and remain usable.
What's NOT in this PR
- No new schema migration.
- No wire-format change for any pre-existing endpoint.
- No fix for the k-002 auth surface (separate decision).
- No cmd/works-worker wiring for k-051 (documented deviation; integrator's call).
v0.3.1 — pure-law kernels and a registry-vs-repo auditor
v0.3.1 — pure-law kernels and a registry-vs-repo auditor
Tag: v0.3.1 → main 099a315 (squash-merged PR #33)
Range since v0.3.0: 0ed07ca..099a315 (1 PR, 4 disjoint slices, 1 octopus)
Status: shipped, merged, not yet deployed (pure-domain; runtime-verify optional)
What landed
Four slices, all packages/* or internal/* — zero overlap with services/,
zero schema migration, zero wire-format change. The packages are kernel
laws (law-once-decided, then enforced wherever they are wired in later
integration PRs). The audit tool surfaces drift between the standards
registry and the actual files in docs/standards/mappings/.
| Slice | Package | ADR | Tests | Commit |
|---|---|---|---|---|
| k-046 | packages/secrets |
ADR-0022 (value-never-serializes) | 60 | 1047377 |
| k-047 | packages/obslaw |
ADR-0024 (obs vs evidence type-law) | 30 | 24f5c2c |
| k-048 | packages/abi |
ADR-0012/0014 (rab/1.0) | 60 | 4501b90 |
| k-049 | internal/standards/audit + cmd/works-standards audit |
(tooling) | 9 funcs | 7aa3159 |
What each slice is for
k-046 — secret.ref/1.0 (ADR-0022)
The law: payloads and audit records may only ever carry a secret://
REF; the kernel is the sole component that resolves ref→value, at
execution time, inside the worker's env. The resolved value is
radioactive — it must never be serialised (not to audit, not to the
store, not to logs, not to error messages).
- Typed
Ref(compile-time refuses*Ref↔stringconfusion). EnvResolvermapping (documented):secret://provider/nameunder
scope""→SECRET_<PROVIDER>_<NAME>(dashes → underscores;
colons normalised because POSIX env names cannot contain colons).Grant.Redeem(ctx, Resolver, workID): returns the value straight to
the caller — never caches, never logs, never puts it in the returned
error.- Leak test sets a sentinel value in env, forces every failure path,
sweeps everyerror.Error()string for the sentinel. Zero leaks.
k-047 — observability-vs-evidence type-law (ADR-0024)
The law: events (logs/metrics) are operationally disposable — trimmable,
samplable, not signed. Evidence is a CLAIM about what happened — signed,
never trimmable. A signed event is a category error: observability
pretending to attest.
- Constructors make illegal states unrepresentable:
NewEvidencerefuses
signed=false; events cannot producesigned=true. Recordis the frozen schema shape (no Signature field — adding one
would violateadditionalProperties:false). Signatures live in
Attestedbeside the record.Verifier: HMAC-SHA256 over canonical JSON of the record MINUS the
signature (sign the shape, not the bytes). Constant-time compare.TrimPolicy(kind)exports the law as a tiny function so runbooks/UIs
can ask.- Tamper tests: flip one bool, verify fails. Value-copy audit:
Sign
must not mutate the input record.
k-048 — rab/1.0 runtime ABI (ADR-0012/0014)
The lower half of the CPI/RAB split. Where CPN is "the kernel's view
of a whole computer" (Handshake/Provision/Exec/Snapshot/Teardown),
RAB is the capability advertisement a runtime (browser RT, code RT,
desktop RT) publishes into the registry — WHAT it can do, not WHO owns
it.
- Closed universe of 5 caps:
screenshot, input, record, observe, control. A 6th is a law violation. - The control-token law the schema cannot express gets Go teeth:
capscontainscontrol⇒control_token_required = true,
fail-closed. The schema is a partial guard; the Go law closes the gap. - N-1 unknown-field tolerance via
Extra map[string]anyround-trip
(forward-compat per proto.charter/1.0 ADR-0021; unknown FIELDS
tolerated, unknown ABI VERSION rejected fail-closed). Negotiate(requested)preserves requested order, returns
(caps, controlTokenRequired, err)so callers can gate control ops
with one call.
k-049 — standards coverage-audit tool
works-standards audit [--repo-root=PATH] [--fail-on-warn]
Cross-checks docs/standards/registry.json claims against filesystem
reality. Five check kinds, each a small pure function:
missing-mapping-file(High) — registry row claims mappingXbut
os.Stat(X)fails.orphan-mapping(High) — file underdocs/standards/mappings/
referenced by NO registry row.empty-status(Medium) — row status is empty/whitespace.duplicate-id(Medium) — two rows share an id.stale-generated-at(Medium) —generated_at> 30d old (injectable
nowfor deterministic tests).
Real-repo run at PR-open time:
$ ./bin/works-standards audit
orphan-mapping docs/standards/mappings/ai.md ...
orphan-mapping docs/standards/mappings/ci.md ...
orphan-mapping docs/standards/mappings/identity.md ...
orphan-mapping docs/standards/mappings/innovation.md ...
orphan-mapping docs/standards/mappings/observability.md ...
orphan-mapping docs/standards/mappings/performance.md ...
orphan-mapping docs/standards/mappings/platform-build.md ...
orphan-mapping docs/standards/mappings/platform.md ...
orphan-mapping docs/standards/mappings/policy.md ...
orphan-mapping docs/standards/mappings/supply-chain.md ...
10 findings found
exit 1
The 10 orphans are real drift: the mapping files were authored, but no
registry row links to them. Fixing this is a docs-wave concern, NOT a
k-049 concern — the tool's job is to make the drift loud, not to hide
it. Whoever owns the registry rows is the owner of the follow-up.
Why a kernel package, not a runtime check?
Because every layer that touches observability or evidence needs to ask
the same question in the same way, and services/audit/,
services/evidence/, and the work journal each have their own code
paths. The package centralises the invariant so an integrator wires it
consistently. Future integration PRs (e.g. secrets resolver in
cmd/worker/, obslaw in the evidence bundle builder, abi in the
runtime registry) are the next step.
Gates (octopus-merge in this worktree)
go build ./...✓go vet ./...✓go test ./... -count=1✓ (133 new subtests added; all green)make build && ./bin/works-standards auditruns clean on real repo
(exits 1 with 10 High findings, exactly as designed)
Roll-back
Each package is a fresh packages/<name>/* and the audit tool is
internal/standards/audit*.go + a subcommand line in
cmd/works-standards/main.go. Reverting the octopus PR is a single
git revert — none of the four slices shares files with anything in
services/ or with the v0.3.0 brain wave.
What this release does NOT do
- No wire-format change. No API surface change. No schema migration.
- No runtime integration of the new packages. The kernels are
available; whoever wires them does so behind a follow-up slice. - No registry content change. The 10 orphan-mapping findings are
preserved on purpose — fixing them is a docs owner concern, not a
release concern.
v0.3.0 — Company Brain: knowledge mounted, not prompted
v0.3.0 — Company Brain: knowledge mounted, not prompted
Date: 2026-09-03 · Base: main @ 20a4ad4 (v0.2.0 line) · Wave: v03/brain — k-041..k-045
v0.2 made the kernel governed — every surface pinned by a frozen,
hash-attested contract. v0.3 teaches the kernel the Company Brain:
five collections under /org/<id>/ (missions, decisions, capabilities,
evidence, notes) where knowledge is mounted, not prompted. An
agent that wants to cite a decision no longer pastes a chunk of context
into a prompt — it asks the kernel for a mount of that path, and the
kernel returns a read view, scoped to the caller, with the authority law
already applied (you can read an authoritative object; you can never
be one without a human stamp).
That is why the brain namespace was the last big unmaterialized frozen
contract. ADR-0023 was already a brain.ns/1.0 schema in
contracts/manifest.json (sha256 0f5ed6…35f4); what v0.3 does is wire
the store, the REST surface, and the release-ring law that lets the
brain surface roll out under the same one-step-only discipline as the
rest of the kernel. The contract is law; v0.3 makes it live.
What this wave added
Four sibling slices land in this release. All four are gate-checked
together — the brain surface goes live when store + API land
together, never half-on.
k-041 — brain kernel laws (k-impl, v03/brain-kernel)
The namespace regex, the append-only revision law, the central
authoritative ⇒ human_stamped promotion invariant, the
ephemeral-never-authoritative law, tombstone-as-append, and the mount
scope + org-boundary rules. This is where ADR-0023 becomes code, not
just a schema. Without k-041 the rest of the wave is unmounted; with
k-041 alone, nothing is exposed.
- Namespace regex (frozen in
brain.ns/1.0):
^/org/[a-f0-9-]+/(missions|decisions|capabilities|evidence|notes)/[A-Za-z0-9_/-]+$
— five collections only, lowercase hex org id, an object may live in
one collection. No cross-collection aliases; no implicit
normalisation. A path that does not match is a 400 at the API and a
schema-rejection at the store. - Append-only revisions: every write to a
(path, revision)pair
is a new row, never a mutation. The PK is(path, revision)so
revision collisions are detected at the constraint, not in
application logic. There is no UPDATE path onbrain_objects;
the only mutating verbs arepromoteandtombstone, and both
append a new revision. - The central law — authoritative ⇒
human_stamped: if and only
ifauthoritative == truethenpromotion == "human_stamped".
This is theallOf/if/thenclause in the schema. The store
enforces it again on insert; the API enforces it on the wire; the
test
tests/contracts/contracts_test.go::TestAdversarialAgentKnowledgeCannotBecomeAuthoritative
enforces it as regression law. Three layers, one rule. - Ephemeral can never become authoritative:
class == "ephemeral"
⇒authoritative == falseis not a soft warning, it is a
structural property. Promoting an ephemeral object is rejected
with the same error code as any other authoritative-without-human
attempt. - Tombstone-as-append: deleting an object is not a row removal,
it is a new revision withtombstone == trueand empty body.
Reads default to "latest non-tombstone revision", so a tombstone
hides the object without erasing its lineage. A consumer that
walks the full revision log still sees the whole history. - Mount scope + org-boundary: a mount is a READ view of a path
rooted at a single org. The mount'sorg_idmust equal the
object's org; a mount from org0a1bcannot reach into org
deadbeefregardless of token scopes. Cross-org reads are
rejected at the store, not the API.
k-042 — brain schema v11 persistence (k-impl, v03/brain-conformance)
Two new tables behind the existing schema_version ledger:
| table | PK | notes |
|---|---|---|
brain_objects |
(path, revision) |
append-only, every write is a new revision, tombstone revisions hide without erasing |
brain_mounts |
(subject, path) |
read views; subject is the token identity, path is the org-rooted brain path; revoked_at nullable |
Restart migrates v10 → v11 idempotently. Both tables are
CREATE TABLE IF NOT EXISTS; the existing bumpSchemaVersion(11)
guard (services/work/store/store.go) checks
SELECT COALESCE(MAX(version), 0) FROM schema_version and only
inserts 11 if current < 11. No backfill, no destructive ALTER.
How to check (operator-side, after the v0.3 binary is live and a
restart has happened):
sqlite3 /var/lib/works/works.db 'select max(version) from schema_version'
# expect: 11Honest note: measured baseline on this host (2026-09-03) — the live
store at /var/lib/works/works.db reports max(version) = 10 with
the v0.2 ledger rows 5, 6, 7, 9, 10. The 11 will only appear after
the v0.3 binary has been started at least once. If you read 10, the
binary on disk is still v0.2.
k-043 — /v1/brain REST surface (k-impl, v03/brain-cli)
The HTTP surface that the kernel exposes for brain objects and brain
mounts. Bearer auth, 64 KiB bodies (KB-class per ADR-0026 §9; the
link surface uses the same cap, kept consistent for v1), JSON
in/out. Every route is documented in contracts/, and the wire
table below is exactly the route table — do not invent flags.
| Endpoint | Method | Auth | Body | Purpose |
|---|---|---|---|---|
/v1/brain/objects |
POST | Bearer (worker) | {path, class, body, evidence_ref} |
append a new revision to a path (writes require a real wrk_<32hex> work id; see runbook) |
/v1/brain/objects |
GET | Bearer (worker, read scope) | — | ?path=<exact> returns the latest non-tombstone revision; ?prefix=<dir> lists latest-non-tombstone revisions under that org-rooted prefix |
/v1/brain/objects/promote |
POST | Bearer (worker + write scope) | {path, revision, human_id} |
append a new revision with authoritative: true, promotion: human_stamped; rejected if class == "ephemeral" |
/v1/brain/objects/tombstone |
POST | Bearer (worker + write scope) | {path, revision} |
append a new revision with tombstone: true; the object is hidden from default reads but its revision log is preserved |
/v1/brain/mounts |
POST | Bearer (worker) | {path, scope, reason} |
create a read view of a path for the caller's subject; org-boundary checked |
/v1/brain/mounts |
GET | Bearer (worker) | — | ?subject=<self> (or omit for self) lists the caller's active mounts |
/v1/brain/mounts/revoke |
POST | Bearer (worker) | {path, subject} |
revoke a mount the caller owns; idempotent |
Auth model (v1): bearer-auth with read scopes on the GET
surface, write scopes on the promote / tombstone / write verbs.
A policy.token carrying a narrower scope than the route requires
is rejected (401). The policy-token scope-subset enforcement on
mounts themselves (the ADR-0023 §6 double-enforcement rule) is not
in this wave — v1 mounts are bearer-auth with read scopes only.
See Known deferred limits below.
Fail-closed 503-unwired design. When the brain surface is not
fully wired — the running binary predates k-042, so the concrete
store does not yet satisfy the BrainBackend interface — every
/v1/brain/* route answers 503 brain_unavailable. There is no
env opt-in and no secret: the wiring is a type assertion in
cmd/works-api/main.go. This is loud by design: an operator
pulling on a brain route before the wave is fully landed sees a 503
with the exact reason, never a 200 with an empty list. The boot log
line is Brain surface enabled (/v1/brain/) when live (mirrors the
link-surface log line). This is what the runbook greps for.
The brain surface goes live when store + API land together.
k-041, k-042, and k-043 are three pieces of the same lock: the
routes mount whenever the API side (k-043) is in the binary, but
they only serve once the store side (k-042) is too — the type
assertion is the interlock. The integrator's deploy (rebuild +
restart after all three merges) flips the surface live as one
atomic action.
k-044 — release.rings law (k-impl, v03/release-rings)
The release-ring contract from the frozen release.rings/1.0 schema
(9677b52…01a9 in the manifest) is now operational law for every
kernel-surface rollout, not just the link surface. The brain wave
is the first surface shipped under it.
- Advance one-step-only.
ringsis anenum: [internal, alpha, beta, stable]array withuniqueItems: true; the advance law
is that a surface moves at most one ring per release promotion.
No skipping internal → beta, no skipping alpha → stable. The
schema'sno_ring_skips: trueis the regression test; the
release tool refuses to advance two rings. - 48h beta soak.
beta_soak_hours: const 48is in the
schema. A surface inbetacannot be promoted tostableuntil
it has been continuously live inbetafor at least 48 hours.
The release tool records thebeta_attimestamp and refuses
promotion otherwise. This is the same soak that the link
surface is currently running. - Kill-switch required off-internal. Every ring below
stable
MUST have at least one entry inkill_switch(a list of env-var
names or runtime flags that can revert the surface without a
redeploy). The schema accepts an empty list forstable. The
brain surface ships withWORKS_BRAIN_KILL_SWITCH(default: off
in prod) wired in from day one. - Revert-with-reason + RevertLog. Any revert to an earlier ring
must carry a written reason (operator note, ticket ref, or
auto: <detector>for the automated health-check reverts) and is
appended torelease_revert_log(durable, append-only). The
RevertLog is what makes the rings auditable after the fact; it
is the surface of evidence that the law was followed. - **Stable needs freeze evid...
v0.2.0 — governed kernel: contracts, missions, evidence, link
v0.2.0 — Governed kernel: contracts, missions, evidence, link
Date: 2026-09-02 · Base: main @ 05cdacb · Range: 0eca06b (freeze-slice-0, PR #9) → fb5bbfb (kanban closure for the v0.2 wave, PR #27)
v0.1 built the execution machinery: durable Work objects, lease-based
scheduling, disposable workers, webhook ingestion, result publishing. v0.2
makes that machinery governed: every surface the kernel exposes is pinned by
a frozen, hash-attested contract, and regression tests over those contracts
are law — drift changes a hash, never silently changes semantics.
What each merged slice added
Contract Freeze Slice 0 — contracts(freeze) (PR #9, 0eca06b)
Twenty frozen contracts materialized as 21 draft-07 JSON schemas under
contracts/schemas/, wrapped in contracts/manifest.json (21 entries;
work.schema/handoff inlined) with a freeze-attestation SHA-256 in
contracts/manifest.sha256
(2d2f1d27474a908a19aafb9c152be5e27c80987400f21cdfca94080b8bf14a86).
tests/contracts/ adds 21 contract tests (conformance, baseline,
adversarial, compatibility) that are now regression law: a schema change
without a re-generated manifest hash fails CI by design. Compatibility rule
frozen per proto.charter/1.0: N-1 read tolerance; breaking changes require a
major version bump.
k-mission-01 — kernel(mission) (PR #10, ab87f8d)
Mission contract + budget ledger (ADR-0008/0009). Mission Works carry a
MissionContract (budget ceiling, verification criteria, purpose bindings,
kill switch) and a BudgetLedger (reserved at lease time, consumed
continuously, clock paused while WAITING_HUMAN, stopped on hard stop; late
provider bills recorded but never push user-visible consumption past the
ceiling). The state machine gains the forward mission states
WAITING_HUMAN, SUSPENDED, BUDGET_EXHAUSTED — reachable only by mission
works, never by CI works.
k-mission-02 — kernel(mission) persistence (PR #11, 5041452)
Handoff/checkpoint persistence (ADR-0010, handoff.schema/1.0). A handoff is
the frozen 5-layer checkpoint payload (state snapshot, narrative, decision
log, priority queue, warnings) written only on kernel-recognized
suspend/wait/fail transitions. Suspend/resume is fail-closed: an invalid
handoff is never persisted, resume without a checkpoint is refused, and a
checkpoint whose re-derived hash mismatches its persisted hash
(ErrCorruptHandoff) is never silently resumed from. Schema v8: works.mission_json
column + work_handoffs table.
k-evid-01 — evidence(quittance) (PR #12, a83a8a9)
FailureAttribution + Quittance (ADR-0011/0024) in services/evidence: the
settlement-grade mission receipt layer. Kernel-negation law: a work that
failed verification can never carry a price hint
(ErrQuittanceFailedPriced) and a quittance cannot exist without an evidence
bundle (ErrQuittanceNoEvidence) — failed ⇒ no price.
k-hal-01 — hal(cpi) (PR #13, 4f05fc0)
The CPN provider boundary at cpi/1.0 (ADR-0012/0018): a single frozen
handshake/exec/snapshot contract every compute provider speaks. Plus the
conformance harness (packages/providers): interface compliance alone proves
nothing — every future provider must pass the executable ConformanceSuite
before it can be registered.
k-pulse-01 — hal(pulse) (PR #14, 4994501)
PULSE-Node CPN adapter (packages/providers/pulse_provider.go, ADR-0013/0026),
consent-gated and request-only: it announces only capabilities backed by an
active ConsentGrant, every wire call carries the grant reference and the
daemon re-validates (double enforcement), and with no grants it emits zero
outbound bytes — every call fails locally before any network dial. v1
transport is loopback HTTP on 127.0.0.1:7777 with a localhost pairing token;
mTLS is the v2 upgrade path.
k-now-01 — shell(now) (PR #15, d9c77de)
NOW-shell contract surface (ADR-0025, shell.contracts/1.0,
packages/shell): typed validation law for every shell surface plus the NOW
projection — the live mission view of what needs human attention first and
what the budget clock is doing. NOW is a read surface (T1); privileged
actions (approve/deny/kill/take/hand_back) belong to the COMMAND surface at
T3 and can never be exposed by pulse or local_only surfaces.
k-cap-01 — capability(cap) (PR #16, b1491fd)
Capability-evaluation law (packages/capability, policy.token/1.0): a
shell contract declaration executes only if the frozen contract allows it AND
the caller's policy token carries a matching scope. Grants carry token_id,
work_id, org, and can only ever narrow, never widen, delegation.
k-billing-01 — billing(settle) (PR #18, d7bc4e4)
Settlement law over quittance + budget (packages/billing, ADR-0011/0024 +
0009): settlement consumes the budget ledger against a quittance under four
laws — clock law (paused clocks never meter), clamp law (late bills never
push past the ceiling), late-bill evidence law, fail-closed (no quittance, no
settlement). Settlement never mutates the ledger.
WORKS Conversation V1 Task 1 — journaling (PR #19, e89d2e9)
Every canonical state transition is journaled to the durable per-work event
journal (schema v9 work_events, globally monotonic sequences), with a REST
journal listing for the conversation mirror; SSE consumers resume with
sequence > cursor.
k-link-01 — feat(link) (PR #20, 4f9bc69)
The WORKS-Link server surface (ADR-0026/0027, services/api/link_handler.go,
packages/link): the only routes a PULSE device may present to the kernel —
POST /link/v1/pair, POST /link/v1/mounts, GET /link/v1/missions,
POST /link/v1/revoke. /link/v1/commands exists in the frozen
link.wire/1.0 enum but is deliberately not mounted: the request-only law
(PULSE is never a controller) makes every privileged command structurally
unmountable. Auth: SAS pairing (6-char human-displayed code) mints a
short-lived HMAC device token; the service re-reads durable device state on
every call. Schema v10: link_devices + link_mounts. Enabled opt-in via
WORKS_LINK_PAIRING_SECRET (min 32 bytes); without it every link route stays
mounted and answers 503 fail-closed — never a silent default. See
runbooks/link-surface.md.
Upgrade notes (v0.1 → v0.2)
- No breaking wire changes. All new surfaces are additive and the frozen
compatibility rule is N-1 read tolerance perproto.charter/1.0; existing
API clients keep working unchanged. - Schema migration v7 → v10, automatic and idempotent on restart. The
production store (/var/lib/works/works.db) is currently at schema v7.
Restartingworks-api/works-workerwith the new binary applies, in order:
v8 (works.mission_json+work_handoffs, PRAGMA-checked column add),
v9 (work_eventsjournal), v10 (link_devices+link_mounts). All
migrations areCREATE TABLE IF NOT EXISTS/ introspection-guardedALTER TABLE— safe to run repeatedly, no data backfill. Verify after restart:
sqlite3 /var/lib/works/works.db 'select max(version) from schema_version'
→10. WORKS_LINK_PAIRING_SECRETis opt-in. Generate one (openssl rand -hex 32, ≥ 32 bytes), add it to/etc/works/works.env, and restart. If it
is absent or short, the link surface is mounted but every route answers
503 — that is the intended fail-closed posture, not an error to fix under
pressure. Setup walk-through: runbooks/link-surface.md.- Contract tests are regression law. If you change anything under
contracts/schemas/, re-runpython3 contracts/gen_freeze.py— the
manifest hash must move visibly. CI runs the 21 tests intests/contracts/.
Governance invariants (unchanged by v0.2, now contract-pinned)
- State machine authority — only the kernel moves a
Workthrough its
transitions; the transition table is frozen inwork.schema/1.0and locked
by baseline contract tests. - Evidence-on-disk for
SUCCEEDED— a succeeded work has evidence
records; a quittance cannot exist without an evidence bundle. - Lease TTL — leases expire and are re-grantable; expired leases are
reclaimable viaListExpiredLeases. - Idempotent webhooks — webhook ingestion is replay-safe.
- Frozen contract tests — the 21 tests in
tests/contracts/fail on
drift; the manifest hash is the freeze attestation. - Pool enforcement at grant — BYOC pool membership is checked when a
lease is granted, not later. - Cache exit≠0 refusal — the content cache never stores results from a
failed attempt.
ADR boundary (honest note)
ADRs 0005–0007 live in this repo (docs/adr/). ADRs 0008–0027 referenced by
the v0.2 slices live in the AVC workspace docs, not in this repository —
they are cited from the frozen schemas and package docs here, but their text
is not vendored in-repo.
Verification at release
go build ./...— cleango test ./tests/contracts/ -count=1— 21/21 PASS- Full suite
go test ./... -count=1— all packages ok (per
contracts/FREEZE_EVIDENCE.md: 30/30 packages, exit 0) - v0.2.0 wave slices (PRs #22–#27) — exact-head CI + post-merge main CI
both SUCCESS; e2e green;works-kanban validategreen; standards
registry parses.
v0.2.0 wave (the last 5 PRs in this release)
| Slice | Title | PR | Merge SHA |
|---|---|---|---|
| k-039 | v0.2.0 release notes + WORKS-Link runbook + README kernel section | #22 | 46a7a10 |
| k-035 | Revoke cascades mission suspension with handoff (ADR-0020/0026) | #23 | 8ec59b3 |
| k-036 | Wire conformance of /link/v1 vs frozen link.wire/1.0 + pairing/1.0 |
#24 | 8d0ee8d |
| k-037 | works missions CLI — NOW-ordered mission projection |
#25 | 03ba423 |
| k-038 | WebUI work-detail journal timeline (escaped, last 50 events) | #26 | e405f9e |
| kanban | k-impl-035..039 closed on the board |
#27 | fb5bbfb |
Five workers, disjoint file-ownership, all exact-head CI SUCC...