Four releases that tightened the verifier itself
0.9.8 through 0.9.11 in one day: the contract schema stops drifting, the web-server suite stops rotting, a whole flake class dies, a trust boundary gets a name, VAL-3 runs on a real cluster, and the verify lane finally follows the deliverable.
TFactory’s job is to hold other people’s code to a standard, which makes it embarrassing when its own scaffolding slips. This cycle shipped four releases in a day — 0.9.8 through 0.9.11 — and almost all of it points inward: the verifier’s own contracts, its own test suite, its own file writes, its own security boundaries. Here is what landed and why each piece matters.
0.9.8 — the schema stops drifting, and the web-server suite stops rotting
The vendored task-contract-v2 schema is what ingest validates every incoming
contract against. It turned out to be a fossil: 623 lines behind the canonical
hub copy, missing execution.autonomy_tier, routing, deployment,
environment, and the entire $defs block (#679). Contract validation was
running against a version of the contract that no longer existed. The copy is
now byte-synced to the hub canonical, and a blocking CI drift gate
(scripts/check_schema_drift.py, reused from PFactory’s proven gate) makes the
stale state unrepresentable: hard fail on drift, soft skip only when the network
is down.
The same release fixed a quieter rot. CI ran only pytest tests/, so the
apps/web-server suite decayed to 31 failures without anyone noticing (#681).
The suite was triaged honestly — fixed where behaviour had changed, deleted
where the behaviour under test had been removed — and wired into ci.yml as a
blocking step, green in its own introduction run. A test suite that does not
run in CI is not a test suite; it is a rumour.
0.9.9 — atomic secret writes, a named trust boundary, and VAL-3 for real
Three things in one release, all of them about closing gaps between what the code claimed and what it did.
First, write_secret_file is now atomic (#688). The old path wrote in place,
so a concurrent reader could observe a torn hybrid of old and new content —
the root cause of the flaky TestFileLocking CI failures. The fix is the
boring, correct one: mkstemp in the same directory plus os.replace. The
stress evidence is the point: 65 torn files in 1,000 iterations before, 0 in
1,000 after. That is a flake class removed structurally, not retried into
submission.
Second, CodeQL alerts #705-#709 were not sanitizer misses — they were flows of
an untrusted project path, a trust boundary the code enforced but had never
named (#664). A new trusted_project_root() choke point gives the boundary a
name and a barrier, and the local CodeQL oracle (2.25.6) confirms the claim:
21 residual flows down to 17, zero in the terminal-worktree service, and no
over-suppression.
Third, the VAL-3 disposable-target provisioner (#607). VAL-3 means effectful
behaviour verified against a real, disposable host — never production. The new
k8s-Job backend runs each effectful command as an ephemeral Kubernetes Job
(create, watch, collect logs, delete), env-gated and off by default, with no
credentials in the Job env or argv and teardown on every failure path. It was
then proven live for the Factory#257 milestone: a real Job scheduled onto a
different node of the factory cluster, executed, and fully torn down. Until
this release VAL-3 was an honest not_run; now it is a level the fleet can
actually earn.
0.9.10 — tenant scoping, behind a flag
Verification specs, runs, and verdicts are now tenant-scoped (#683). Ingest
accepts an optional tenant field (an explicit AIFactory stamp always wins),
or resolves the X-Tenant-Id header when TFACTORY_MULTI_TENANT is on. The
tenant is written into the spec workspace and the task list filters by it. With
the flag off — the default — behaviour is byte-identical apart from the new
field, and readers lazily backfill legacy rows to "default". Part of the
fleet-wide multi-tenancy program.
0.9.11 — the verify lane follows the deliverable, not the repo
The best bug of the batch. On a repo carrying a go.mod left over from earlier
polyglot runs, a pure-Python deliverable got Go tests generated — nine of nine
failing at compilation — because the planner’s language detection ranked
acceptance-criteria command tokens first and repo manifests second, and when
neither was decisive it guessed from repo markers (#696). The repo is not the
deliverable.
Detection now ranks deterministic signals strongest-first: the extensions of
the files actually changed on the ingest source_branch diff, then the
deliverable filenames named in the spec text, then AC command tokens, and repo
manifests only as an unambiguous last resort. A mixed diff never guesses — it
falls through to the next signal. Regression tests pin both directions: a
Python diff on a Go-marker repo selects the Python lane, and a Go diff on a
Python repo still selects Go.
The through-line
None of these releases added a feature a demo would show off. They made the verifier harder to fool and harder to break: contracts that cannot silently drift, tests that cannot silently rot, writes that cannot tear, boundaries with names and proofs, an assurance level that is earned on a real cluster, and a planner that tests what was actually delivered. That is the job.