fix(engineering): V1-0 gap closures per Codex review
Codex audit of cbf9e03 surfaced two P1 gaps + one P2 scope concern,
all verified with code-level probes. Patches below.
P1: promote_entity did not re-check F-8 at status flip.
Legacy candidates with source_refs='[]' and hand_authored=0 can
exist from before V1-0 enforcement. promote_entity now raises
ValueError before flipping status so no F-8 violation can slip
into the active store through the promote path. Row stays
candidate on rejection. Symmetric error shape with the create
side.
P1: supersede_entity was missing the F-5 hook.
Plan calls for synchronous conflict detection on every
active-entity write path. Supersede creates a `supersedes`
relationship rooted at the `superseded_by` entity, which can
produce a conflict the detector should catch. Added
detect_conflicts_for_entity(superseded_by) call with fail-open
per conflict-model.md:256.
P2: backfill script --invalidate-instead was too broad.
Query included both active AND superseded rows; invalidating
superseded rows collapses audit history that V1-0 remediation
never intended to touch. Now --invalidate-instead scopes to
status='active' only. Default hand_authored-flag mode stays
broad since it's additive/non-destructive. Help text made the
destructive posture explicit.
Four new regression tests in test_v1_0_write_invariants.py:
- test_promote_rejects_legacy_candidate_without_provenance
- test_promote_accepts_candidate_flagged_hand_authored
- test_supersede_runs_conflict_detection_on_new_active
- test_supersede_hook_fails_open
Test count: 543 -> 547 (+4). Full suite green in 81.07s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -51,10 +51,20 @@ def run(db_path: Path, dry_run: bool, invalidate_instead: bool) -> int:
|
||||
)
|
||||
return 2
|
||||
|
||||
# Scope differs by mode:
|
||||
# - Default (flag hand_authored=1): safe/additive, applies to active
|
||||
# AND superseded rows so the historical trail is consistent.
|
||||
# - --invalidate-instead: destructive — scope to ACTIVE rows only.
|
||||
# Invalidating already-superseded history would collapse the audit
|
||||
# trail, which the plan's remediation scope never intended
|
||||
# (V1-0 talks about existing active no-provenance entities).
|
||||
if invalidate_instead:
|
||||
scope_sql = "status = 'active'"
|
||||
else:
|
||||
scope_sql = "status IN ('active', 'superseded')"
|
||||
rows = conn.execute(
|
||||
"SELECT id, entity_type, name, project, status, source_refs, hand_authored "
|
||||
"FROM entities WHERE status IN ('active', 'superseded') "
|
||||
"AND hand_authored = 0"
|
||||
f"SELECT id, entity_type, name, project, status, source_refs, hand_authored "
|
||||
f"FROM entities WHERE {scope_sql} AND hand_authored = 0"
|
||||
).fetchall()
|
||||
|
||||
needs_fix = []
|
||||
@@ -141,7 +151,13 @@ def main() -> int:
|
||||
parser.add_argument(
|
||||
"--invalidate-instead",
|
||||
action="store_true",
|
||||
help="Invalidate no-provenance rows instead of flagging hand_authored",
|
||||
help=(
|
||||
"DESTRUCTIVE. Invalidate active rows with no provenance instead "
|
||||
"of flagging them hand_authored. Scoped to status='active' only "
|
||||
"(superseded rows are left alone to preserve audit history). "
|
||||
"Not recommended for first run — start with --dry-run, then "
|
||||
"the default hand_authored flag path."
|
||||
),
|
||||
)
|
||||
args = parser.parse_args()
|
||||
return run(args.db, args.dry_run, args.invalidate_instead)
|
||||
|
||||
Reference in New Issue
Block a user