THOTH-GQL-OPS-03: add effective-mode fleet verification - #799
Conversation
THOTH-GQL-OPS-03: implement the effective-mode fleet-verification mechanism, closing capability gap 2's implementation candidate. Each serving process emits one structured record on its own log stream at startup, read out of the same stored `Data<MutationGuardMode>` that is installed as the request path's `app_data`, so the reported mode and the request-path mode are one value by construction. The record carries the effective mode and the minimum correlation identity — the process's OS-reported host name — and nothing else. A fleet verifier takes the orchestrator's live instance enumeration and the collected observations and answers CONSISTENT / MIXED / NOT ESTABLISHED. Complete coverage is required: an unattributable member is UNKNOWN, a distinct variant from OFF that never decays into it, and any incomplete or ambiguous coverage fails closed. A pre-guard instance is therefore UNKNOWN, never OFF. Conforms to the approved section 3.2 boundary: no HTTP route, no GraphQL field, no header, no new authorization decision and no public unauthenticated surface of any kind. Request acceptance, guard, batching and loader-store semantics are unchanged, the generated SDL is byte-identical, and there is no migration, schema or data change. Activates nothing. No environment is transitioned, no mode is set anywhere, and no fleet has been verified: implementing a verifier is not verifying a fleet. CG-13 remains OPEN, the runtime-operations gate remains NOT SATISFIED and the runbook remains PROVISIONAL.
Adds the required CHANGELOG entry for THOTH-GQL-OPS-03 and records the pull-request reference in the implementation report. The PR number could not be known before the pull request existed. Documentation only. No runtime, schema, migration or workflow change.
ja573
left a comment
There was a problem hiding this comment.
Independent exact-head implementation review — THOTH-GQL-OPS-03
Decision: CHANGES REQUIRED
Exact head reviewed: 8604dbf93be734c2e04431b15d4a9db1f3a1d2fb
Authorized base: 2bec75e6698232f7643862120e5437452fcfa252
Risk: HIGH
I reviewed the actual nine-file base-to-head diff, the implementation code, the new tests, the task/runbook/report/changelog changes, and fresh exact-head CI. The core mechanics are strong: one stored Data<MutationGuardMode> feeds both the startup observation and the request path; the public listener gains no mode endpoint; UNKNOWN is structurally distinct from OFF; complete coverage fails closed; mixed-mode reduction is correct; the closed OPS-02 behavior is regression-pinned; and exact-head build/test/lint/format CI is green.
Two load-bearing findings prevent approval.
1. Observation provenance is forgeable through existing public request-body logging
thoth-api-server/src/logger.rs already captures the raw body of every public POST /graphql request into request extensions and Logger::default() writes that value into the access log. start_server installs both Logger::default() and BodyLogger on the public application.
The new EffectiveModeObservation::parse_record does not validate trusted log provenance. It calls line.find(THOTH_MUTATION_GUARD_EFFECTIVE_MODE) and then accepts syntactically valid mode= / instance= fields from the remainder of that line.
Therefore text supplied by an unauthenticated public caller can enter the same process log stream and can contain an observation-looking record. A collector/parser that trusts message text alone cannot prove that a parsed observation came from the one startup emitter rather than from public request content. At minimum this permits injected contradictory evidence to force UNKNOWN / NOT ESTABLISHED; more importantly, it breaks the provenance property required of a mechanism whose purpose is to prove process-effective mode.
The existing AC-11.1 tests prove that the server does not return the mode to a public caller. They do not prove that a public caller cannot inject record-looking content into the observation channel.
Required remediation:
- make trusted observation provenance structural, not dependent only on finding a marker in arbitrary log text;
- the collector/parser must be able to distinguish the startup emitter from public request/access-log content;
- add an adversarial integration test that sends public
/graphqlinput containing the exact observation marker and valid-looking fields, and proves that this input cannot become an accepted trusted observation; - preserve the approved section 3.2 boundary: do not solve this by adding a public endpoint or a new authorization decision.
If no trusted per-instance mechanism can satisfy this within section 3.2, return BLOCKED rather than widening architecture.
2. FEASIBLE / AC-3 / AC-20 rely on external orchestration facts that are currently unevidenced
The implementation report, PR body, runbook and code comments rely on these claims:
- the serving process's output/log stream is collected and addressable per serving instance by the actual orchestration/logging plane; and
- the OS hostname read from
/proc/sys/kernel/hostnameor/etc/hostnameis the identity by which the actual orchestrator enumerates that serving instance.
The Linux test establishes only that a Linux process can resolve a hostname. It does not establish that the hostname equals, or can be correlated to, the orchestrator's enumeration identity in the Thoth deployment. Likewise local stderr capture does not establish that the real logging plane preserves per-instance provenance in the form this mechanism needs.
Those are deployment/orchestration facts on which the FEASIBLE conclusion and AC-3 depend. The report nevertheless states External deployment facts relied on: NONE and marks AC-20 PASS (not engaged). That is inconsistent with the binding section 6.6 evidence boundary and AC-20.
Required remediation:
- obtain sanitized Route A or authorized-human Route B evidence establishing the actual Thoth correlation contract needed by this design: per-instance log provenance and the mapping between the emitted identity and the orchestrator-enumerated identity; OR
- if that evidence is unavailable or disproves the assumption, mark the affected acceptance criteria
BLOCKEDand returnBLOCKEDas the specification requires; - do not read the private secret-bearing deployment source to obtain it;
- update the report, PR body, runbook, CHANGELOG and relevant code comments/tests so they state only what the accepted evidence supports.
Additional precision to reconcile with the selected mechanism
The record is emitted at info, so suppressing that level makes the member UNKNOWN; record this as an explicit operational precondition rather than an unconditional claim that every serving process emits a usable observation. The record is also emitted before startup succeeds, so collection semantics must ensure evidence is correlated to the currently enumerated serving process/generation rather than stale or failed-start records.
These points are compatible with fail-closed behavior, but they must be made explicit in the mechanism/evidence contract used by OPS-04.
CI / controls
Fresh exact-head workflows are green. build-test-and-check ran classify, format_check, test, build, and lint successfully. run-migrations classified successfully and its migration job was skipped for this non-migration diff. check-changelog and publish-to-dockerhub succeeded.
Control state should remain unchanged while remediating:
PR #799 DRAFT / UNMERGED
CG-13 OPEN
Runtime-operations gate NOT SATISFIED
Runbook PROVISIONAL
OBSERVE NOT AUTHORIZED
ENFORCE NOT AUTHORIZED
BE-02 runtime NOT AUTHORIZED
THOTH-GQL-OPS-04 NOT IMPLEMENTED
This review does not authorize deployment, production access, mode transition, OPS-04, or merge.
Remediate on the existing bounded branch/PR. A new head requires fresh exact-head CI and fresh independent review.
Independent exact-head review 4906399962 returned CHANGES REQUIRED on two load-bearing findings. Both are correct. This commit reconciles the repository to what is actually established; it makes no mechanism change. Finding 1 - observation provenance is forgeable. Verified from source: BodyLogger captures every public POST /graphql body, the access logger interpolates it prefixed by a newline, and that log shares one facade, level and sink with the startup effective-mode record. A caller therefore controls a whole line, including any prefix it appears to carry, and parse_record accepts it. Trust must become structural. Finding 2 - the external correlation contract is unevidenced. Per-instance collection provenance and the mapping from the emitted identity to the orchestrator's enumeration identity are deployment facts no local test can establish. The section 6.6 evidence boundary is engaged. No fix is attempted for finding 1 because every candidate trust property - dedicated stream, stream metadata, structured event provenance - depends on real collection-plane behaviour, which is exactly the unevidenced fact E4. Selecting one now would repeat the error the review identified. logger.rs was inspected to reproduce the defect and deliberately left unmodified. Withdraws the unsupported claims that the OS hostname is the orchestrator-assigned identity, that the orchestration plane already collects the stream per instance, that every Linux production observation is attributable, and that no external deployment fact is relied on. AC-3, AC-4, AC-11, AC-20 and AC-23 are corrected from PASS to BLOCKED; overall result BLOCKED. Records the outstanding Route B evidence request (E1-E5), stale/failed-start semantics, and the info-level signal-availability precondition. No runtime behaviour change. No schema, migration or data change. No deployment, no mode set anywhere, no fleet verified.
…el (#788) Merge the CTO-approved BE-02 specification at exact reviewed head 50f9d36. This merge makes the specification repository-authoritative only. It does not authorize BE-02 implementation, creation of feature/publisher-services/be-02, deployment, migration execution, assignment backfill, distribution activation, OBSERVE/ENFORCE, or any action on PR #799.
Add the CHANGELOG entry for implementation PR #805, reconcile the Publisher Services tracker to the actual live state, and add the BE-02 implementation report. The tracker's previous "BLOCKED - IMPLEMENTATION NOT AUTHORIZED" wording is now stale: the reconciled specification was independently reviewed, CTO-approved and merged through PR #788, and implementation was then separately authorized against that exact `develop` SHA. BE-02 moves to "IMPLEMENTED - AWAITING INDEPENDENT REVIEW / MERGE AUTHORIZATION" — not complete, because fresh independent exact-head review and separate CTO merge authorization are both still outstanding. The report records the preflight, the exact commands and their concise results, empty and representative-populated migration evidence, empirical `pg_locks` observation on a disposable PostgreSQL 17.10 database, the base-versus-head SDL diff, the DataLoader contract, the real-SQL query-count evidence for both parent shapes and every required boundary, and the known limitations — including the two places where the repository's own facts shaped how a specification requirement could be evidenced. Implementation remains an inactive foundation. No deployment, production migration, backfill, distribution activation or guard-mode change is performed or authorized, and PR #799 is untouched.
Merge the independently reviewed and CTO-authorized BE-02 implementation at exact head 039ca97 into develop. This merge authorizes repository integration only. It does not authorize deployment, environment or production migration execution, assignment creation or backfill, distribution activation, OBSERVE/ENFORCE, production access, or any action on PR #799.
Merge the independently reviewed and CTO-authorized BE-02-CLOSEOUT-01 documentation/control correction at exact head 1f2cb58 into develop. Repository integration only. This merge authorizes no deployment, environment or production migration execution, backfill, distribution activation, OBSERVE/ENFORCE, production access, or action on PR #799.
…on (#808) Merge the independently reviewed and CTO-approved BE-03-SPEC at exact head a3fc706 into develop. This makes the exact approved specification and contained Publisher Services BE-03/BE-04/APP-01/APP-02 programme decision reachable from develop. Specification/control repository integration only. This merge does not authorize BE-03 implementation, creation of the BE-03 implementation branch, deployment, environment or production migration execution, MIG-01, backfill, assignment creation, distribution activation, OBSERVE/ENFORCE, production access, BE-04 implementation, or action on PR #799.
Three findings were raised against the addendum's first revision. All three are
corrected here; the three substantive addendum decisions (NULL-safe attempt-error
constraint, Route A composite loader, thoth-client gate resolution) are not
reopened.
Finding 1 - the statement arithmetic was not mathematically exact. It used one
page-global boolean, `2 + (3 if J else 1) * C_job + C_assign`, but cost is
decided per dispatch chunk: a multi-chunk page can contain a chunk that resolves
jobs (3 statements) and another that resolves none (1 statement). Replaced
throughout by
statements = 2 + 3 * C_job_nonempty + 1 * C_job_empty + 1 * C_assign
with the four named cases evaluated mechanically - job-only/report on a page
with jobs give 5 and 6, and on a page without give 3 and 4 - plus a worked
multi-chunk example and a rule that multi-chunk pages are derived from the
actual per-chunk classification. Also removes the self-contradictory
`C_job = C_assign = 1`: for the job-only selection C_assign is 0, because that
selection does not select enabledDistributionPlatforms. Loader-dispatch
acceptance is now stated per loader - each selected first-level loader has its
expected chunk count, each unselected loader has zero, and the second-level
loaders have zero report-path dispatches - rather than as a blanket claim that
every loader dispatches once.
Finding 2 - the control records conflated "addendum 01 is not approved" with
"BE-04 never had an approved specification". The live GitHub authority shows
otherwise: PR #814 is merged, its merge commit is the authorized base
ed32712, and it carries the CTO's explicit
BE-04 implementation authorization against that base, naming the merged BE-04.md
as the repository-authoritative specification. Corrected to APPROVED BASELINE /
ADDENDUM 01 NOT YET APPROVED across the specification header, sections 6.3 and
31, the new section 34.0, the tracker and the changelog. The baseline approval
and the implementation authorization are preserved as historical authority; the
implementation candidate is properly authorized work, and is blocked because the
contract it was built against is being corrected, not because it was
unauthorized. The authorization is insufficient - not void - for the corrected
contract. Clearly labelled historical DRAFT and remediation narrative is
retained, and no review or approval comment identifier is transcribed into a
committed file (ADR-0005 section 5).
Finding 3 - an unsupported inference about BE-02. The claim that an unexpected
chunk count would apply "equally" to BE-02's merged assignment loader is
withdrawn: ADR-0007 requires field-specific query-count evidence per adopting
field and establishes no universal sublinear bound for arbitrary arrival timing.
The escalation rule now requires the cause to be classified on evidence as
BE-04-specific, a shared-foundation dependency/runtime regression, or another
execution shape; only the second escalates to the owning Shared Thoth GraphQL /
Backend Architecture programme, the control that a genuine shared finding must be
surfaced rather than worked around locally is preserved, and no claim about
another field's loader may be made without verifying that field.
Documentation only. No runtime, migration, contract, workflow or manifest
change. PR #816 and PR #799 untouched.
Committed control documents must stay truthful before review, after review, before merge and after merge (docs/engineering/AGENTS.md section 1.1, ADR-0005 section 6). The addendum's records carried wording whose truth would change merely because PR #817 is reviewed, approved or merged. Removed and replaced by durable form; no substantive addendum decision is reopened. BE-04.md - Status becomes "APPROVED BASELINE + ADDENDUM 01 AUTHORITY CONDITION BELOW". - The header now states, durably: the approved baseline and its implementation authorization through PR #814; that this document carries the corrected addendum content; that approval authority is the CTO; the authority condition that the corrected content is repository-authoritative when the exact CTO-approved content is reachable from develop; and that live review, approval and merge evidence is the GitHub history of PR #817. - Section 34.0 restated as baseline and addendum authority rather than as a current approval state; sections 6.3, 31 and 34.5 follow. Section 31 no longer calls the implementation pull request draft, and states durably that the candidate was built against the baseline contract and does not satisfy the corrected one. - Section 33 no longer waits for someone to fill an approval block. It records that approval, review, authorization and merge are GitHub-owned terminal evidence, and that copying them here would need an approval-state commit, which ADR-0005 section 4.1 item 10 prohibits. No PR #814 comment identifier is transcribed. Implementation report - "Independent reviewer: required and absent" becomes a statement of the requirement and of reviewer independence, with the live review decision and its exact-head binding left to GitHub. - The preflight table is labelled a point-in-time observation record rather than a claim about current pull-request state. - No committed statement calls addendum 01 unapproved; approval authority and live approval state are GitHub-owned, and the report issues no approval decision. Tracker - BE-04 status becomes "BLOCKED - IMPLEMENTATION CANDIDATE REQUIRES CORRECTED-CONTRACT RECONCILIATION", which survives addendum review and merge. - Durable facts retained: the approved baseline through PR #814; candidate #816 properly authorized against ed32712; the candidate does not satisfy the corrected contract; continuation needs the corrected content repository-authoritative, a fresh base and a new CTO implementation authorization; live satisfaction is GitHub-owned. - PR references no longer commit draft or approval state. CHANGELOG - The entry describes the durable change and attributes approval and merge state to GitHub, so it stays true after approval and merge without another commit. Documentation only. No runtime, migration, contract, workflow or manifest change. PR #816 and PR #799 untouched.
Two residual documentation defects, plus one found by running the search variant-aware rather than literal. 1. The implementation report described its own framing as the "approved-baseline / not-yet-approved-addendum distinction". That is transient workflow state and becomes false when the CTO approves the addendum. Replaced by the "approved-baseline / corrected-addendum authority distinction", stated durably: the baseline is CTO-approved and repository-authoritative through PR #814, addendum 01 is the corrected specification content, the CTO is its approval authority, and its live review, approval and merge state is GitHub-owned. 2. The PR #817 changelog entry still described the tracker as updated to "BLOCKED - IMPLEMENTATION CANDIDATE EXISTS - SPECIFICATION ADDENDUM REQUIRED", which stopped being the tracker status when the previous commit made it lifecycle-neutral. It now states the current durable status, "BLOCKED - IMPLEMENTATION CANDIDATE REQUIRES CORRECTED-CONTRACT RECONCILIATION". The rest of that entry is unchanged. 3. A variant-aware search (not[- ]yet[- ]approved) found a hyphenated instance the earlier literal-space search missed: the tracker's programme note 14 still called the addendum "not-yet-approved". Replaced with its approval authority and authority condition. The report's consistency table is also reworded so its rows no longer contain the very tokens they report as absent, and it records that the search must be run variant-aware. Documentation only. No substantive addendum decision is reopened: the NULL-safe attempt-error CHECK and its truth-table requirements, Route A and its loader ownership, the per-chunk statement arithmetic and the 5/6/3/4 examples, ADR-0007's 200/10, the evidence-classified escalation rule, the BE-02 non-inference, the thoth-client gate resolution, the approved-baseline and implementation-authorization history, and the post-addendum implementation gate are all unchanged and verified present. No runtime, migration, contract, workflow or manifest change. PR #816 and PR #799 untouched.
Task identity
Specification approval: #798 comment 5252446279. Implementation authorization: #798 comment 5252526720.
Finding 1 — observation provenance is forgeable — OPEN
Verified independently from repository source, not accepted on the reviewer's word:
Both travel the same
logfacade, at the same level, into the same sink. The caller therefore controls an entire line including any prefix it appears to carry, and can synthesise[<ts> INFO thoth_api_server] THOTH_MUTATION_GUARD_EFFECTIVE_MODE mode=ENFORCE instance=attacker.parse_recordlocates the marker in arbitrary text and accepts it.Not fixed in this remediation, deliberately. Every candidate structural trust property is trustworthy only if the real collection plane preserves it — that is fact E4, unevidenced. Selecting one now would repeat exactly the unsupported-assumption error the review identified. Specifically:
stdoutfor the record vsstderrfor access logs is plausible and would not change request-logging semantics, but depends on E4;logtarget is forgeable today, since the target is rendered into text the caller can synthesise;The required adversarial test is deferred with the fix: it must assert that injected content cannot become a trusted observation, which cannot be made true before the channel is selected.
Finding 2 — external correlation contract unevidenced — BLOCKED
The earlier report claimed
External deployment facts relied on: NONEandAC-20: PASS (not engaged). Both were wrong. Feasibility rests on two deployment facts no local test can establish: per-instance collection provenance, and that the emitted identity correlates with the orchestrator's enumeration identity.Withdrawn as unsupported: "the orchestration plane already collects the stream per instance", "the OS host name is the orchestrator-assigned task/pod identity", "every Linux production observation is attributable", "no external deployment fact is relied on".
Outstanding Route B evidence request (sanitized facts only — no credentials, secret values, resource/account identifiers, private configuration or unnecessary topology):
No AI agent or model is a valid source. The implementing agent did not read the private deployment source, query a real orchestrator, or access production.
Also now recorded explicitly
info: suppressed level → no record → memberUNKNOWN→NOT ESTABLISHED. Fail-closed, neverOFF— an operational precondition, not a guarantee. AC-3 is not claimed on the strength of the code callinglog::info!.instancemay be unnecessary disclosure. If E1 shows the collection plane supplies a trusted identity, the preferred design removes the self-reported host name entirely — smaller disclosure and stronger provenance.Acceptance criteria — 17 PASS, 6 BLOCKED
instancenecessity open)Because AC-3 and AC-20 cannot pass, the overall result is BLOCKED.
Unchanged and accepted by review: single-stored-value property (AC-1), mode distinguishability (AC-2), the verifier's fail-closed reduction incl.
MIXED/UNKNOWN/incomplete coverage (AC-5, AC-7, AC-22), the silent-adoption fixture (AC-6), unchanged request/guard/batching/store semantics (AC-8, AC-9), byte-identical SDL (AC-13), untouched control state (AC-15–AC-19).Remediation changed files
Documentation, code-comment and test-comment reconciliation only — no mechanism change:
docs/.../THOTH-GQL-OPS-03-implementation-report.md—BLOCKEDstatus; finding-1 reproduction from source; §12 rewritten as External correlation evidence with the Route B request; §12.4 attack fixture and currentFAILresult; §6.6 stale/failed-start semantics; §6.7 signal availability; AC matrix correcteddocs/.../graphql-mutation-guard-mode-transition-runbook.md— §4.0 mechanismNOT ESTABLISHED; §4.0.1 E1–E5 contract; §4.0.2 the OPS-04 procedure; capability gap 2 restored as still open. RemainsPROVISIONALCHANGELOG.md— entry rewritten; unsupported hostname/collection/no-external-facts claims withdrawnthoth-api/src/graphql/fleet_verification.rs—NOT TRUSTED EVIDENCEstatus block;InstanceIdentitycorrelation semantics marked unestablishedthoth-api/src/graphql/fleet_verification/tests.rs— Linux test comment corrected to claim resolvability onlythoth-api-server/src/lib.rs— emitter comment records the shared-sink defect and theinfodependencytests/mutation_guard_fleet_verification.rs— records that these processes all fail to start, so a record is not membershipthoth-api-server/src/logger.rswas inspected to reproduce the defect and not modified.Tests
Control state — unchanged
No deployment, workflow dispatch, credential use, production access, orchestrator query or mode change occurred at any point.
Next steps