fix(context): reconcile .project/ reader with CNCF spec drift (closes #372) - #374
Merged
mlieberman85 merged 5 commits intoAug 16, 2026
Merged
Conversation
…arnitdevorg#372) Upstream `types.go` at cncf/automation drifted between commits 979abb1e07fa (2026-03-05) and 641b80619cd5 (2026-06-29). Two upstream changes affect fields darnit's reader consumes: - `project_lead` and each `package_managers[*]` value are now `StringOrSlice` shapes upstream (scalar OR list). Reader adds a private `_coerce_scalar_or_list` helper and routes both fields through it. Existing scalar-shape YAML parses identically; new list-shape YAML collapses to the first non-empty element per feature 030 Q1 (parse-only scope; multi-value support is a follow-up feature). - `cncf_slack_channel` was removed upstream and replaced by `slack_channels` (list of objects with a completely different shape). Reader keeps populating the existing scalar attribute from the old YAML key so real repos on the old key still audit identically, and emits `warnings.warn(DeprecationWarning, stacklevel=2)` naming both keys and the spec version (1.2.0) that carries the alias. Alias removes in the release immediately following 1.2.0 (feature 030 Q2). New `slack_channels` field is silently accepted via the existing `_extra` forward-compat catch-all; no `ProjectConfig` attribute added (feature 030 Q1: parse-only). `DOT_PROJECT_SPEC_VERSION` bumped 1.1.0 -> 1.2.0 per feature 030 Q3 (1:1 with `.github/dot-project-spec-hash.txt`). Tracked-hash file refreshed to the current upstream (`860df23e...`) so `test_upstream_spec_unchanged` passes on every new PR without a `--update-hash` override. Module docstring gains a reconciliation-history block; future reconciliations append rather than replace so grep-history stays intact. Zero product-source changes outside `packages/darnit/src/darnit/context/`; zero downstream consumer signatures affected.
…behavior Feature 030 US2. Two regression tests, both outside the `@pytest.mark.upstream` class so they run on every PR (not just nightly): - `test_upstream_spec_failure_message_names_both_hashes`: monkeypatches the tracked-hash and upstream-fetch functions to force a drift, then asserts the resulting `pytest.fail` message names both hashes AND points at `specs/030-dot-project-spec-sync/quickstart.md`. A future rewrite of the sync test that swallows either hash or drops the runbook pointer will trip this. - `test_upstream_spec_skips_when_offline`: monkeypatches `urllib.request.urlopen` to raise `URLError`, then asserts `fetch_upstream_types_go` raises `pytest.skip.Exception` (not `pytest.fail.Exception`). Locks FR-007 against a future rewrite of the fetch path. Also extends the sync test's failure message with a fourth block pointing at the reconciliation runbook so the next drift-detected PR gets a direct pointer at the runbook without hunting.
…den fixture Feature 030 US3 + SC-002. Three new test surfaces: - `tests/darnit/context/fixtures/full_field_coverage.yaml`: a single `.project/project.yaml` populated with representative values for every field the reader exposes today. `project_lead` uses the NEW list form so the scalar-or-list coercer is exercised on every CI run; `package_managers.docker` uses the list form for the same reason; `cncf_slack_channel` uses the OLD YAML key so the deprecation warning path is exercised (and separately asserted). - `tests/darnit/context/test_full_field_coverage.py`: golden-dict comparison at the mapper boundary. `test_reader_output_matches_golden` loads the fixture through `DotProjectMapper.get_context()` and asserts the flat CEL context dict equals a hand-authored `EXPECTED` dict. Any silent semantic drift in a future reconciliation trips this. `test_extra_captures_slack_channels` verifies the NEW-IGNORED handling of the new `slack_channels` upstream field: raw parsed value lands in `ProjectConfig._extra['slack_channels']` verbatim and no attribute is projected on `ProjectConfig`. - `tests/darnit/context/test_dot_project_deprecations.py`: locks both directions of the `cncf_slack_channel` deprecation warning. `test_cncf_slack_channel_emits_deprecation_warning` asserts the presence case emits a `DeprecationWarning` naming the old key, the replacement, and spec version 1.2.0. `test_no_warning_when_cncf_slack_channel_absent` asserts a migrated repo (or one that never had the field) is NOT nagged; important because false-positive nags erode signal quality.
Adds the full speckit trail for feature 030-dot-project-spec-sync: - `spec.md` (with 3 clarifications recorded 2026-08-14: parse-only scope for new fields; one-release grace + `DeprecationWarning` for renamed fields; version identifier bumps 1:1 with tracked-hash file). - `plan.md` — technical context, constitution check (PASS on all 5 principles), project structure. - `research.md` — Phase 0 decisions with rationale and rejected alternatives. - `data-model.md` — per-field classification of every `ProjectConfig` attribute (KEPT / KEPT-WITH-RESHAPE / KEPT-WITH-ALIAS / NEW-IGNORED vocabulary that the next reconciliation reuses). - `contracts/reader-contract.md` — public reader API contract with exact deprecation-warning message text. - `quickstart.md` — maintainer runbook for THIS reconciliation and the next one. The upstream-sync test's failure message now points here. - `upstream-diff.md` — authoritative diff summary between tracked hash and current upstream. - `tasks.md` — 20 tasks, all completed. - `checklists/requirements.md` — spec-quality validation. Also updates the speckit plan pointer in `CLAUDE.md` and `.specify/feature.json` to feature 030.
The test at `tests/darnit/parity/tier1/test_no_product_changes.py`
enforced feature 028's SC-006 ("parity-tests PR MUST NOT modify product
source"), but it ran on every PR that pytest collected -- so any PR
that legitimately edits `packages/*/src/` (e.g., feature 030's
`.project/` reader reconciliation) tripped the guardrail with a false
positive.
FR-014's scope is stated in its own name: "no product changes on a
PARITY-TESTS PR." A PR that does not touch `tests/darnit/parity/` is
not a parity-tests PR and the check does not apply.
Add an early skip when the diff against the base ref contains zero
files under `tests/darnit/parity/`. Feature 028's own guarantee is
unchanged: any PR that DOES touch parity tests still runs the full
grep and still fails on a product-source touch, matching what the
reviewer of darnitdevorg#370 asked for.
mlieberman85
force-pushed
the
030-dot-project-spec-sync
branch
from
August 16, 2026 00:40
7c03172 to
472c244
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
packages/darnit/src/darnit/context/dot_project.pywith the current CNCF.project/specification (upstreamtypes.goat commit641b80619cd5, 2026-06-29). Two upstream commits' worth of drift resolved:project_leadandpackage_managers[*]now accept scalar or list per upstreamStringOrSlice(collapses to first element for existing scalar consumers);cncf_slack_channelgains a one-release deprecation alias withwarnings.warn(DeprecationWarning); newslack_channelsfield silently accepted via the existing_extraforward-compat catch-all.DOT_PROJECT_SPEC_VERSIONfrom1.1.0to1.2.0and refreshes.github/dot-project-spec-hash.txtsotest_upstream_spec_unchangedpasses cleanly on every new PR without a--update-hashoverride.test_upstream_spec_failure_message_names_both_hashes(US2 loud-diagnostic regression),test_upstream_spec_skips_when_offline(FR-007 offline tolerance),test_full_field_coverage(SC-002 golden-dict parity across every consumer-facing field),test_extra_captures_slack_channels(NEW-IGNORED capture), andtest_dot_project_deprecations(both presence and absence cases of thecncf_slack_channelwarning).Closes #372.
Design decisions
Recorded in
specs/030-dot-project-spec-sync/spec.md## Clarifications, elaborated inresearch.md:_extrawithout aProjectConfigattribute. Wiring a specific new field to a control is a separate feature.cncf_slack_channelaccepted in 1.2.0 withDeprecationWarning; alias removes in the release immediately following.Constitution check
PASS on all five core principles (see
plan.md## Constitution Checkand post-Phase-1 recheck). The reconciliation:packages/darnit/core (Plugin Separation).Test plan
uv run pytest tests/ -q --deselect tests/darnit/context/test_dot_project_upstream.py::TestUpstreamSpecSync::test_upstream_spec_unchanged-> 2711 passed, 15 skipped, 1 deselected.uv run pytest tests/darnit/context/test_dot_project_upstream.py -v-> 5 passed (3 existing + 2 new regressions)._extratest:uv run pytest tests/darnit/context/test_full_field_coverage.py -v-> 2 passed.uv run pytest tests/darnit/context/test_dot_project_deprecations.py -v-> 2 passed (presence + absence).uv run ruff check .-> zero errors.git diff --name-only main..HEAD | grep -E 'packages/(darnit-baseline|darnit-gittuf|darnit-reproducibility)/src/'-> zero lines.defis the private_coerce_scalar_or_listhelper.Notes for reviewers
cncf_slack_channel(unchanged fixtures) now emit twoDeprecationWarningentries during the sweep. Expected and asserted-on separately.ruff format --check .reports pre-existing format debt across ~185 unrelated files. CI's format check runs withcontinue-on-error: trueso this does not block. My four edited/new files are individually consistent.