Merge pull request 'ci(545): require a **Signals:** line on decision records' (#547) from fix/545-signals-required into main
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 7m35s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 20m50s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 14m23s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 7m35s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 20m50s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 14m23s
This commit was merged in pull request #547.
This commit is contained in:
+8
-4
@@ -16,7 +16,10 @@ A migrated record is an H2 whose first non-blank content line is a metadata bloc
|
||||
|
||||
(Blockquoted here so the illustrative `key`/heading don't parse as a real record; the `## 2026-07-17
|
||||
— No persistent compiler servers …` entry below is a live example.) Five metadata fields: `key`
|
||||
(dotted, e.g. `ci.runner-placement`), `status`, `since`, `supersedes`, `superseded-by`. `status` is
|
||||
(dotted, e.g. `ci.runner-placement`), `status`, `since`, `supersedes`, `superseded-by`. The
|
||||
**`Signals:`** line is also required (validator-enforced, ersatztv#545): it is the keywords/paths
|
||||
MemPalace's recall matches on, so a record without it ingests with weak recall metadata and
|
||||
under-surfaces — pack it with synonyms, symbol names, `paths:`, and issue refs. `status` is
|
||||
one of:
|
||||
- **`active`** — the current, authoritative record for its `key`. Exactly one active record per key.
|
||||
- **`superseded`** — reversed by a newer record; relocated to `docs/decisions/archive/` with
|
||||
@@ -35,9 +38,10 @@ in on both records. Never silently rewrite a record's rationale prose in place.
|
||||
(this file + the topic files, excluding the archive) via `scripts/build_decisions_catalog.py` — run
|
||||
it after any status change; CI's `decisions lifecycle` job fails on drift (`--check`).
|
||||
|
||||
**Enforcement**: `scripts/decisions_validate.py` checks metadata well-formedness, one-active-record-
|
||||
per-key, reciprocal links, that no record vanishes from the active set without an archive copy, that
|
||||
the active catalog is in sync, and an aggregate active-corpus line budget (replaces the old 1800-line
|
||||
**Enforcement**: `scripts/decisions_validate.py` checks metadata well-formedness (including a required
|
||||
`Signals:` line), one-active-record-per-key, reciprocal links, that no record vanishes from the active
|
||||
set without an archive copy, that the active catalog is in sync, and an aggregate active-corpus line
|
||||
budget (replaces the old 1800-line
|
||||
floor on this single file). The Husky `pre-commit` hook runs the structural checks over the working
|
||||
tree; the CI `decisions lifecycle` job additionally runs the body-diff check with `--base`/`--head`.
|
||||
|
||||
|
||||
@@ -18,7 +18,11 @@ from pathlib import Path
|
||||
import scripts.decisions_lib as dl # noqa: E402 (run with PYTHONPATH=. or as module)
|
||||
|
||||
SKIP_HEADINGS = {"Index", "Active catalog", "Contents"}
|
||||
REQUIRED_META = ("key", "status", "since", "supersedes", "superseded_by")
|
||||
# `signals` is required alongside the lifecycle fields: the `**Signals:**` line (plus `key:`) is what
|
||||
# MemPalace's keyword recall matches on when surfacing a decision, so a record without it ingests with
|
||||
# weak recall metadata and produces confident false-negatives for the "MemPalace to find, file to
|
||||
# confirm" workflow (ersatztv#545).
|
||||
REQUIRED_META = ("key", "status", "since", "supersedes", "superseded_by", "signals")
|
||||
EDIT_TOKEN = "[decisions-edit]" # noqa: S105 (a commit-message marker, not a credential)
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ def _rec(**kw: Any) -> dl.Record:
|
||||
since="2026-01-01",
|
||||
supersedes="none",
|
||||
superseded_by="none",
|
||||
signals="concept · paths: a/b.py · issues: #1",
|
||||
)
|
||||
base.update(kw)
|
||||
return dl.Record(**base)
|
||||
@@ -131,6 +132,20 @@ def test_missing_since_fails():
|
||||
assert any("missing required metadata since" in e for e in _v([rec]))
|
||||
|
||||
|
||||
def test_missing_signals_fails():
|
||||
# `**Signals:**` is required (ersatztv#545): it is what MemPalace's keyword recall matches on, so
|
||||
# a record without it ingests with weak metadata and under-surfaces ("no convention exists").
|
||||
assert any("missing required metadata signals" in e for e in _v([_rec(key="a.b", signals=None)]))
|
||||
|
||||
|
||||
def test_empty_signals_fails():
|
||||
assert any("missing required metadata signals" in e for e in _v([_rec(key="a.b", signals="")]))
|
||||
|
||||
|
||||
def test_signals_present_passes():
|
||||
assert not any("signals" in e for e in _v([_rec(key="a.b", signals="concept · paths: x.py · issues: #1")]))
|
||||
|
||||
|
||||
def test_demoted_heading_fails():
|
||||
assert any(
|
||||
"demoted to legacy-unmigrated" in e for e in _v([_rec(key="a.b")], demoted=["2026-01-01 — Some decision (#1)"])
|
||||
|
||||
Reference in New Issue
Block a user