Fresh node evidence before publication¶
Work item: ginsys/bronzeward#247; prerequisite of ginsys/bronzeward#241. Owner decision: 2026-10-19, fresh read-only executor observations of every covered machine before compilation; offline/unreadable covered nodes refuse publication. This is implementation approval, not the PoC assessment in #31.
Contract and implementation¶
The snapshot supplies the target contract and covered machines. After checking
the pinned renderer can compile that contract, the executor obtains a fresh
publication observation for each machine, ordered by machine ID. Its start
binds the publish operation, controller, owner generation, epoch and endpoint.
Both start and completion require the running owner's unexpired lease, checked
after the operation lock. The compiler's provider interfaces are unchanged.
A known identity or minor mismatch refuses 422; unread identity/version/access refuses 503. T3 rechecks evidence under the existing machine locks and refuses changed endpoints (including changed away/back), a different evidence binding, or a higher-basis unread/contradicting observation. A lower-basis read completing later does not supersede the chosen evidence. Matching higher-basis reads pass. Each release machine retains its observation ID; evidence IDs are excluded from the content digest so commit-unknown retains the original release.
Authoritative contracts: design §7.4/§12.6, compilation §10.2,
persistence-api §6.1.1/§6.2 and execution-recovery §4.1. The initial schema is
edited in place under the pre-release rule. The additive API fields are
publicationObservation and nullable publicationGeneration.
Database and provider verification¶
The required mise run go-db gate passed at
e04d6272006fdfaa57e192d8531dbb5b0a47e8af: 21 packages with tests and two
without test files, exit 0. The API package took 523.906 s, migrations 128.539 s
and monitor checks 114.864 s. The environment was Go 1.27.1 linux/amd64,
PostgreSQL 17.11 and OpenBao 2.6.1
(ba7ad8861d0578cd4da4f7b9e5a6756d30484f8f).
The later 247e22c2adff4bf21c6d3a7427587b86a2693035 changes documentation
only; the subsequent corrections at aede267c50f7c10978e42aa1eb4a8c43c96feaee
and 44e8cf8c8f0285e70768e07291c2cd6f4a0ee9e6 change fixtures and
documentation. Through 14262bd5283c78e863f88cf7ebe3df37a124b1e6, no Go source
differs from e04d627. The later commit-unknown clarification adds a focused test,
described below, without changing production Go code. This nonverbose task does not
provide a complete skip inventory. The live S2 run separately records the
selected Go checks and requires that none of those checks skip.
The accounted mise run verify gate also passed at e04d627, exit 0, unchanged
checkout and settled process group. Its raw log SHA-256 is
584319a3d3b9b1cc5f126eda9d8e56784b0421a3319bb063a34e083ac2cf9fc3.
The final PR gate is recorded separately at its final candidate.
Use the repository's development database/provider setup and mise run go-db
to reproduce the full task. Set TMPDIR and GOTMPDIR to disposable storage.
The publication-specific checks use isolated PostgreSQL databases and explicit
read-only stand-ins:
| Check | Boundary exercised |
|---|---|
TestPublicationRunningMinor |
Matching minor publishes; different minor refuses 422; missing/malformed reports refuse 503. |
TestPublicationIdentityAndAccess, TestPublicationOffline |
Wrong node/cluster, unread identity/version and all five provider failure causes refuse before compiler value reads; failed evidence remains safe. |
TestPublicationEvidenceAtCommit |
Missing/misbound evidence, old generation, changed endpoint including away/back, and higher-basis unread/contradicting evidence refuse T3; matching newer evidence passes. |
TestPublicationReadFenced, TestPublicationLeaseAfterLockWait |
Superseded reads cannot start or complete; a lease expiring during the operation-lock wait cannot start a read. |
TestPublicationMixedCoverage |
Two matching machines publish with both evidence links; a mismatching later machine prevents publication. |
TestPublicationOlderReadCompletingLate |
Lower-basis late completion cannot supersede chosen evidence; release and observation APIs retain the binding. |
TestPublicationWorkerRefusal |
The actual worker persists the failed operation and observation, with no release or Desired change. |
TestPublicationEndpointRace |
T3 waits for the endpoint writer, then rejects the changed endpoint. |
TestReleasePublicationEvidence |
Schema rejects missing, unknown and another publication's evidence; matching evidence commits. |
Existing publication worker tests retain commit-unknown/idempotency checks and interruption takeover; the release after takeover names generation 2 evidence. Existing execution tests now compare their own observations with the count before execution, since publication legitimately adds two timeline entries. The migrations' execution fixtures likewise express revisions relative to the publication entries. These adaptations preserve the execution assertions.
Commit-unknown clarification¶
The second hosted advisory review identified ambiguous retry prose. T3 commits the release, Desired, published draft, operation success and terminal event in one transaction. A lost reply after successful COMMIT leaves that succeeded operation in the database, so the claim predicate cannot select it for takeover. The client reads the persisted result even if a covered node is now offline. An explicit T3 retry uses the retained built unit and returns its existing matching release before evidence checks, without another remote read.
If T3 rolled back, there is no release. Its unfinished operation may be claimed after lease lapse; that new pass must obtain fresh observations and correctly refuses an offline node. PA §6.1.1 and §13.3 now distinguish these paths. No earlier release lookup or worker behavior change is required.
At clean 7d06094caee750fe537483521ce52cf78319be8b,
TestPublishWorkerCommittedReplyLost performed a real COMMIT, then returned a
synthetic connection-loss error to the worker. It verified durable operation,
release, draft, Desired and original evidence, then made the node reader
unavailable and proved neither the original nor another worker could claim it;
no second observation, start, access read or release appeared. Six existing
retry, rollback, claim, takeover and offline checks also passed: seven top-level
tests, nine PASS records including subtests, zero failures or skips, exit 0.
This simulates loss of the commit reply, not a real database network partition.
The sanitized evidence retains the exact command, revision and log hash.
Negative controls and development failures¶
The first new tests ran on the pre-implementation source at a74cee5 with test edits still uncommitted. The matching-minor control passed, while different, missing and malformed versions and absent access did not refuse. That failure is a development reproduction, not evidence for the final revision.
Four temporary source overlays subsequently removed one production guard each.
They were rerun at clean committed revision
aede267c50f7c10978e42aa1eb4a8c43c96feaee; all four exited 1 for the expected
assertions, while their matching controls passed. No selected test skipped:
| Overlay | Expected and observed exit 1 |
|---|---|
| Minor comparison | Matching-minor control passes; different-minor test detects publication. |
| Higher-basis comparison | Matching-newer control passes; different and unreadable newer evidence incorrectly commits. |
| Endpoint comparison | Matching control passes; changed and changed-away/back endpoints incorrectly commit. |
| Lease check before lock wait | Matching-minor control passes; the expired owner incorrectly starts a read. Completion still fences it. |
Each overlay maps only internal/api/publication_observation.go to a temporary
copy through go test -overlay. It leaves the checkout unchanged. Run the named
checks with BW_REQUIRE_PG=1 and the development BW_TEST_PG_DSN, through
mise exec -- go test -v -count=1, selecting TestPublicationRunningMinor,
TestPublicationEvidenceAtCommit or TestPublicationLeaseAfterLockWait as
appropriate. Removing the overlay must pass. The retained overlay source and
logs allow comparison of the exact disabled predicate.
Earlier development runs failed on fixture assumptions about empty timelines, relative revision numbers and missing executor setup, plus three new-test mistakes: a mismatched default Kubernetes version, an error-code query using the wrong JSON field, and a schema matching control that consumed the unique operation before subsequent rejection cases. The retained intermediate logs are not assigned to a clean committed revision. The final database/provider gate above supersedes them as acceptance evidence, without erasing them.
S2 assertion review repair¶
The first hosted advisory review found a stale revision label in the sanitized
header and a weakness in S2's released_once assertion. The header now names
aede267 as the chain that stopped at S3 and 44e8cf8 as the passing repair
chain. The assertion now requires nonempty release-machine coverage and a
positively matching observation for every machine; a missing row or null field
cannot disappear from a count of violations.
The reproducible harness
extracts the actual function, without executing the scenario's top-level code.
Each query uses fresh nullable PostgreSQL temporary tables inside a rolled-back
transaction with search_path = pg_temp. Deliberately missing/null rows test
the assertion independently of production schema constraints; several are
already forbidden by those constraints. This is a fixture assertion repair,
not evidence that the publication implementation accepted those states.
At clean 6fe0af3afb0eca83844489d15d7069a43dfc8d17, PostgreSQL 17.11,
the same harness checked the old function from published 0476c19 and the
corrected function. The old function missed 15 of 28 controls (expected exit 1);
the corrected function passed all 28 (exit 0), with zero SQL errors in both.
The controls cover one/two-machine success, missing/null/badly bound evidence,
wrong minor, mixed valid/invalid machines, empty coverage and the original
release/draft assertions. The checkout remained unchanged. Syntax, ShellCheck
and whitespace checks passed. No full live chain was rerun for this assertion
change; the live results above and below keep their original revisions.
With the disposable development database available, choose an empty artifact
directory outside the checkout and set TMPDIR/GOTMPDIR as above:
bash scripts/test/s2-publication-evidence-test.sh fixtures/scenarios/s2 "$artifact_dir/after"
git show 0476c196fa06f27c5320de50b27efb929b114ba9:fixtures/scenarios/s2 > "$artifact_dir/before-s2"
bash scripts/test/s2-publication-evidence-test.sh "$artifact_dir/before-s2" "$artifact_dir/before"
# The old-function control must exit 1; the corrected function must exit 0.
BW_TEST_PG_DSN defaults to the repository's disposable development PostgreSQL.
The retained development attempt that could not connect through the restricted
sandbox is a harness/environment failure, not an assertion result. The final
harness refuses a failed connection preflight and records SQL errors separately
from assertion outcomes. Both committed logs passed the same synthetic-value
and private-key-header screen, with all positive controls detected; this only
screens emitted output.
Live fixture verification¶
The fresh chain completed at clean revision
44e8cf8c8f0285e70768e07291c2cd6f4a0ee9e6, with every step exiting 0 and
no tracked edits during execution. Retained record: steps-20261019T163018Z.
mise exec -- fixtures/scenarios/integrated "$record_dir" \
up s1p s3h s2p s3r s4c s4x s4 s5p s7 s7n s7t s7o down
Set TMPDIR and GOTMPDIR to the same disposable directory on disk before
running. The profile uses Talos 1.13.6, requested Kubernetes 1.36.2,
PostgreSQL 17.11, OpenBao 2.6.1 (an older snapshot of that same version in S7), Docker
26.1.5, Compose 2.27.1, SOPS 3.13.3 and age 1.3.2. The retained manifest and
bundles pin the exact tool and image digests.
| Scenario | PASS | FAIL | SKIP |
|---|---|---|---|
s1-pass-20261019T163225Z |
36 | 0 | 2 |
s2-pass-20261019T163420Z |
53 | 0 | 4 |
s3-handover-20261019T163336Z |
29 | 0 | 0 |
s3-run-20261019T163614Z |
98 | 0 | 0 |
s4-controls-20261019T163901Z |
66 | 0 | 2 |
s4-restores-20261019T164201Z |
141 | 0 | 1 |
s4-run-20261019T164706Z |
33 | 0 | 0 |
s5-pass-20261019T164803Z |
73 | 0 | 3 |
s7-no-entry-20261019T165828Z |
49 | 0 | 0 |
s7-old-bao-20261019T170553Z |
41 | 0 | 0 |
s7-run-20261019T165047Z |
128 | 0 | 0 |
s7-transit-20261019T165941Z |
46 | 0 | 0 |
All 48 bundles name this revision, have an empty fixtures diff,
complete capture and no leak-scan hit outside the positive-control file.
The built server image is sha256:417c71280c270e335ae434c008c839441e71296ebdfd94d7300d5b633a2a19e9.
Teardown confirmed no fixture containers, networks, volumes or state left.
The explicit pass-mode skips are retained in the sanitized evidence. They
do not rerun the full S1/S2/S5 negative matrices; earlier acceptance runs retain
their own revisions and coverage. No S8 human review ran in this chain.
Full S2 separately passed all 373 checks at clean aede267 in the retained
run below, before that chain stopped at S3. It covers offline-node refusal,
all seven publication interruption points, generation-2 takeover with one
release and provider-wide outage refusal with linked safe observations.
Soft-deleted/destroyed pinned-value refusals, monitor classifications and
alerts passed too. Neither the Go implementation nor S2 changed between that
run and this chain; this chain reruns S2's pass paths, the repaired S3/S4
controls and the S7 integrated/variant sequence. S7's separate stale-instance
closing mode was not rerun at this revision; its earlier evidence remains
revision-bound.
S4's frozen-scope, schema/DS controls and transaction-level race remain
explicit skips naming their separate checks.
The first launch, steps-20261019T151526Z, omitted mise exec and stopped
at up, exit 1: ambient Go 1.27.2 differed from the pinned 1.27.1. It ran no
scenario and created no server image. The scoped fixtures/bin/down completed
before rerunning; its result and the failed chain remain retained. This was a
launcher error, not a passing fixture result.
The next run, steps-20261019T151940Z, at 247e22c ran from 15:19:40 to
15:49:04 UTC. Initial import and handover passed (36/0/2 and 29/0/0
PASS/FAIL/SKIP). S2 finished with 367/3/0 and the chain stopped. Its three
failures were the old expectation that a provider-wide outage produces a
pinned-dependency problem. Publication correctly refused earlier with 503 and
publication-evidence: the required executor read could not retrieve Talos
access. Network partition, seal and pause each produced that mismatch.
All seven publication interruption points and the live offline-worker refusal passed in that failed run. Its selected Go checks also passed without skips. All 33 bundles matched the revision, had empty fixture diffs and complete sources, and contained only the leak scan's positive-control matches. The scoped teardown then reported no fixture containers, networks, volumes or state left. Later scenarios did not run. This is retained failed-run evidence, not a successful integrated acceptance result.
The correction at aede267 changes the outage assertion to require that early 503 and its linked publication observation, correct generation and absent access-version/node values. Soft-deleted/destroyed pinned-version refusals, monitor classifications, alerts and the compiler's isolated unavailable-value checks stay covered. AP S2 and the fixture README explain the order.
The third run, steps-20261019T155019Z, at aede267 ran from 15:50:19 to
16:22:19 UTC. S1 pass and handover passed again; full S2 passed all 373 checks
with zero failures or skips, including the repaired outage assertions. S3 pass
then failed eight assertions (66/8/1 PASS/FAIL/SKIP), and later scenarios did
not run. Its step-4 publications correctly refused the worker that S3 had
intentionally disconnected to hold approved plans before commitment. The
remaining failures followed from the missing releases, not an approval
implementation change. Both failed publications retained 503
publication-evidence problems naming their observations.
All 37 bundles matched aede267, had empty fixture diffs, complete captures and positive-control-only scans. The scoped teardown completed before edits. The repair prepares each release and its plan while connected and still unapproved, then disconnects every instance before approving the three plans. Their immutable releases retain the same self-approval reason checks. A parallel source audit identified analogous fixture sequencing in S4 controls and S7 recovery: those also need node reads available during publication while preserving their held commitment or blocked-scope assertions. These fixture adaptations change no product safety check.
A later advisory review found that AP S4 omitted the existing A/B pause
prerequisite for its commitment-level restore controls. The fixture already
pauses both instances before those cases and directs requests to C, the sole
running publisher/executor. The retained s4-restores-20261019T164201Z/s4.tsv
at 44e8cf8 passes that pause, both assignment/Desired publications and both
comparison-2 refusals. The written procedure now states this prerequisite;
the fixture is unchanged, and no additional live run is claimed.
Evidence retention and scan limits¶
The owner retains raw logs, temporary guard controls and fixture bundles. Only sanitized results and their SHA-256 references are published. The development and committed test stdout/stderr logs were screened for the three named synthetic test values and private-key headers, with zero hits. Every pattern matched its positive control. This screen says nothing about surfaces the command did not emit; database/timeline assertions and live fixture bundle scans provide separate evidence.
The sanitized results retain exact commands, statuses, skips and raw-log SHA-256 references, including all three failed live attempts and the repaired chain. Raw bundles remain with the owner until released; this summary does not replace their captured evidence.
Limits¶
The selected live profile runs Talos 1.13.6. Different and malformed version reports are explicit read-only stand-in tests, not an upgrade or a claim about a second live Talos profile. No health predicate is required for publication; configuration/resource reads remain diagnostic. No mechanism can exclude an unrecorded remote change after a read; dispatch retains its own fresh checks. Publication availability now depends on every covered node. There is no new lifecycle/transport implementation, credential policy, deployment claim or PoC owner verdict. The independent timeline review and owner assessment remain open in ginsys/bronzeward#31.