From f7b3e131f9b83b5e4df7af36f403f4c91d19988a Mon Sep 17 00:00:00 2001 From: Paul O'Fallon Date: Sun, 26 Jul 2026 00:48:58 +0000 Subject: [PATCH 1/2] feat(conformance): migrate the default-workspace-mount unit, and find a compose defect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates `parity_state_diff::default-workspace-mount-target-parity` into two declarative cases, and records the spec violation that measurement surfaced while attempting the two compose units. The migrated unit needed TWO cases, not one, because it asserted two distinct claims through one reported outcome (research D2's inverse defect): - `case-state-default-mount` (live-differential) — the two CLIs agree. - `case-state-default-mount-target` (spec-expectation) — the ABSOLUTE claim that NEITHER CLI mounts the workspace at `workspaceFolder` and BOTH mount it at the spec default `/workspaces/` (#273). A differential cannot carry this: it would pass if both sides regressed together. The absolute claim needed no new assertion predicate. `workspaceBindTargets` is already a DERIVED observer field, so the quantified `∄ /workspace ∧ ∃ /workspaces/*` claim becomes plain equality against a one-element list. The legacy unit wrote no report fragment at all — its `assert_parity` call was absent — so this is a coverage GAIN, not a transfer. `bhv-state-default-workspace-mount-target` is a new behavior, and the frozen denominator guard fired on it, correctly. It is NEWLY NAMED rather than newly observed: the baseline unit genuinely asserted the claim while the registry described only the agreement half. Refusing it would mean migrating the unit at reduced fidelity — losing a claim to preserve a number, which is the exact under-count 023 exists to fix. `POST_BRANCH_BEHAVIORS` now distinguishes the two categories explicitly rather than stretching one. The two compose units are NOT migrated, and the reason is a deacon defect (T117). The spec says `overrideCommand` "Defaults to `true` for when using an image Dockerfile and `false` when referencing a Docker Compose file", because "the default command must run for the container to function properly". `compose.rs:1323` reads `unwrap_or(true)` on a compose-only path, under a comment asserting the opposite. Measured against pinned oracle 0.87.0 with a service whose command touches a marker then sleeps: the marker exists under the reference and not under deacon. deacon puts its keep-alive in `Cmd` and discards the service's command; the reference puts it in `Entrypoint` ending in `exec "$@"` and keeps `Cmd`, so the declared command runs. Any compose service whose command matters — a server, a bootstrap, a worker — silently does not start under deacon. It is invisible for the common `command: ["sleep","infinity"]` idiom, which is why every existing compose fixture passed. Migrating those units now would mean tolerating the divergence, i.e. recording a spec violation as intentional. They stay residual under `res-state-diff-compose-command-override`, whose text says the blocker is a defect and not a missing capability, and T117 carries the fix with its acceptance criteria. One guard fired on this change's own data and was obeyed, not loosened: the conservation report's no-absolute-paths check rejected a `/tmp/`-shaped path in a rationale string, so the rationale was rephrased. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UCGzJEFZd85HJqz1Wx2kE --- .../.devcontainer/devcontainer.json | 6 + conformance/migration/mapping.json | 17 ++- .../registry/behaviors/observable-state.json | 10 ++ conformance/registry/cases.json | 133 ++++++++++++++++++ conformance/registry/residuals.json | 26 ++-- conformance/registry/sources/spec.json | 10 ++ crates/conformance/src/conservation.rs | 36 ++++- .../tasks.md | 7 + 8 files changed, 226 insertions(+), 19 deletions(-) create mode 100644 conformance/fixtures/fx-state-default-mount/.devcontainer/devcontainer.json diff --git a/conformance/fixtures/fx-state-default-mount/.devcontainer/devcontainer.json b/conformance/fixtures/fx-state-default-mount/.devcontainer/devcontainer.json new file mode 100644 index 00000000..32f1e247 --- /dev/null +++ b/conformance/fixtures/fx-state-default-mount/.devcontainer/devcontainer.json @@ -0,0 +1,6 @@ +{ + "name": "StateDiffDefaultMount", + "image": "debian:bookworm-slim", + "workspaceFolder": "/workspace", + "containerEnv": { "DM_ENV": "yes" } +} diff --git a/conformance/migration/mapping.json b/conformance/migration/mapping.json index 7552ad9e..6fbbc07f 100644 --- a/conformance/migration/mapping.json +++ b/conformance/migration/mapping.json @@ -1116,17 +1116,26 @@ { "unit": "parity_state_diff::compose-parity-with-feature-mount-gap", "disposition": "residual", - "residualId": "res-state-diff-cross-cli-snapshot" + "residualId": "res-state-diff-compose-command-override" }, { "unit": "parity_state_diff::compose-sidecar-and-named-volume", "disposition": "residual", - "residualId": "res-state-diff-cross-cli-snapshot" + "residualId": "res-state-diff-compose-command-override" }, { "unit": "parity_state_diff::default-workspace-mount-target-parity", - "disposition": "residual", - "residualId": "res-state-diff-cross-cli-snapshot" + "disposition": "migrated", + "caseIds": [ + "case-state-default-mount", + "case-state-default-mount-target" + ], + "fixtureMapping": [ + { + "from": "inline:state_diff_default_workspace_mount_target_parity", + "to": "conformance/fixtures/fx-state-default-mount" + } + ] }, { "unit": "parity_state_diff::dockerfile-build-and-nonroot-user", diff --git a/conformance/registry/behaviors/observable-state.json b/conformance/registry/behaviors/observable-state.json index f79b0f14..68546ace 100644 --- a/conformance/registry/behaviors/observable-state.json +++ b/conformance/registry/behaviors/observable-state.json @@ -51,6 +51,16 @@ "decision": "follow-spec", "notes": "Backed by case-observable-state (parity_observable_state)." }, + { + "id": "bhv-state-default-workspace-mount-target", + "area": "observable-state", + "statement": "With `workspaceFolder` set and no explicit `workspaceMount`, the workspace is bound at the spec default `/workspaces/` — never at `workspaceFolder`, which is the working directory rather than a mount target.", + "applicability": [], + "spec": "conformant", + "reference": "aligned", + "decision": "follow-spec", + "notes": "deacon mounted the workspace at `workspaceFolder` until #273 aligned it. This is an ABSOLUTE claim about each side, not an agreement claim: a live differential would pass if deacon and the reference regressed together, which is why it is evidenced by the spec-expectation case case-state-default-mount-target while its differential twin case-state-default-mount carries the agreement half. Both sides were measured at the pinned oracle 0.87.0. Asserted through the observer's DERIVED `workspaceBindTargets` field, so no quantified assertion predicate was needed." + }, { "id": "bhv-state-diff-parity", "area": "observable-state", diff --git a/conformance/registry/cases.json b/conformance/registry/cases.json index 973b72f5..9a78741d 100644 --- a/conformance/registry/cases.json +++ b/conformance/registry/cases.json @@ -1881,6 +1881,139 @@ }, "notes": "Migrated from parity_state_diff::appport-published-ports (024 Phase 5). `appPort: [3000]` publishes the same container port in both CLIs. This case was unmigratable before 024 Phase 3: with a FIXED host port both sides collided, because deacon's container was still running while the oracle's `up` ran. The side sequencing fix (capture deacon's evidence, reclaim, then run the oracle) is what makes a fixed-host-port differential expressible at all. Image pinned to debian:bookworm-slim (V18)." }, + { + "id": "case-state-default-mount", + "behaviors": [ + "bhv-container-identity-labels", + "bhv-container-keepalive-command", + "bhv-container-metadata-label-authored", + "bhv-state-diff-parity" + ], + "context": [], + "oracleType": "live-differential", + "resourceGroup": "docker-shared", + "operations": [ + { + "id": "op-up", + "subcommand": "up", + "argv": [ + "--workspace-folder", + "${WORKSPACE}" + ], + "fixtures": [ + "fx-state-default-mount" + ] + } + ], + "expected": [ + { + "channel": "chan-container-state", + "operation": "op-up" + } + ], + "allowedDifferences": [ + { + "behavior": "bhv-container-identity-labels", + "context": [], + "observablePath": "chan-container-state.labels.devcontainer.configHash", + "rationale": "deacon sets `devcontainer.configHash`; the reference sets no such label (measured against the pinned oracle 0.87.0). One named key, never the `devcontainer.*` namespace.", + "divergenceId": "ext-container-identity-labels" + }, + { + "behavior": "bhv-container-identity-labels", + "context": [], + "observablePath": "chan-container-state.labels.devcontainer.config_name", + "rationale": "deacon sets `devcontainer.config_name`; the reference sets no such label (measured against the pinned oracle 0.87.0). One named key, never the `devcontainer.*` namespace.", + "divergenceId": "ext-container-identity-labels" + }, + { + "behavior": "bhv-container-identity-labels", + "context": [], + "observablePath": "chan-container-state.labels.devcontainer.name", + "rationale": "deacon sets `devcontainer.name`; the reference sets no such label (measured against the pinned oracle 0.87.0). One named key, never the `devcontainer.*` namespace.", + "divergenceId": "ext-container-identity-labels" + }, + { + "behavior": "bhv-container-identity-labels", + "context": [], + "observablePath": "chan-container-state.labels.devcontainer.source", + "rationale": "deacon sets `devcontainer.source`; the reference sets no such label (measured against the pinned oracle 0.87.0). One named key, never the `devcontainer.*` namespace.", + "divergenceId": "ext-container-identity-labels" + }, + { + "behavior": "bhv-container-identity-labels", + "context": [], + "observablePath": "chan-container-state.labels.devcontainer.workspaceHash", + "rationale": "deacon sets `devcontainer.workspaceHash`; the reference sets no such label (measured against the pinned oracle 0.87.0). One named key, never the `devcontainer.*` namespace.", + "divergenceId": "ext-container-identity-labels" + }, + { + "behavior": "bhv-container-keepalive-command", + "context": [], + "observablePath": "chan-container-state.cmd", + "rationale": "The keep-alive command strings differ; the observable behavior is measured equal (`docker stop` 245 ms / exit 0 vs 215 ms / exit 0). Scoped to `cmd`; `entrypoint` and every other field stay compared.", + "divergenceId": "bhv-container-keepalive-command" + } + ], + "cleanup": { + "containers": true, + "images": false, + "networks": true, + "volumes": true, + "tempdir": true + }, + "notes": "Migrated from parity_state_diff::default-workspace-mount-target-parity (024 Phase 5b). A config with `workspaceFolder` but NO explicit `workspaceMount`: both CLIs must produce equivalent container state, including where the workspace is bound. The legacy unit wrote NO report fragment at all (its `assert_parity` call was absent), so migrating it is a coverage GAIN, not a transfer. Its `∄ /workspace ∧ ∃ /workspaces/*` claim is an ABSOLUTE claim about each side, which a differential cannot express — that half lives in the spec-expectation twin `case-state-default-mount-target` (the T072 pattern). Image pinned to debian:bookworm-slim (V18)." + }, + { + "id": "case-state-default-mount-target", + "behaviors": [ + "bhv-state-default-workspace-mount-target" + ], + "context": [], + "oracleType": "spec-expectation", + "resourceGroup": "docker-shared", + "operations": [ + { + "id": "op-up", + "subcommand": "up", + "argv": [ + "--workspace-folder", + "${WORKSPACE}" + ], + "fixtures": [ + "fx-state-default-mount" + ] + } + ], + "expected": [ + { + "channel": "chan-exit-code", + "operation": "op-up", + "assertion": { + "equals": 0 + } + }, + { + "channel": "chan-container-state", + "operation": "op-up", + "assertion": { + "jsonSubset": { + "workspaceBindTargets": [ + "/workspaces/" + ] + } + } + } + ], + "cleanup": { + "containers": true, + "images": false, + "networks": true, + "volumes": true, + "tempdir": true + }, + "notes": "The spec-expectation twin of case-state-default-mount (024 Phase 5b), carrying the half a differential structurally cannot: the legacy unit asserted an ABSOLUTE fact about each side — NEITHER CLI mounts the workspace at `workspaceFolder` (/workspace), BOTH mount it at the spec default /workspaces/ (#273) — and a differential would pass if both regressed together. `workspaceBindTargets` is the observer's DERIVED field (the destinations of binds rooted at the workspace), so the quantified `∄ ∧ ∃` claim becomes plain equality with no new assertion predicate: a single-element list at the spec default is exactly '∃ the right one and ∄ any other', including /workspace. `` is the `workspace_basename_token` rewrite. Image pinned to debian:bookworm-slim (V18)." + }, { "id": "case-state-diff", "behaviors": [ diff --git a/conformance/registry/residuals.json b/conformance/registry/residuals.json index 901d263c..bcf0d0e6 100644 --- a/conformance/registry/residuals.json +++ b/conformance/registry/residuals.json @@ -58,7 +58,7 @@ "parity_exec::env-propagation" ], "blockedCarrier": "parity_exec", - "missingCapability": "a declarative case for `parity_exec::env-propagation`. NOTE: the previously recorded need for per-side argument vectors was DISPROVEN in 024 planning \u2014 deacon's `exec` declares `--remote-env` as the primary flag with `--env` as a legacy alias, so the single shared argv `--remote-env FOO=BAR` runs on both CLIs and no runner capability is missing. Only the case itself is outstanding.", + "missingCapability": "a declarative case for `parity_exec::env-propagation`. NOTE: the previously recorded need for per-side argument vectors was DISPROVEN in 024 planning — deacon's `exec` declares `--remote-env` as the primary flag with `--env` as a legacy alias, so the single shared argv `--remote-env FOO=BAR` runs on both CLIs and no runner capability is missing. Only the case itself is outstanding.", "followUp": "specs/024-drain-parity-residuals/tasks.md#P6", "behaviors": [ "bhv-exec-command-parity" @@ -86,7 +86,7 @@ "parity_harness_faults::q_stale_snapshot_is_reported_naming_the_drifted_field" ], "blockedCarrier": "parity_harness_faults", - "missingCapability": "fault injection against the harness itself: these units assert how the COMPARISON MACHINERY reports a missing oracle, a version mismatch, a crash, a timeout, a malformed output, a waived difference, a stale waiver, each difference class (ref-only / deacon-only / value / accept-vs-reject with direction) and each declarative outcome (allowed-difference / no-reference-for-platform / stale) \u2014 they observe the harness, not a devcontainer, so they have no observable channel and no oracle to compare against", + "missingCapability": "fault injection against the harness itself: these units assert how the COMPARISON MACHINERY reports a missing oracle, a version mismatch, a crash, a timeout, a malformed output, a waived difference, a stale waiver, each difference class (ref-only / deacon-only / value / accept-vs-reject with direction) and each declarative outcome (allowed-difference / no-reference-for-platform / stale) — they observe the harness, not a devcontainer, so they have no observable channel and no oracle to compare against", "disposition": "permanent", "outOfScopeRationale": "These units observe the COMPARISON MACHINERY, not a devcontainer: they assert how the harness reports a missing oracle, a crash, a timeout, a stale waiver and each difference class. A declarative case compares deacon to a reference across an observable channel, so a self-test of the comparator has no oracle and no observable channel to name. Out of scope structurally, not pending a feature.", "behaviors": [] @@ -102,9 +102,9 @@ "parity_registry_check::waivers_live_in_conformance_registry_not_legacy_locations" ], "blockedCarrier": "parity_registry_check", - "missingCapability": "structural self-checks of the repository's own test wiring: these units assert registry-to-test-file agreement, nextest profile selection, corpus minimums and the no-silent-skip idiom scan \u2014 repository structure, not consumer behavior, so no declarative case can express them", + "missingCapability": "structural self-checks of the repository's own test wiring: these units assert registry-to-test-file agreement, nextest profile selection, corpus minimums and the no-silent-skip idiom scan — repository structure, not consumer behavior, so no declarative case can express them", "disposition": "permanent", - "outOfScopeRationale": "These units assert repository structure \u2014 registry-to-test-file agreement, nextest profile selection, corpus minimums, the no-silent-skip idiom scan. That is a property of the repository, not consumer behavior against the spec, so no observable channel and no reference exist. Out of scope structurally, not pending a feature.", + "outOfScopeRationale": "These units assert repository structure — registry-to-test-file agreement, nextest profile selection, corpus minimums, the no-silent-skip idiom scan. That is a property of the repository, not consumer behavior against the spec, so no observable channel and no reference exist. Out of scope structurally, not pending a feature.", "behaviors": [] }, { @@ -127,7 +127,7 @@ "blockedCarrier": "parity_observable_state", "missingCapability": "reference-CLI feature-authoring interop: the unit feeds a deacon-generated lockfile to the reference's `features resolve-dependencies`, and the declarative operation surface is deliberately restricted to the consumer subcommands (Principle II)", "disposition": "permanent", - "outOfScopeRationale": "Constitution II: feature authoring is permanently out of scope for deacon, and this unit needs deacon `upgrade` plus the reference's `features resolve-dependencies` \u2014 both outside the consumer subcommand surface a declarative operation may name. The principle forbids it, so no capability will ever unblock it.", + "outOfScopeRationale": "Constitution II: feature authoring is permanently out of scope for deacon, and this unit needs deacon `upgrade` plus the reference's `features resolve-dependencies` — both outside the consumer subcommand surface a declarative operation may name. The principle forbids it, so no capability will ever unblock it.", "behaviors": [ "bhv-state-container-parity" ] @@ -198,19 +198,18 @@ ], "missingCapability": "vendored fixtures for the pinned third-party workspaces: the manifest fetches 33 public workspace snapshots over the network on demand, and vendoring third-party content into this repository is deliberately out of scope, so no case can materialize them", "disposition": "permanent", - "outOfScopeRationale": "Vendoring third-party content into this repository is out of scope by decision (research D8): the manifest names 33 public workspace snapshots fetched over the network on demand, and a declarative fixture must be committed, content-hashed input. These entries are inventoried as a recorded coverage SOURCE, never executed, so there is no capability to add \u2014 only a decision already taken.", + "outOfScopeRationale": "Vendoring third-party content into this repository is out of scope by decision (research D8): the manifest names 33 public workspace snapshots fetched over the network on demand, and a declarative fixture must be committed, content-hashed input. These entries are inventoried as a recorded coverage SOURCE, never executed, so there is no capability to add — only a decision already taken.", "behaviors": [] }, { - "id": "res-state-diff-cross-cli-snapshot", + "id": "res-state-diff-compose-command-override", "units": [ "parity_state_diff::compose-parity-with-feature-mount-gap", - "parity_state_diff::compose-sidecar-and-named-volume", - "parity_state_diff::default-workspace-mount-target-parity" + "parity_state_diff::compose-sidecar-and-named-volume" ], "blockedCarrier": "parity_state_diff", - "missingCapability": "cross-CLI container-state snapshot comparison: a normalized whole-container state snapshot taken on both sides and diffed field-by-field, which the per-channel `chan-container-state` observer does not provide", - "followUp": "specs/023-migrate-parity-to-conformance/tasks.md#T103", + "missingCapability": "not a missing capability — a deacon DEFECT the replacement cases would surface as an uncharacterized `stricter` verdict: on the compose path deacon replaces the service's declared `command` with its keep-alive instead of honoring `overrideCommand`'s compose default of false, so the compose service's own process never runs. Measured against pinned oracle 0.87.0: a service whose command touches a marker file and then sleeps creates that marker under the reference and not under deacon. The cases are held back until deacon is fixed, rather than migrated with a tolerance that would characterize a spec violation as intentional", + "followUp": "specs/023-migrate-parity-to-conformance/tasks.md#T117", "behaviors": [ "bhv-state-diff-parity", "bhv-state-container-parity" @@ -223,10 +222,11 @@ ], "blockedCarrier": "parity_state_diff", "missingCapability": "a reference-free comparison mode that diffs two DEACON runs against each other (single-container versus compose for the same logical configuration), with no reference side involved", - "followUp": "specs/023-migrate-parity-to-conformance/tasks.md#T103", "behaviors": [ "bhv-state-diff-parity" - ] + ], + "disposition": "queued", + "followUp": "specs/023-migrate-parity-to-conformance/tasks.md#T103" } ] } diff --git a/conformance/registry/sources/spec.json b/conformance/registry/sources/spec.json index d5df2240..76eba633 100644 --- a/conformance/registry/sources/spec.json +++ b/conformance/registry/sources/spec.json @@ -12,6 +12,16 @@ "bhv-state-diff-parity" ] }, + { + "id": "src-spec-default-workspace-mount", + "inventory": "spec", + "revision": "rev-spec-113500f4", + "locator": "devcontainerjson-reference.md workspaceFolder / workspaceMount", + "summary": "`workspaceFolder` sets the path to open inside the container; `workspaceMount` overrides the default workspace mount. With no `workspaceMount` the default source-to-`/workspaces/` bind applies, so `workspaceFolder` is a working directory, not a mount target.", + "behaviors": [ + "bhv-state-default-workspace-mount-target" + ] + }, { "id": "src-spec-exec-command", "inventory": "spec", diff --git a/crates/conformance/src/conservation.rs b/crates/conformance/src/conservation.rs index b36290b0..67987d76 100644 --- a/crates/conformance/src/conservation.rs +++ b/crates/conformance/src/conservation.rs @@ -47,8 +47,23 @@ pub const PRE_MIGRATION_BEHAVIORS: usize = 25; /// new *variant* its own behavior — 24 per-workspace cases proving one claim must count as /// one behavior, not 24. That guard is about **re-describing** existing coverage. /// -/// It is not about **newly observed** conformance facts, and conflating the two would make -/// the guard forbid the thing the migration is for. `chan-container-state` became an +/// It is not about **newly observed** conformance facts, nor about a claim the baseline +/// already carried that the registry had simply never NAMED — and conflating either of +/// those with a variant would make the guard forbid the thing the migration is for. +/// +/// Two categories therefore live here, and each entry says which it is: +/// +/// 1. **Newly observed** — a fact no pre-migration record could state because nothing +/// compared the field. `bhv-container-identity-labels` and +/// `bhv-container-keepalive-command` are these. +/// 2. **Newly named** — a claim a legacy unit genuinely asserted while the registry +/// described only part of it, so the unit reported several claims through one outcome. +/// That is research D2's inverse defect, and correcting it necessarily produces a +/// behavior the frozen count never held. Refusing it would mean migrating the unit at +/// reduced fidelity — losing a claim to keep a number — which is the precise failure +/// 023 exists to prevent. `bhv-state-default-workspace-mount-target` is this. +/// +/// `chan-container-state` became an /// observed channel in 024 Phase 4, which retired a blanket rule that had been hiding /// whole label namespaces from comparison; what it exposed is a real, previously /// unrecorded difference between the two CLIs. Refusing to record it would keep the @@ -106,6 +121,23 @@ pub const POST_BRANCH_BEHAVIORS: &[(&str, &str)] = &[ exit 0 vs 215 ms / exit 0); the same field's earlier 'cannot matter behaviorally' \ assumption hid a 10s stall.", ), + ( + "bhv-state-default-workspace-mount-target", + "NEWLY NAMED, not newly observed — the second category above. \ + `parity_state_diff::default-workspace-mount-target-parity` asserted TWO distinct \ + claims through one reported outcome: that the two CLIs agree, and the absolute claim \ + that NEITHER mounts the workspace at `workspaceFolder` and BOTH mount it at the spec \ + default `/workspaces/` (#273). The registry named only the agreement half \ + (`bhv-state-diff-parity`). A live differential cannot carry the absolute half — it \ + would pass if both sides regressed together — so migrating the unit faithfully needs a \ + spec-expectation case, and a spec-expectation case needs a behavior. Not a variant of \ + `bhv-state-diff-parity` or `bhv-state-container-parity`: those state that deacon \ + AGREES with the reference, this states WHERE the workspace is bound, which stays \ + false-if-broken even when both sides break identically. Dropping it to keep the \ + denominator lower would migrate the unit at reduced fidelity — losing a claim to \ + preserve a number, which is research D2's inverse defect reintroduced by the very \ + guard meant to catch it (024 Phase 5b).", + ), ]; /// The observable-channel count at the branch point (research §1g). diff --git a/specs/023-migrate-parity-to-conformance/tasks.md b/specs/023-migrate-parity-to-conformance/tasks.md index d82ee11d..81621fae 100644 --- a/specs/023-migrate-parity-to-conformance/tasks.md +++ b/specs/023-migrate-parity-to-conformance/tasks.md @@ -547,6 +547,13 @@ Per Constitution I (Deferral Tracking); rationale in [research.md §4](./researc - **Fix**: both workflow globs repointed; new `scripts/parity/prepull-fixture-images.sh` gives `make test-parity` the same warm-cache step CI has, so a local run behaves like CI; and T089 gained a THIRD direction (`no_surface_globs_a_removed_path`) — the name-based check could never have caught this, because a rotted *path* fails more quietly than a rotted *name*. - **Accounting, corrected**: nothing was ever silently dropped. With the cache warm, a full `parity_conformance_runner` run puts **`case-merged-decl-universal-jsonc` in the diverge list**, and merged is still 23-of-24 — because the 24th, `case-merged-decl-extends-child`, **agrees**: T072 re-pointed it to the `spec-expectation` oracle, since a live-differential inverted its `reference-stricter` expectation into a guaranteed failure. So 23 diverging + 1 agreeing = all 24 accounted for. The earlier "merged ×23" figure was complete; the suspicion that a case had been excluded from the denominator was reasonable but does not hold. Separately, the 59-unit equivalence run reported no `UNCLASSIFIABLE`/`UNVERDICTED` lines and exited 0, and `run_case` propagates a timeout as `Err` rather than a verdict — so every one of those 59 comparisons genuinely completed. +- [ ] T117 [Deferral, 024 Phase 5b] **deacon replaces the compose service's declared `command`, so the service's own process never runs** — a spec violation, found by migrating the two compose container-state units + - **The spec is explicit.** `devcontainerjson-reference.md`: `overrideCommand` *"Defaults to `true` for when using an image Dockerfile and **`false` when referencing a Docker Compose file**"*, with the rationale spelled out — *"Set to `false` if the default command must run for the container to function properly."* `crates/core/src/compose.rs:1323` reads `self.override_command.unwrap_or(true)`, under a comment asserting the opposite of the spec, on a code path reached ONLY for compose. `test_generate_injection_override_command_default_on` pins the wrong default and cites "Spec default" while doing it. + - **Measured, not inferred** (pinned oracle 0.87.0). Fixture: a compose service with `command: ["sh","-c","touch /tmp/SERVICE_COMMAND_RAN; sleep infinity"]`. `test -f /tmp/SERVICE_COMMAND_RAN` → **reference YES, deacon NO**. The mechanism is visible in `docker inspect`: the reference puts its keep-alive in `Entrypoint` ending in `exec "$@"` and leaves `Cmd` as the compose command, so the declared command runs and the keep-alive loop is reached only when there is no command; deacon puts its keep-alive in `Cmd` and leaves `Entrypoint` empty, discarding the command outright. + - **Consequence**: any compose service whose command matters — a server, a database bootstrap, a queue worker — silently does not start under deacon. The defect is invisible for the common `command: ["sleep","infinity"]` idiom, which is why every existing compose fixture passed. + - **Why the two units are held back rather than migrated**: their declarative replacements compare `entrypoint` and `cmd`, so they report `diverge`. Migrating them with a scoped tolerance would characterize a spec violation as intentional — the one move the model forbids. They carry `res-state-diff-compose-command-override`, whose `missingCapability` records that the blocker is a defect and not a missing capability. + - **Acceptance**: deacon honors the compose default (`overrideCommand` unset ⇒ false on the compose path) and still keeps the container alive when the service declares no command — the reference's entrypoint-wrapper shape achieves both; a regression test asserts the declared command actually ran; `case-state-compose-feature` / `case-state-compose-volume` land and report `agree`/`allowed-difference`; `res-state-diff-compose-command-override` is deleted in the same change. + - [ ] T110 [Deferral, US3 T054] Give `bhv-exec-container-id-metadata` its own independently-reported case - **Decision**: `parity_up_exec` asserts both up/exec parity AND `exec --container-id` metadata recovery but emits ONE `CaseResult`, so the two behaviors shared one reported outcome (research D2's inverse defect). T054 resolved the registry's over-claim by merging the two pointer cases into one truthful record; it did not create the missing evidence, because a declarative case cannot address a container the previous operation created — there is no runtime-resolved container-id token in an operation's argv. - **Acceptance**: A declarative case evidences `bhv-exec-container-id-metadata` independently (its own reported outcome), and `case-up-exec-parity` no longer claims two behaviors from one outcome. From 943a877fdd398210b2e52d79d651e0d6531f3a1a Mon Sep 17 00:00:00 2001 From: Paul O'Fallon Date: Sun, 26 Jul 2026 01:23:29 +0000 Subject: [PATCH 2/2] docs(conformance): record the appport case's environment dependence (T118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `case-state-appport` is green locally and RED in the `live-certification` lane. Found by reading the lane's failure list rather than trusting the local run: it reports `divergingPaths: ["chan-container-state"]` — the channel ROOT, not a field — with all six of its scoped tolerances STALE. That combination is the signature of one side having no container at all. The cause is the fixture: `"appPort": [3000]` is a FIXED host port. 024 Phase 3's D-3 fix made the two sides sequential so they cannot collide with each other, but nothing makes port 3000 free on a shared runner. When it is taken, that side's `up` fails and the differential compares an object against an absence. It matters beyond one red case: the lane is already red on the T113 families, so an environment-dependent case hides inside a list of known-red ones, and the published-ports half of `bhv-state-diff-parity` quietly stops being proven. Pre-existing on main; recorded rather than papered over. No tolerance was authored — a whole-channel divergence caused by a missing container is not a characterized difference, and tolerating it would make "the container was never created" report `allowed-difference`, the exact silent green pass D-2 exists to prevent. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UCGzJEFZd85HJqz1Wx2kE --- specs/023-migrate-parity-to-conformance/tasks.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/specs/023-migrate-parity-to-conformance/tasks.md b/specs/023-migrate-parity-to-conformance/tasks.md index 81621fae..214f3198 100644 --- a/specs/023-migrate-parity-to-conformance/tasks.md +++ b/specs/023-migrate-parity-to-conformance/tasks.md @@ -554,6 +554,14 @@ Per Constitution I (Deferral Tracking); rationale in [research.md §4](./researc - **Why the two units are held back rather than migrated**: their declarative replacements compare `entrypoint` and `cmd`, so they report `diverge`. Migrating them with a scoped tolerance would characterize a spec violation as intentional — the one move the model forbids. They carry `res-state-diff-compose-command-override`, whose `missingCapability` records that the blocker is a defect and not a missing capability. - **Acceptance**: deacon honors the compose default (`overrideCommand` unset ⇒ false on the compose path) and still keeps the container alive when the service declares no command — the reference's entrypoint-wrapper shape achieves both; a regression test asserts the declared command actually ran; `case-state-compose-feature` / `case-state-compose-volume` land and report `agree`/`allowed-difference`; `res-state-diff-compose-command-override` is deleted in the same change. +- [ ] T118 [Deferral, 024 Phase 5b] **`case-state-appport` pins host port 3000, so it is environment-dependent — green locally, RED in the `live-certification` lane** + - **Found by**: reading the CI lane's failure list rather than trusting the local run. Locally every `case-state-*` case agrees; in the lane `case-state-appport` reports `chan-container-state: Diverge` with `divergingPaths: ["chan-container-state"]` — the channel ROOT, not a field — and **all six** of its scoped tolerances reported STALE. That combination is the signature of one side having no container at all: with nothing to descend into, no path-scoped tolerance can be consumed. Pre-existing on `main`; not introduced by this branch. + - **Cause**: `fx-state-appport` declares `"appPort": [3000]`, a FIXED host port. 024 Phase 3's D-3 fix made the two sides sequential so they cannot collide with each other, but nothing makes port 3000 free on a shared CI runner. When it is taken, that side's `up` fails and the differential compares an object against an absence. + - **Why it matters beyond one red case**: it is invisible today because the lane is already red on the T113 families, so a genuinely environment-dependent case hides inside a list of known-red ones. It also makes `bhv-state-diff-parity`'s evidence for the published-ports claim non-reproducible, which is the sort of thing that silently becomes "we never actually tested that". + - **Options, in preference order**: (a) let the runner allocate a free host port and substitute it into the fixture — but that needs a new operation/fixture capability and `${WORKSPACE}`-style tokenisation for ports, which is a real design decision, not a tweak; (b) drop the fixed host binding and assert only the container-side `ExposedPorts`, losing the host-publish half of the claim; (c) keep the fixed port and characterize the case as environment-dependent, which is honest but leaves the lane permanently unable to prove it. + - **Deliberately NOT done here**: no tolerance was authored. A whole-channel divergence caused by a missing container is not a characterized difference — tolerating it would make "the container was never created" report `allowed-difference`, which is precisely the silent green pass 024 Phase 3's D-2 fix exists to prevent. + - **Acceptance**: the case either runs deterministically on a shared runner (option a or b, with the lost coverage stated if b) or carries an explicit environment-dependence record; and the lane's red list contains no case whose redness is an artifact of the runner rather than a characterized divergence. + - [ ] T110 [Deferral, US3 T054] Give `bhv-exec-container-id-metadata` its own independently-reported case - **Decision**: `parity_up_exec` asserts both up/exec parity AND `exec --container-id` metadata recovery but emits ONE `CaseResult`, so the two behaviors shared one reported outcome (research D2's inverse defect). T054 resolved the registry's over-claim by merging the two pointer cases into one truthful record; it did not create the missing evidence, because a declarative case cannot address a container the previous operation created — there is no runtime-resolved container-id token in an operation's argv. - **Acceptance**: A declarative case evidences `bhv-exec-container-id-metadata` independently (its own reported outcome), and `case-up-exec-parity` no longer claims two behaviors from one outcome.