wdi-method 0.6.15 → 0.6.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1135,18 +1135,64 @@ def _dec_date(c: Corpus, dec: dict) -> dt.date | None:
1135
1135
  return None
1136
1136
 
1137
1137
 
1138
+ # A mandate whose authority ONCE stood. `accepted` and `applied` are live; `superseded` is retired,
1139
+ # and retirement is NOT retroactive — the owner accepted it in person, and what was taken under it
1140
+ # while it stood stays accepted. Reading this status as present tense is what turned a whole run's
1141
+ # decisions red the day the owner changed one setting of the mandate, with no repair available that
1142
+ # does not falsify the record: the decisions are frozen, and the supersession really happened.
1143
+ MANDATE_STOOD = ("accepted", "applied", "superseded")
1144
+
1145
+
1146
+ def _dec_fm(c: Corpus, dec: dict) -> dict:
1147
+ """The frontmatter of a decision's own file — `supersedes`/`superseded_by` live there in the
1148
+ template, and a product that wrote them only in the file is not wrong."""
1149
+ did = str(dec.get("id") or "")
1150
+ if not did:
1151
+ return {}
1152
+ for path in sorted(c.root.glob(f".control/decisions/{did}-*.md")):
1153
+ return frontmatter(path) or {}
1154
+ return {}
1155
+
1156
+
1157
+ def _revoked_on(c: Corpus, mandate: dict, by_id: dict[str, dict]) -> dt.date | None:
1158
+ """The day a superseded mandate stopped delegating: the date of the decision that replaced it.
1159
+
1160
+ For a mandate, supersession IS revocation — `wdi-autopilot` names it as the way a run is ended
1161
+ for good, and the way a setting is changed. So it binds tighter than `expires`, and the window
1162
+ ends at whichever of the two came first.
1163
+ """
1164
+ if str(mandate.get("status") or "") != "superseded":
1165
+ return None
1166
+ ref = str(mandate.get("superseded_by") or _dec_fm(c, mandate).get("superseded_by") or "").strip()
1167
+ if not ref:
1168
+ return None
1169
+ return _dec_date(c, by_id.get(ref) or {})
1170
+
1171
+
1138
1172
  def mandate_accept(c: Corpus, r: Result) -> None:
1139
- """A decision accepted BY DELEGATION points at a real mandate that had not lapsed when it was taken.
1173
+ """A decision accepted BY DELEGATION points at a mandate that stood, and had not ended, when it was taken.
1140
1174
 
1141
1175
  `wdi-autopilot` lets the agent accept decisions the owner would have accepted, and that is legal
1142
1176
  only because the owner accepted the MANDATE in person. So three things hold: a mandate is never
1143
- itself accepted by another decision — the chain of authority has a person at its root; an accepted
1144
- mandate names the day it ends, or it is standing permission; and a decision whose `accepted_by` is
1145
- a `DEC-` names one that is `type: mandate`, accepted, and unexpired on the decision's own date.
1177
+ itself accepted by another decision — the chain of authority has a person at its root; a mandate
1178
+ that stood names the day it ends, or it is standing permission; and a decision whose `accepted_by`
1179
+ is a `DEC-` names one that is `type: mandate`, stood on the decision's own date, and had not ended
1180
+ by then — expired, or superseded, whichever came first.
1181
+
1182
+ Every question it asks is about the PAST, so every answer is read from the past. A mandate's
1183
+ status today says when its authority ENDED, never that it was never granted: `superseded` is a
1184
+ retired mandate, and the decisions taken under it while it stood are still accepted. The one
1185
+ thing supersession does change is the window — see `_revoked_on`.
1146
1186
 
1147
1187
  It says nothing about WHAT was decided — that is the ledger's job and the owner's review.
1148
1188
  """
1149
1189
  by_id = {str(d.get("id")): d for d in c.decs}
1190
+ # Which mandates were actually USED. A retired mandate's obligations are read from what was taken
1191
+ # under it, not from its status: superseding one that delegated nothing owes no account of a run
1192
+ # that never happened, and superseding one that delegated forty decisions owes exactly what it
1193
+ # owed the day before — otherwise supersession is a way to make the ledger demand disappear.
1194
+ delegated_under = {str(d.get("accepted_by") or "").strip() for d in c.decs
1195
+ if str(d.get("type") or "") != "mandate"}
1150
1196
  for dec in c.decs:
1151
1197
  did = str(dec.get("id"))
1152
1198
  ref = str(dec.get("accepted_by") or "").strip()
@@ -1156,7 +1202,7 @@ def mandate_accept(c: Corpus, r: Result) -> None:
1156
1202
  r.fail("mandate-accept", did,
1157
1203
  f"is a mandate accepted by delegation (`accepted_by: {ref}`) — the mandate is the one "
1158
1204
  f"decision the owner accepts in person")
1159
- if status in ("accepted", "applied"):
1205
+ if status in ("accepted", "applied") or (status == "superseded" and did in delegated_under):
1160
1206
  if not ref:
1161
1207
  r.fail("mandate-accept", did, "is an accepted mandate and `accepted_by` names nobody — "
1162
1208
  "a person and a date is enough")
@@ -1177,6 +1223,17 @@ def mandate_accept(c: Corpus, r: Result) -> None:
1177
1223
  # silently disables the lapse comparison for every decision taken under this mandate.
1178
1224
  r.fail("mandate-accept", did, f"`mandate.expires: {raw}` is not a date — write `YYYY-MM-DD`. "
1179
1225
  f"An expiry nothing can read stops nothing")
1226
+ if status == "superseded" and _revoked_on(c, dec, by_id) is None:
1227
+ # Retiring a mandate and not dating the retirement leaves the delegation reading as
1228
+ # good until `expires` — the opposite of what superseding it was for. Same failure as
1229
+ # the unparseable expiry above: a bound that looks present and compares nothing.
1230
+ r.fail("mandate-accept", did,
1231
+ "is a superseded mandate with decisions accepted under it, and nothing dates the "
1232
+ "supersession — `superseded_by` naming the decision that replaced it, and "
1233
+ "`supersedes` back on that one (both sides, decision-guide.md), is what says when "
1234
+ "the delegation was revoked. Until it is there the mandate reads as delegating "
1235
+ "right up to its `expires`. Recording a supersession is the one edit an applied "
1236
+ "decision allows")
1180
1237
  continue
1181
1238
  if not ref.startswith("DEC-"):
1182
1239
  continue
@@ -1188,19 +1245,26 @@ def mandate_accept(c: Corpus, r: Result) -> None:
1188
1245
  r.fail("mandate-accept", did, f"`accepted_by: {ref}` is not a `type: mandate` decision — only a mandate "
1189
1246
  f"delegates acceptance")
1190
1247
  continue
1191
- if str(target.get("status") or "") not in ("accepted", "applied"):
1192
- r.fail("mandate-accept", did, f"`accepted_by: {ref}` is `{target.get('status')}`, not accepted "
1193
- f"nothing was delegated yet")
1248
+ if str(target.get("status") or "") not in MANDATE_STOOD:
1249
+ r.fail("mandate-accept", did, f"`accepted_by: {ref}` is `{target.get('status')}` nothing was ever "
1250
+ f"delegated. A mandate delegates from `accepted` onward, and a "
1251
+ f"`superseded` one still stands for what was taken before it ended")
1194
1252
  continue
1195
1253
  params = target.get("mandate") if isinstance(target.get("mandate"), dict) else {}
1196
1254
  expires = _dec_date(c, {"date": params.get("expires")})
1255
+ revoked = _revoked_on(c, target, by_id)
1197
1256
  when = _dec_date(c, dec)
1257
+ # Whichever end came first is the one that counts.
1258
+ limit, ended, tail = expires, "expired on", "the delegation had lapsed"
1259
+ if revoked and (expires is None or revoked < expires):
1260
+ limit, ended, tail = (revoked, "was superseded on",
1261
+ "the delegation was revoked then, whatever its `expires` still says")
1198
1262
  if when is None:
1199
1263
  r.fail("mandate-accept", did, f"is accepted under `{ref}` but no date says when — `date:` in its "
1200
- f"frontmatter is what the expiry is checked against")
1201
- elif expires and when > expires:
1202
- r.fail("mandate-accept", did, f"was taken on {when.isoformat()}, after `{ref}` expired on "
1203
- f"{expires.isoformat()} — the delegation had lapsed")
1264
+ f"frontmatter is what the mandate's window is checked against")
1265
+ elif limit and when > limit:
1266
+ r.fail("mandate-accept", did, f"was taken on {when.isoformat()}, after `{ref}` {ended} "
1267
+ f"{limit.isoformat()} — {tail}")
1204
1268
 
1205
1269
 
1206
1270
  def defect_root_cause(c: Corpus, r: Result) -> None: # was V20
@@ -1302,18 +1366,62 @@ PRUNE_DIRS = frozenset({
1302
1366
  })
1303
1367
 
1304
1368
 
1369
+ _IGNORED: dict[Path, frozenset[str]] = {}
1370
+
1371
+
1372
+ def _git_ignored(root: Path) -> frozenset[str]:
1373
+ """What git ignores in this tree, repo-relative posix — a whole ignored directory as `name/`.
1374
+
1375
+ ONE call for the whole run. `git check-ignore` per folder inside `os.walk` is one subprocess per
1376
+ folder, and on a big tree that costs more than the walk it is protecting.
1377
+
1378
+ Git answers rather than a hand-written parser, for the reason `_ignore_rule` sets out: nested
1379
+ `.gitignore` files, negation with `!`, and `core.excludesFile` are exactly where a parser of this
1380
+ one repo is wrong. `--directory` collapses a wholly-ignored folder into a single entry, which is
1381
+ the granularity the walker prunes at — and a folder holding TRACKED files is never collapsed, so
1382
+ corpus that is in git cannot be pruned away by this.
1383
+
1384
+ Outside a repo, or with no git, this is empty and PRUNE_DIRS carries the walk alone. That is why
1385
+ PRUNE_DIRS stays: it is the fallback, and a `node_modules/` nobody remembered to ignore still has
1386
+ to be pruned — a dangling symlink in one took a whole run down once.
1387
+ """
1388
+ got = _IGNORED.get(root)
1389
+ if got is None:
1390
+ out = git(root, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory", "-z")
1391
+ got = frozenset(x for x in (out or "").split("\0") if x)
1392
+ _IGNORED[root] = got
1393
+ return got
1394
+
1395
+
1305
1396
  def _walk_corpus(root: Path, suffixes: tuple[str, ...]) -> list[Path]:
1306
- """Every file under `root` with one of `suffixes`, sorted, pruning PRUNE_DIRS as it goes.
1397
+ """Every file under `root` with one of `suffixes`, sorted, pruning PRUNE_DIRS and what git ignores.
1398
+
1399
+ Ignored material is not this product's corpus: it is not in the clone, nobody reviews it, and
1400
+ nothing in it can be repaired by the reader of a finding. A vendored upstream checkout under
1401
+ `.temp/` produced 172 `cites-resolve` findings in one repo, every one of them about somebody
1402
+ else's source tree.
1307
1403
 
1308
1404
  Sorted because determinism is this script's contract: two runs over the same tree MUST report the
1309
1405
  same thing in the same order.
1310
1406
  """
1407
+ ignored = _git_ignored(root)
1408
+
1409
+ def rel(path: Path) -> str:
1410
+ try:
1411
+ return path.relative_to(root).as_posix()
1412
+ except ValueError: # a walk that left the tree — treat it as unignored and let PRUNE_DIRS rule
1413
+ return ""
1414
+
1311
1415
  out: list[Path] = []
1312
1416
  for dirpath, dirnames, filenames in os.walk(root, onerror=lambda _e: None):
1313
- dirnames[:] = sorted(d for d in dirnames if d not in PRUNE_DIRS)
1417
+ here = Path(dirpath)
1418
+ dirnames[:] = sorted(d for d in dirnames
1419
+ if d not in PRUNE_DIRS and f"{rel(here / d)}/" not in ignored)
1314
1420
  for name in filenames:
1315
- if name.endswith(suffixes):
1316
- out.append(Path(dirpath) / name)
1421
+ # An ignored FILE inside a folder that is otherwise corpus: git lists it on its own,
1422
+ # because `--directory` only collapses folders that are ignored whole.
1423
+ if name.endswith(suffixes) and rel(here / name) not in ignored:
1424
+ out.append(here / name)
1317
1425
  return sorted(out)
1318
1426
 
1319
1427