Skip to content

feat: tentative retirement floors and owned expiring waivers (#34, #80) - #91

Merged
thossullivan merged 37 commits into
mainfrom
feat/tentative-dates-and-waivers
Sep 3, 2026
Merged

feat: tentative retirement floors and owned expiring waivers (#34, #80)#91
thossullivan merged 37 commits into
mainfrom
feat/tentative-dates-and-waivers

Conversation

@thossullivan

Copy link
Copy Markdown
Owner

Closes #34. Closes #80.

Tentative retirement floors (#34)

Anthropic's deprecations page lists a "Not sooner than " tentative retirement date for every active model. The refresh now parses that status table for the Anthropic provider and publishes those dates as shutdown with a new date_precision: "tentative" and no announced date.

  • tentative is a third precision value in SPEC.md and the feed and report schemas. earliest keeps its meaning and behavior in full.
  • The checker treats a tentative date as a floor: the finding is scheduled, never retiring or retired, and safe_until is the later of the floor and the publisher policy date. It cannot fail CI and the bot opens no work for it.
  • Announcement tables always win for the same model. Non-active status rows without an announcement produce an announced plus exact shutdown record. Unrecognised lifecycle text throws; a status merge never deletes a committed announced or exact shutdown.
  • No feed files change here; the next weekly refresh regenerates feeds/anthropic.json (11 tentative rows on today's page).

Sample:

· app.py:1  claude-sonnet-4-5-20250929  scheduled - tentative, not announced: not sooner than 2026-09-29, guaranteed until 2026-10-17 by anthropic policy

Waivers (#80)

waivers in .model-eol.json (root and path overrides) records an owned, expiring exception on a live dependency: model, optional paths and via, reason, owner, absolute expires. Fail-closed validation, cap of 500.

  • A waived finding stays in check, inventory, schedule, alert, plan, and CycloneDX with a waiver object (reason, owner, expires, active).
  • An active waiver removes a retired/retiring finding from the exit-1 total, badge counts, and alert errors, and the plan records reason waived so the bot creates no work.
  • On the expiry date (UTC) the finding is actionable again with the lapsed waiver named. The bot closes existing work as stale when a waiver begins and treats an expired waiver as a fresh reintroduction.
  • waiver is optional in the 0.1 report schemas so existing documents still validate. CycloneDX marks a component waiver_active: true only when every occurrence is waived, partial otherwise. via accepts a distributor id, publisher, or publisher-fallback.

Sample:

✗ app.py:2  o3-deep-research  RETIRED 2026-07-23 (42 days ago) -> gpt-5.6-sol  [waived until 2026-12-31 by @research-platform: Research pipeline pinned until the Q4 rerun.]

README

One pass to integrate both features and tighten the policy sections. Sample lines are real output.

Verification

  • npm test green on the merged branch.
  • Read-only cross-repo sweep over local repositories: identical totals to main (ok 1845 / retired 644 / retiring 42 / scheduled 10).
  • Codex adversarial review, pass 1: seven findings. Six fixed in this branch (the first cut wrongly cleared 43 real Google earliest deprecations; parser fail-open paths; status merge erasing announced; waiver required in 0.1 schemas; CycloneDX mixed-component waiver; publisher-clock waivers). One rejected: invalidating pre-waiver human dismissals.
  • Codex adversarial review, pass 2: not run. The Codex backend is returning 404 at the time of writing; a retry is queued and its result will be posted here.

Implemented by Codex from Claude briefs with three correction rounds; reviewed, merged, and tested by Claude.

The Anthropic deprecations page opens with a model-status table that lists
active models with "Not sooner than <date>" retirement dates. The refresh
now parses that table for the Anthropic provider only and publishes those
dates as shutdown with date_precision "earliest" and no announced date.
Announcement tables always win for the same model; status rows for
deprecated or retired models without an announcement are skipped with a
parser note; an N/A row drops a previously tentative date.

The checker treats a publisher-clock earliest date with no announcement as
a tentative floor: the finding is always "scheduled", never "retiring" or
"retired", and safe_until is the later of the floor and the publisher's
minimum-notice policy date. Human output labels the floor as tentative and
not announced. Plans and the bot create no migration work for it. No schema
or feed files change; the weekly refresh regenerates the feed.

Implemented by Codex (gpt-5.6-luna) from a Claude brief with one correction
round on the checker rule; reviewed and tested by Claude.
A new "waivers" list in .model-eol.json (root and path overrides) records
an owned, expiring exception for a live model dependency: model, optional
paths and via, reason, owner, and an absolute expires date. Waivers are
validated fail-closed with the same strictness as the rest of the config
and capped at 500.

A waived finding stays in every report with a waiver object (reason,
owner, expires, active). An active waiver removes a retired or retiring
finding from the exit-1 total, the badge counts, and the alert errors, and
plans record it with reason "waived" so the bot creates no work. On the
expiry date the finding is actionable again and reports say why. The bot
closes existing work as stale when a waiver begins, naming the owner and
expiry, and treats an expired waiver as a fresh reintroduction by binding
the expiry into the work metadata so an earlier dismissal is not inherited.

Schemas for bot config, check, inventory, and plan gain the waiver field;
CycloneDX output exposes active suppression as component properties.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed and
tested by Claude.
Adversarial review findings on the waiver work: the report schemas had
made waiver a required finding field, which rejected every existing
model-eol/check@0.1 document; the CycloneDX exporter marked a whole
component waived when only some of its occurrences were; and a waiver
could not name the publisher clock because configured-channel validation
rejected the literal. waiver is now optional-nullable in the check,
inventory, and plan schemas, CycloneDX emits waiver_active "true" only
when every occurrence is waived and "partial" otherwise, and waiver.via
accepts publisher and publisher-fallback.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
…e parser

The first cut keyed the tentative-floor rule on earliest precision with no
announced date. The Google feed carries 43 real deprecations in exactly
that shape, so a retired gemini-2.0-flash reference stopped failing CI.
date_precision now has a third value, "tentative", meaning a publisher
not-before date for a model whose deprecation is not announced. Only that
value triggers the floor rule; earliest keeps its scheduled-date meaning
on every clock. Feed and report schemas accept the new value, the diff
prints "(tentative)", and human output reads "not sooner than".

Adversarial review also found two fail-open paths in the status-table
parser. Unrecognised retirement text now throws instead of yielding an
id-only record, non-active rows without an announcement entry produce an
announced plus exact-shutdown record instead of a notice, and a status
merge never deletes a committed announced date or exact shutdown.

Implemented by Codex (gpt-5.6-luna) from Claude briefs; reviewed by Claude.
# Conflicts:
#	README.md
#	lib/feeds.mjs
#	scripts/test-document-validation.mjs
# Conflicts:
#	README.md
#	scripts/test-document-validation.mjs
@thossullivan

Copy link
Copy Markdown
Owner Author

Review gate status (2026-09-03):

  • Codex adversarial pass 1: 7 findings, 6 fixed on this branch, 1 rejected (pre-waiver dismissal invalidation; rationale in the PR body).
  • Codex adversarial pass 2: backend returned 404 on every request at PR-open time. A retry loop is running; the result will be posted here.
  • Claude inline review of the final merged diff (main...HEAD): clean. Checked and cleared: tentative rule keyed only on date_precision: "tentative"; earliest lifecycle path unchanged (Google entries still retire); findingFromRef returns the finding with waiver attached; validator/diff/distributor parser accept tentative; status merge preserves committed announced and exact shutdown; no schema required array contains waiver; pre-waiver 0.1 documents validate; README sample lines match real output.
  • Full npm test green; read-only cross-repo sweep identical to main.

@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 2 ran (2026-09-03, after the outage). Verdict: needs-attention, 4 findings, all accepted and being fixed on this branch:

  1. New producer output includes waiver: null on every finding, which strict 0.1 schemas pinned from 0.5.2 reject. Fix: emit waiver only when a waiver matched; add a test that default output validates against the 0.5.2 schemas.
  2. The tentative branch in lifecycleFor ran before retired handling and ignored announced. Fix: reorder, require no announced on the judged clock, and reject tentative + announced / tentative + retired distribution in feed validation.
  3. The Anthropic status parser accepted unknown states and inconsistent cells. Fix: state allowlist (Active/Deprecated/Retired), Active rows must have no Deprecated date, shutdown must not precede announced.
  4. A feed distribution named publisher or publisher-fallback collides with the publisher-clock waiver literals. Fix: reserve those names in feed validation.

Cleared by the review: Google earliest entries still retire; no schema required contains waiver; CycloneDX partial; README samples match real output; findingFromRef has no dead path.

Second adversarial pass: the tentative branch in lifecycleFor ran before
retired handling and ignored announced, so contradictory records could
clear CI. Feed validation now rejects tentative with announced and
tentative on a retired distribution; the checker judges retired status
first and applies the floor only when the judged clock has no announced
date. The Anthropic status parser allows only Active, Deprecated, and
Retired, refuses an Active row with a Deprecated date, and refuses a
shutdown before its announcement. Distribution via values "publisher" and
"publisher-fallback" are reserved so they cannot collide with the checker's
publisher clock or with publisher-clock waivers.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
Second adversarial pass: every finding carried waiver: null, and the 0.1
report schemas published by 0.5.2 use additionalProperties false, so a
consumer pinned to those schemas rejected every new report. Findings,
model references, plan items, and plan issues now include waiver only
when a waiver matched (active or expired), so a repository without
waivers produces the same shape as 0.5.2. The 0.5.2 check, inventory,
and plan schemas are snapshotted under test/fixture/schemas-0.5.2 and
default output is validated against them.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 3 ran. All four pass-2 fixes cleared (waiver omitted when unmatched and default output validates against pinned 0.5.2 schemas; retired status wins and tentative floors require no announcement; parser state allowlist and date ordering; reserved publisher clock names). Three new findings, all accepted and being fixed on this branch:

  1. Anthropic status merge is not state-aware: a Retired row cannot replace stale committed exact dates, and an Active tentative row on a previously announced model yields announced + tentative, which feed validation rejects. Fix: merge by row state; Active clears lifecycle fields then applies the floor, Deprecated/Retired apply the row's dates.
  2. A status table with an extra column is silently unrecognised, dropping Retired rows. Fix: index header cells by label, throw on a partially matching header, throw when Anthropic has no status table.
  3. CycloneDX drops expired-waiver evidence. Fix: export waiver_active: "false" with the matched waiver metadata.

Third adversarial pass: a reference whose matched waiver had expired
exported no waiver properties, so the audit artifact lost the owner,
reason, expiry, and the fact that a waiver lapsed. A component whose
occurrences carry only expired matches now exports waiver_active "false"
with the matched waiver metadata; active-plus-expired stays "partial".

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
Third adversarial pass: the status merge ignored the parsed row state, so
a Retired row could not replace stale committed dates and an Active
tentative row on a previously announced model produced a feed that failed
validation. Each status record now carries its state; Active rows retract
committed lifecycle and replacement fields before applying the floor, and
Deprecated or Retired rows replace the committed dates. The header matcher
indexes cells by label so extra or reordered columns are tolerated, a
header with the tentative column but missing other required columns
throws, and an Anthropic page with no status table throws.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 4 ran. All three pass-3 fixes cleared (state-aware status merge with every result passing validateFeed; label-indexed status headers with fail-closed drift; expired-waiver CycloneDX evidence). Three findings:

  1. Accepted. A header-only status table counted as parsed, so stale active lifecycle data could survive silently. Fix: require at least one parsed status row; reject duplicate required headers.
  2. Accepted. A status table with an extra Recommended replacement column is claimed by the generic announcement classifier and halts the refresh. Fix: exclude tables carrying the tentative column from the generic classifier.
  3. Policy, not code - flagged for the maintainer. tentative and waiver are added under the unchanged 0.1 schema identity, so a consumer pinned to the 0.5.2 validator rejects a refreshed hosted feed or a waiver-bearing report. Pre-1.0 additive evolution is the working assumption here; the alternative is a 0.2 schema line. A compatibility note goes into SPEC.md; the decision to bump the schema line instead is the maintainer's.

…rser

Fourth adversarial pass: a header-only status table counted as parsed, so
stale active lifecycle data could survive silently, and a status table
with an extra Recommended replacement column was claimed by the generic
announcement classifier and halted the refresh. The status parser now
counts parsed rows before announcement deduplication and throws on zero
rows or on duplicate required headers; the generic classifier skips any
Anthropic table whose header carries the tentative column.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 5 ran. Both pass-4 code fixes cleared (header-only status tables fail; duplicate headers fail; status tables with a Recommended replacement column stay out of the announcement parser; rows counted before deduplication). Waivers, expiry, reports, bot reconciliation, schemas, tentative floors, and reserved clocks all cleared again.

One remaining finding, accepted: status-table ownership matches the exact <th> text Tentative retirement date, so a footnote suffix or a <td> header row lets the generic announcement classifier claim the table and emit false exact dates. Fix in progress: shared header normalisation (strip footnote markers, case, whitespace), semantic signature tentative retirement, and <td> header rows accepted when they carry the signature.

Fifth adversarial pass: status-table ownership matched the exact th text
"tentative retirement date", so a footnote marker on the label or a header
row built from td cells let the generic announcement classifier claim the
table and emit false exact dates. Header labels are now normalised (case,
footnote markers, trailing punctuation, whitespace), the signature is a
contains-match on "tentative retirement", and a td-only first row counts
as the header when the table has no th cells at all. Both the status
parser and the generic guard share the detection.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 6 ran. The pass-5 fix cleared on all three requested probes (footnoted Tentative retirement date<sup>1</sup>: label, td-only header row, drifted second table with a renamed column), and the review re-cleared required columns, state values, date parsing, duplicate rows, announcement precedence, waiver matching, expiry, reporting, schemas, bot reconciliation, and reserved clocks.

One medium finding remains, accepted: a leading spanning title row above the real header defeats the first-row ownership check, so the generic classifier can still claim a status table. Fix in progress: scan the whole leading th block for exactly one signature row, return its index to both parsers, and fail closed on two signature rows.

Sixth adversarial pass: a leading spanning title row above the real
header defeated the first-row ownership check, so the generic
announcement classifier could still claim a status table. Detection now
scans every leading all-th row for the status signature, requires exactly
one such row, and starts data parsing after the complete header block.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 7 ran. The pass-6 fix cleared on every probe (leading spanning title row, two signature rows, title plus footnoted label, title plus footnote plus replacement column), and the review re-cleared status validation, announcement precedence, waiver scoping, expiry, bot reconciliation, report propagation, CycloneDX evidence, schema references, pinned compatibility, reserved clocks, and semantic diffs.

One medium finding, accepted: the publisher's minimum-notice policy also extended safe_until on a distributor's tentative clock. Fix: apply the policy floor only on the publisher and publisher-fallback clocks, with a distributor-tentative assertion.

Seventh adversarial pass: the publisher's minimum-notice policy also
extended safe_until on a distributor's tentative clock, claiming runway
that channel never established. The policy floor now applies only on the
publisher and publisher-fallback clocks.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 8 ran (bounded probes only). The pass-7 fix cleared: publisher and fallback floors extend to the policy date, a distributor floor keeps its own date. Waivers, expiry, bot suppression, CycloneDX evidence, schemas, compatibility policy, reserved clocks, header drift, semantic diffs, and feed-refresh PR reuse re-cleared.

Two findings, both accepted:

  1. A status row whose id already has an announcement is dropped without comparison, so a page that lists a model as Active with a floor while also announcing its retirement produces no signal. Fix: Active plus announced throws; Deprecated/Retired rows must match the announcement's dates or throw.
  2. Human output cites publisher policy for a distributor tentative floor, and a tentative entry without shutdown validates and renders not sooner than null. Fix: cite policy only when it extended safe_until; tentative precision requires shutdown in feed validation.

Eighth adversarial pass: human output claimed a publisher-policy
guarantee for every tentative floor, including distributor clocks where
no policy applies, and a tentative entry without a shutdown date passed
validation and rendered "not sooner than null". The policy clause now
appears only when safe_until is later than the floor, and tentative
precision requires a shutdown date at both the model and distribution
level.
Eighth adversarial pass: a status row whose id already had an
announcement entry was dropped without comparison, so a page that lists
a model as Active with a tentative floor while also announcing its
retirement produced no signal. An Active row for an announced model now
throws, and a Deprecated or Retired row must match the announcement's
announced and shutdown dates or throw. Announcement aliases take part in
the match.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 9 ran (bounded probes). Both pass-8 fixes cleared: same-id Active conflicts throw, matching Deprecated/Retired rows deduplicate, date conflicts throw, policy text appears only when it extended safe_until, tentative precision requires shutdown. Waivers, gating, schemas, reserved clocks, diff rendering, and docs re-cleared by inspection.

Three findings:

  1. Accepted, this branch. Anthropic announcement cells with several model tokens keep only the first as id, so an Active status row for the alias bypasses the conflict check and the merge replaces the exact shutdown with a tentative floor. Fix: Anthropic announcements carry the extra tokens as aliases.
  2. Accepted, this branch. A Deprecated row with N/A passes against a dated announcement. Fix: compare shutdown unconditionally for non-Active matches.
  3. Accepted, separate PR. The feed-refresh PR reuse merged in PR 90 force-pushes any open feed-refresh/* branch without a base check, ownership check, review-state check, or lease. Follow-up PR from main: require base main, bot-only committers, no completed review, and --force-with-lease on the fetched remote head.

Ninth adversarial pass: an announcement cell listing several model
tokens kept only the first as id, so an Active status row for the alias
bypassed the conflict check and the merge replaced the exact shutdown
with a tentative floor. Anthropic announcement records now carry the
extra tokens as aliases, matched rows compare shutdown unconditionally so
a Deprecated N/A row against a dated announcement throws, and an
Anthropic announcement row with an N/A shutdown yields an announced-only
record.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed by Claude.
Adversarial review of the reuse path merged in PR 90: it force-pushed any
open feed-refresh/* branch without checking base, ownership, or review
state, so it could erase a maintainer's commits or replace a reviewed
diff. Reuse now requires base main, only "model-eol feed refresh"
committers beyond origin/main, and no completed review decision;
otherwise a new PR is opened and the older one is named in the body. The
push uses --force-with-lease against the fetched remote head.

Implemented by Codex (gpt-5.6-luna) from a Claude brief; reviewed and
tested by Claude.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 10 ran (bounded probes). Both pass-9 fixes cleared: multi-token announcement aliases reach the status conflict check and survive the merge deduplicated; Deprecated N/A against a dated announcement throws; Deprecated N/A against an announced-only announcement deduplicates. Tentative floors, waivers, reports, schemas, and reserved clocks re-cleared.

One finding, accepted: a Retired status row with N/A matched against an announcement that also has no shutdown passes, because the retired-date guard only ran for unmatched rows. Fix: require a parsed shutdown for every Retired row before announcement matching.

Tenth adversarial pass: the retired-date guard ran only for status rows
with no matching announcement, so a Retired row with N/A matched against
an announcement without a shutdown parsed and became a non-actionable
watch record. A Retired row now needs a shutdown date regardless of
announcement matching.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 11 ran (bounded probes). The pass-10 fix cleared: matched, unmatched, and alias-matched Retired N/A rows all throw; valid Deprecated N/A and dated Retired rows still parse. Tentative floors, waivers, expiry, reporting, plan suppression, bot reconciliation, config limits, schemas, reserved clocks, and docs re-cleared.

One medium finding, accepted: a status data row with an empty model cell but populated lifecycle cells is skipped, so a partial DOM failure can drop a model and let the merge erase its committed tentative date. Fix: skip only wholly empty rows; a populated row without a model id throws.

Eleventh adversarial pass: a status data row with an empty model cell but
populated lifecycle cells was skipped, so a partial DOM failure could
drop a model and let the merge erase its committed tentative date. Only
wholly empty spacer rows are skipped now; a populated row without a
model id throws.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 12 ran (bounded probes). The pass-11 fix cleared: a wholly empty spacer row is skipped, a populated row with an empty model cell throws. Waivers, tentative lifecycle, policy floors, merge, validation, reporting, scanner propagation, plans, distributor guards, schemas, fixtures, and docs re-cleared.

One medium finding, accepted: a status-shaped table whose tentative column is renamed to Retirement date but still carries Current state is not recognised as a status table and falls through to the announcement parser. Fix: either signature column (tentative retirement or current state) marks a status table, and the missing required column then throws.

Twelfth adversarial pass: a status-shaped table whose tentative column
was renamed still carried a Current state column, was not recognised as
a status table, and fell through to the announcement parser as false
exact dates. Either column now marks a status table, after which the
missing required column throws.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 13 ran (bounded probes). The pass-12 fix cleared: a renamed tentative column with Current state present throws for the missing column; a real announcement table still parses. Truncated rows, required headers, model ids, known states, Retired shutdowns, aliases, announcement consistency, tentative lifecycle, policy scoping, waivers, CycloneDX, bot reconciliation, feed validation, and schemas all re-cleared.

One medium finding, accepted: a status date cell containing two dates (for example an ISO date followed by a human date) parses as the first. Fix: a status date cell must contain exactly one date candidate.

Thirteenth adversarial pass: a status date cell holding an ISO date and
a human date parsed as the first one because the residual check removed
one of each. A status date cell must now contain exactly one date
candidate.
@thossullivan

Copy link
Copy Markdown
Owner Author

Codex adversarial pass 14: approve, no material findings.

Cleared: date parsing (single dates pass; mixed or duplicate candidates throw); Anthropic fixture ingestion, announcement conflicts, status drift guards, feed validation; tentative lifecycle on publisher and distributor clocks; waiver matching, alias/path/channel scope, UTC expiry, reporting, planning, bot reconciliation; schema alignment, compatibility fixtures, documentation, diff hygiene.

Gate summary for this PR: 14 adversarial passes, 25 findings fixed on this branch, 1 rejected with rationale (pre-waiver dismissal invalidation), 1 policy decision documented in SPEC.md (additive 0.1 line), 1 deferred to PR 92 (feed-refresh reuse lease). Full npm test green at HEAD; read-only cross-repo sweep matches main on every gate count.

@thossullivan

Copy link
Copy Markdown
Owner Author

Feature UAT on real local repositories (read-only, checker at HEAD):

Tentative floors. Built feeds/anthropic.json from today's live Anthropic page through the real parseAnthropicDeprecations + mergeFeed path: 11 tentative entries, 0 validation errors, no retractions. Sweep over local repos with that feed versus the bundled feed:

feed ok retired retiring scheduled
bundled (2026-08-31) 1852 644 42 10
with tentative floors 387 644 42 1475

Retired and retiring are identical; every delta is ok -> scheduled on a reference to an active Claude model, and no tentative finding became retired or retiring. kew's schedule shows claude-sonnet-4-5-20250929 scheduled - tentative, not announced: not sooner than 2026-09-29, guaranteed until 2026-11-02 by anthropic policy and claude-opus-4-6 ... not sooner than 2027-02-05 with no policy clause; earliest risk unchanged.

Waivers. Temporary config waiving claude-sonnet-4-20250514 on kew (111 references): all 111 stay in check output marked waived until 2026-12-31 by @thossullivan; badge drops from 316 to 205 retired; plan records 111 issues with reason waived and no migration items for that model; CycloneDX component carries waiver_active=true plus owner/expires/reason; emitted check and plan documents validate. Bot dry run on a fixture copy: the waived model's create decision disappears, the others remain.

Rewrites the prose in shorter sentences with more paragraph breaks and
removes figurative phrasing in favour of direct statements. Splits the
policy section into ignores-and-waivers, coverage limits, and monorepos,
and adds a jq example for reading a schedule without tentative floors.
Every command, code block, sample output line, link, and the auto-updated
feeds line are unchanged.
@thossullivan

Copy link
Copy Markdown
Owner Author

README: plain-language pass (shorter sentences, direct statements, section split for the policy chapter) and a jq example for filtering tentative floors out of schedule --json. Every command, code block, sample line, link, and the auto-updated feeds line are byte-identical to before; the jq filter was verified on kew's real schedule (324 announced items kept, 0 tentative left). Full suite green.

@thossullivan

Copy link
Copy Markdown
Owner Author

Bundled PR 92 (leased, ownership-checked feed-refresh PR reuse) into this branch; the only overlap was one README sentence, resolved in favour of the plain-language paragraph. Full suite green including the workflow assertions in scripts/test-public-site.mjs.

@thossullivan thossullivan changed the title Tentative retirement floors and owned expiring waivers (#34, #80) feat: tentative retirement floors and owned expiring waivers (#34, #80) Sep 3, 2026
@thossullivan
thossullivan merged commit 7241899 into main Sep 3, 2026
6 checks passed
@thossullivan
thossullivan deleted the feat/tentative-dates-and-waivers branch September 3, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add expiring, owned waivers without hiding suppressed findings Ingest Anthropic tentative retirement dates for active models

1 participant