Skip to content

M1-69 Canary test across all outputs

No Date Ruling Reason
M1-69/1 2026-10-18 The task names the branch and the base: feat/M1-69-canary-test from int/m1-wave42; the work is not merged locally, the launcher integrates it. The spec chapters the brief cites are on the base already (SEC-T10, DP-T09, SEC-LOG-1 to 7), so no contract change is brought in. The launcher's task overrides the brief's "merge locally".
M1-69/2 2026-10-18 Two fields of error responses count as echoes to the caller, not as sink 5: current of 412 version_conflict and 409 base_version_conflict (06 "Errors" item 3: the representation of a resource the caller may read) and the pointer of an unknown_member error (W7, M1-18/3: it names the member the caller sent). Content there is allowed like in a success response; a secret there stays a finding. TestSEC_T10_ScanEchoFields holds the rule. The contract defines both members; removing them would break M1-18/3 and the 412 contract, and neither tells the caller anything it did not send or may not read. Every other error field stays content-free.
M1-69/3 2026-10-18 The request span gets bothy.principal.kind and, for a person, bothy.principal.pseudo_id from the authenticate stage of the HTTP chain (otel.AnnotatePrincipal); agents, apps and hosts get the kind only. signals.md names both on the server span, and DP-T09 asks for a pseudonym on at least one request span; no code set them before. No new span or metric.
M1-69/4 2026-10-18 The test seams are exported fields with a production zero value: otel.Deps.Exporters, wire.Deps.TelemetryExporters, wire.Deps.OIDCRootCAs (ADR 0071). An injected exporter replaces the OTLP exporter of its signal, including the metric exporter. No collector and no network in tests; one composition root.
M1-69/5 2026-10-18 The harness writes the client secret of the fake OIDC provider as a file of the secrets directory (Options.SecretFiles, referenced as file:<dir>/idp/<name>), not as env:BOTHY_IDP_<name>. The configuration loader refuses a BOTHY_ variable that no configuration key references, so an env: reference stored in a provider row cannot start the server. Reported as a minor in the report, not changed here.
M1-69/6 2026-10-18 The scenario server uses PublicHost: localhost (listeners stay on 127.0.0.1): with an IP address as host of server.public_url passkeys are off (M1-40), so the passkey ways could not run. WebAuthn needs a domain name as relying party; localhost is the one http origin it allows.
M1-69/7 2026-10-18 Texts shorter than minNeedle (test/canary/scan.go) are not searched; six-digit TOTP codes are registered but cannot be proven absent. The detector meta test for logs goes through the OTLP log pipeline of the server (the global logger provider of otel.Setup), because the server logger is not reachable from outside internal/wire. A six-digit needle matches unrelated digits of timestamps and counters; the OTLP pipeline applies the same catalogue filter and queue as the server's own records.
M1-69/8 2026-10-18 The scenario runs once per test binary; TestSEC_T10_CanaryAcrossAllOutputs, TestSEC_T10_ScenarioCoversEveryEnabledOperation and TestDP_T09_NoClearPrincipalOrDeviceIDsInTelemetry share its collected sinks. Console step-ups (password and OIDC re-authentication) run on a console session of the owner (cookie), passkeys on Alex. One scenario takes about a minute and a half; three runs would triple make test for the same data. The console step-ups refuse app sessions by design (D-M6-01-6).
M1-69/9 2026-10-19 Port to main: the ticket's own commits on top of the ported M1 chain with M1-67 and M1-68; the ADR index is regenerated from the English ADRs of main. Superseded by M1-69/11. Only the ticket's own change lands.
M1-69/10 2026-10-20 The scenario skips, with a log line, every request to a route this tree does not serve yet: an operation of the contract that api/operations.yaml does not enable (notification preferences, quiet hours, webhook subscriptions and keys on main today), a path with an empty segment left by such a skipped step, and the console surface with the console form of loginWithPassword until M6-01 sets consoleServed. The coverage test still requires every enabled operation. The scenario was written on the integration line, where later tickets had enabled these routes; on main they answer 404, and the canary checks outputs, not which tickets are on main. Each ticket that enables a route makes the scenario drive it again without a change.
M1-69/11 2026-10-21 The pull request lands the ticket's own commits on main after M1-70 and M1-72, although the brief's Depends on line names only M1-68 and M1-71: the scenario dials through the gateway client of test/harness that M1-72 brought and reads World.Owner of the M1-70 world, and the harness fields of M1-69 sit next to the apiToken field of M1-70. The canary test was built and reviewed on a chain with both tickets below it; landing it earlier would need a copy of their harness code.