mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
fix(settings): the retirement notice names the successor the table states
For any dropped set without comma-list keys the first-boot notice said "no replacement setting: what they used to configure is fixed behavior in this release". That is false for a retired key the retirement table gives a successor: the flat wall-clock pair was superseded by the activity model, and an owner told there is no replacement stops looking for the setting that took over. RETIRED_SETTING_SUCCESSORS is the decision, recorded next to the retirement it explains, as RETIRED_COMMA_LIST_SETTING_KEYS already is. The notice composes one clause per classification, so a mixed set no longer gets one sentence that is wrong for half of it, and a key the table gives no successor gets the neutral clause instead of either claim. A key the seam MIGRATES stays out of the map: the acceptance pass count is consumed into the shared review-cycle cap before the purge computes the dropped set, so it can never reach this notice. Red-first: both new pins fail against the verbatim pre-fix config.py, which answers the wall-clock pair with the "fixed behavior" sentence and gives the neutral shape no wording of its own. The successor-shape pin derives its document from the table rather than spelling the retired keys, keeping the D04 grep gate tight.
This commit is contained in:
parent
64c02a7a6c
commit
ee0acd2a77
4 changed files with 138 additions and 11 deletions
|
|
@ -34,6 +34,7 @@ from ouroboros.settings_defaults import (
|
|||
PACING_INTERVAL_DEFAULT_SEC, # noqa: F401
|
||||
RETIRED_COMMA_LIST_SETTING_KEYS, # noqa: F401
|
||||
RETIRED_SETTING_KEYS, # noqa: F401
|
||||
RETIRED_SETTING_SUCCESSORS, # noqa: F401
|
||||
SETTINGS_DEFAULTS, # noqa: F401
|
||||
SETTINGS_KEYS_NOT_EXPORTED_TO_ENV, # noqa: F401
|
||||
SUPERVISOR_LIVENESS_DEADLINE_DEFAULT_SEC, # noqa: F401
|
||||
|
|
@ -748,19 +749,22 @@ def normalize_settings_raw(raw: dict) -> dict:
|
|||
loaded.pop(_retired, None)
|
||||
if dropped and dropped not in _RETIREMENT_NOTICE_SEEN:
|
||||
_RETIREMENT_NOTICE_SEEN.add(dropped)
|
||||
comma = [key for key in dropped if key in RETIRED_COMMA_LIST_SETTING_KEYS]
|
||||
comma = [k for k in dropped if k in RETIRED_COMMA_LIST_SETTING_KEYS]
|
||||
clauses = []
|
||||
if comma:
|
||||
replacement = (
|
||||
clauses.append(
|
||||
"the reviewer comma-lists (%s) are replaced by the structured "
|
||||
"OUROBOROS_REVIEWER_SLOTS, so this install now runs the SHIPPED "
|
||||
"default reviewer panel until that setting is authored"
|
||||
% ", ".join(comma)
|
||||
)
|
||||
else:
|
||||
replacement = (
|
||||
"no replacement setting: what they used to configure is fixed "
|
||||
"behavior in this release"
|
||||
)
|
||||
"OUROBOROS_REVIEWER_SLOTS, so this install now runs the SHIPPED default "
|
||||
"reviewer panel until that setting is authored" % ", ".join(comma))
|
||||
if named := [k for k in dropped if k in RETIRED_SETTING_SUCCESSORS]:
|
||||
clauses.append("the retirement table names a successor setting: %s" % "; ".join(
|
||||
"%s -> %s" % (k, ", ".join(RETIRED_SETTING_SUCCESSORS[k])) for k in named))
|
||||
if rest := [k for k in dropped if k not in comma and k not in named]:
|
||||
clauses.append(
|
||||
"the retirement table names no successor setting for %s: they are "
|
||||
"removed, not honored — see the release notes for the surface that "
|
||||
"replaced them" % ", ".join(rest))
|
||||
replacement = "; ".join(clauses)
|
||||
log.warning(
|
||||
"settings: retired key(s) %s are present in the settings document and "
|
||||
"are NOT honored; %s",
|
||||
|
|
|
|||
|
|
@ -364,6 +364,30 @@ RETIRED_COMMA_LIST_SETTING_KEYS: tuple[str, ...] = (
|
|||
)
|
||||
|
||||
|
||||
# The second classification INSIDE RETIRED_SETTING_KEYS: retired keys whose
|
||||
# SUCCESSOR SETTING this retirement table states, so the first-boot notice can
|
||||
# name it instead of telling the owner there is none. Membership is a decision
|
||||
# recorded HERE, next to the retirement it explains — a retired key is absent
|
||||
# from this map when the table names no successor for it (the knob's effect
|
||||
# became fixed behavior, or the replacement is a surface rather than a setting),
|
||||
# and the notice then stays neutral instead of claiming either. The pair below
|
||||
# is stated twice over: by the comment above the keys in the tuple, and by the
|
||||
# ABI-5/D04 rows in docs/ARCHITECTURE.md.
|
||||
#
|
||||
# A key whose value the read seam MIGRATES does not belong here even though its
|
||||
# successor is named: `OUROBOROS_ACCEPTANCE_MAX_IMPROVEMENT_PASSES` is consumed
|
||||
# into `OUROBOROS_REVIEW_MAX_CYCLES` before the purge computes the dropped set,
|
||||
# so it never reaches the notice — there is no loss to report, and an entry for
|
||||
# it would promise a line nothing emits.
|
||||
RETIRED_SETTING_SUCCESSORS: dict[str, tuple[str, ...]] = {
|
||||
# The flat wall-clock pair was superseded by the activity model.
|
||||
"OUROBOROS_SOFT_TIMEOUT_SEC": (
|
||||
"OUROBOROS_TASK_IDLE_TIMEOUT_SEC", "OUROBOROS_TASK_ABS_CEILING_SEC"),
|
||||
"OUROBOROS_HARD_TIMEOUT_SEC": (
|
||||
"OUROBOROS_TASK_IDLE_TIMEOUT_SEC", "OUROBOROS_TASK_ABS_CEILING_SEC"),
|
||||
}
|
||||
|
||||
|
||||
# The same keys from the other side: load_settings overlays env onto disk-ABSENT keys, so without this an
|
||||
# ordinary load->save round-trip in a process whose env says low/off would launder that value onto disk
|
||||
# unauthorised — or, once the guard reads disk, raise a PermissionError nobody authored. Owner endpoints
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ _MOVED_OWNERS = {
|
|||
# ABI 7.0 (ABI-7b): the comma-list classification INSIDE the retirement
|
||||
# SSOT, born in this leaf (not an extraction) for the RC auditor to snap.
|
||||
"RETIRED_COMMA_LIST_SETTING_KEYS": settings_defaults,
|
||||
# The stage-2 close-out's second classification inside that same SSOT: the
|
||||
# retired keys whose successor SETTING the table states, so the first-boot
|
||||
# notice can name it instead of claiming there is none.
|
||||
"RETIRED_SETTING_SUCCESSORS": settings_defaults,
|
||||
"SETTINGS_DEFAULTS": settings_defaults,
|
||||
"SETTINGS_KEYS_NOT_EXPORTED_TO_ENV": settings_defaults,
|
||||
"SUPERVISOR_LIVENESS_DEADLINE_DEFAULT_SEC": settings_defaults,
|
||||
|
|
|
|||
|
|
@ -773,6 +773,101 @@ def test_a_retired_comma_list_triad_is_not_dropped_silently(isolated_settings, c
|
|||
assert "shipped" in notices[0].lower()
|
||||
|
||||
|
||||
def test_the_retirement_notice_names_the_successor_the_table_states(
|
||||
isolated_settings, caplog,
|
||||
):
|
||||
"""The FIRST of the notice's two non-comma shapes. Any dropped set without
|
||||
comma-list keys used to get one fixed sentence: "no replacement setting:
|
||||
what they used to configure is fixed behavior in this release". That is
|
||||
false for a retired key this retirement table gives a successor — the flat
|
||||
wall-clock pair was superseded by the activity model, and the acceptance
|
||||
pass count is migrated into the shared review-cycle cap — so an owner told
|
||||
there is no replacement stops looking for the setting that took over.
|
||||
RETIRED_SETTING_SUCCESSORS is the decision table the notice reads, and a key
|
||||
absent from it gets the neutral clause instead (sibling test), never an
|
||||
invented successor.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from ouroboros import config as cfg
|
||||
from ouroboros.settings_defaults import (
|
||||
RETIRED_SETTING_KEYS,
|
||||
RETIRED_SETTING_SUCCESSORS,
|
||||
)
|
||||
|
||||
# The map classifies INSIDE the retirement tuple: a successor for a key that
|
||||
# is not retired would name a migration nothing performs.
|
||||
assert set(RETIRED_SETTING_SUCCESSORS) <= set(RETIRED_SETTING_KEYS)
|
||||
# And a MIGRATED key is not a reportable loss: the acceptance pass count is
|
||||
# consumed into the shared cap before the purge computes the dropped set, so
|
||||
# an entry for it would promise a notice line nothing can emit.
|
||||
assert "OUROBOROS_ACCEPTANCE_MAX_IMPROVEMENT_PASSES" not in RETIRED_SETTING_SUCCESSORS
|
||||
|
||||
# The document is DERIVED from the table, not spelled out: the D04 grep gate
|
||||
# (tests/test_legacy_timeout_retirement.py) lets the retired wall-clock pair
|
||||
# appear only in the retirement SSOT and its own audits, and this pin is
|
||||
# about the CLASS "a retired key whose successor the table states", not about
|
||||
# one key's spelling.
|
||||
successor_bearing = sorted(RETIRED_SETTING_SUCCESSORS)
|
||||
assert successor_bearing, "the notice's successor shape needs a member"
|
||||
document = dict.fromkeys(successor_bearing, 900)
|
||||
document["OUROBOROS_ACCEPTANCE_MAX_IMPROVEMENT_PASSES"] = 3
|
||||
document["TOTAL_BUDGET"] = 10.0
|
||||
|
||||
cfg._RETIREMENT_NOTICE_SEEN.clear()
|
||||
with caplog.at_level(logging.WARNING, logger="ouroboros.config"):
|
||||
loaded = cfg.normalize_settings_raw(document)
|
||||
|
||||
for key in successor_bearing:
|
||||
assert key not in loaded, key
|
||||
assert loaded["TOTAL_BUDGET"] == 10.0
|
||||
# Migrated, hence unreported: cycles = passes + 1, and the notice is silent.
|
||||
assert loaded["OUROBOROS_REVIEW_MAX_CYCLES"] == "4"
|
||||
notices = [r.getMessage() for r in caplog.records if "retired" in r.getMessage()]
|
||||
assert len(notices) == 1, notices
|
||||
assert "OUROBOROS_ACCEPTANCE_MAX_IMPROVEMENT_PASSES" not in notices[0]
|
||||
for key in successor_bearing:
|
||||
for successor in RETIRED_SETTING_SUCCESSORS[key]:
|
||||
assert successor in notices[0], successor
|
||||
assert "fixed behavior" not in notices[0]
|
||||
|
||||
|
||||
def test_the_retirement_notice_invents_no_successor_when_the_table_states_none(
|
||||
isolated_settings, caplog,
|
||||
):
|
||||
"""The SECOND shape, and the reason the two are separate clauses rather than
|
||||
one sentence about the whole dropped set. The observability retention knob
|
||||
really has no successor setting (manifests and blobs are preserved
|
||||
indefinitely by contract) and the plan-task swarm timeouts have none this
|
||||
table states per key, so the notice says they are gone and points at the
|
||||
release notes — without promising a replacement key, and without the older
|
||||
claim that their effect is now fixed behavior, which for the swarm timeouts
|
||||
was never established.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from ouroboros import config as cfg
|
||||
from ouroboros.settings_defaults import RETIRED_SETTING_SUCCESSORS
|
||||
|
||||
cfg._RETIREMENT_NOTICE_SEEN.clear()
|
||||
with caplog.at_level(logging.WARNING, logger="ouroboros.config"):
|
||||
cfg.normalize_settings_raw({
|
||||
"OUROBOROS_OBSERVABILITY_RETENTION_DAYS": 30,
|
||||
"OUROBOROS_PLAN_TASK_SWARM_TIMEOUT_SEC": 60,
|
||||
})
|
||||
|
||||
notices = [r.getMessage() for r in caplog.records if "retired" in r.getMessage()]
|
||||
assert len(notices) == 1, notices
|
||||
assert "OUROBOROS_OBSERVABILITY_RETENTION_DAYS" in notices[0]
|
||||
assert "OUROBOROS_PLAN_TASK_SWARM_TIMEOUT_SEC" in notices[0]
|
||||
assert "removed, not honored" in notices[0]
|
||||
assert "release notes" in notices[0]
|
||||
assert "fixed behavior" not in notices[0]
|
||||
for successors in RETIRED_SETTING_SUCCESSORS.values():
|
||||
for successor in successors:
|
||||
assert successor not in notices[0], successor
|
||||
|
||||
|
||||
def test_the_retirement_notice_stays_quiet_for_a_document_without_ghosts(
|
||||
isolated_settings, caplog,
|
||||
):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue