diff --git a/src/scribe/services/family.py b/src/scribe/services/family.py index b0fb7a0c..c48ec7bc 100644 --- a/src/scribe/services/family.py +++ b/src/scribe/services/family.py @@ -1,4 +1,4 @@ -"""Family canon's promotion engine (milestone 463 step 3). +"""Family canon's promotion engine. A family idea moves between three states — candidate, canon, retired — and every move is a decision with a reason, written to `family_decisions`. No @@ -62,7 +62,7 @@ logger = logging.getLogger(__name__) # # These are what an evaluation is judged against, on every install. They live # here — and in the tool docstrings that quote them — rather than in any -# instance's rulebook (rules 115, 119). +# instance's rulebook, so every install judges by the same text. CRITERIA = ( { @@ -97,13 +97,11 @@ CRITERIA_KEYS = tuple(c["key"] for c in CRITERIA) TRIGGERS = ("citation", "repeat", "milestone") -# The repeat trigger's similarity floor. MEASURED, not guessed (2026-10-06, -# #4989): against a description of one pattern known to have been built four -# times in four projects, the records that implement it scored 0.79-0.82, and -# the best match in a project that never built it scored 0.65. A trigger only -# opens an evaluation, so a false positive costs one judgment, not a wrong -# promotion. Step 5 measures the cross-language threshold against known pairs -# and supersedes this. +# The repeat trigger's similarity floor. MEASURED, not guessed: against a +# description of one pattern known to have been built four times in four +# projects, the records that implement it scored 0.79-0.82, and the best match +# in a project that never built it scored 0.65. A trigger only opens an +# evaluation, so a false positive costs one judgment, not a wrong promotion. REPEAT_THRESHOLD = 0.80 # Words that say a citation is the SOURCE of a pattern rather than background. @@ -119,12 +117,11 @@ _LINEAGE = re.compile( _LINEAGE_WINDOW = 80 # Note kinds the repeat trigger compares: recorded knowledge and shapes, not -# the to-do list — and each only against its own kind (#5130). A repeat is the -# same KIND of thing made twice: two shapes built, or two ideas written down. +# the to-do list — and each only against its own kind. A repeat is the same +# KIND of thing made twice: two shapes built, or two ideas written down. # Across kinds the score stops meaning that. One chunk of a long narrative -# note that mentions a shape's vocabulary scores like the shape itself: a -# dated release log matched a signed-APK CI snippet at 0.83, above this -# trigger's floor, and opened the log as a candidate. The cross-kind relation +# note that mentions a shape's vocabulary scores like the shape itself — above +# this trigger's floor — and would open the note as a candidate. The cross-kind relation # that IS real — code built from an idea written elsewhere — has its own # routes: the citation trigger when the lineage is named, and the shape ledger # once the idea is canon. @@ -172,7 +169,7 @@ async def _topic(session, user_id: int, topic_id: int | None, *, rules: bool) -> async def _snapshot(session, idea: FamilyIdea | None) -> dict | None: """The idea's state, as a decision's before/after. Slugs, never ids: an id - inside JSON cannot be remapped by a restore (the #3182 trap).""" + inside JSON cannot be remapped by a restore.""" if idea is None: return None return { @@ -219,7 +216,7 @@ async def get_idea(user_id: int, note_id: int) -> dict | None: "platforms": await _platform_slugs(session, note_id), "topic": await _topic(session, user_id, idea.topic_id, rules=True), "adoptions": counts, - # Brief: the history is read in bulk; the full rows are list_decisions (#5131). + # Brief: the history is read in bulk; the full rows are list_decisions. "decisions": [d.to_brief() for d in decisions], "undoable_decision_id": _undoable_id(decisions), }) @@ -478,7 +475,7 @@ async def propose( Idempotent: an existing idea comes back unchanged and nothing is logged — a trigger firing on every edit of a record must not grow the log. Write- - gated on the note: an idea is state on someone's record (rule 78). + gated on the note: an idea is state on someone's record. """ if not (reason or "").strip(): raise ValueError("a proposal needs a reason — what makes this a family idea?") diff --git a/src/scribe/services/inception.py b/src/scribe/services/inception.py index 4e9d48bd..adeb52f7 100644 --- a/src/scribe/services/inception.py +++ b/src/scribe/services/inception.py @@ -1,4 +1,4 @@ -"""Project inception — what a project was decided to inherit (milestone 297). +"""Project inception — what a project was decided to inherit. A project's inheritance is a decision, not a default. The record lives on ``projects.inception``:: @@ -14,14 +14,13 @@ A project's inheritance is a decision, not a default. The record lives on } NULL = undecided → enter_project asks. ``legacy`` is the migration's stamp on -projects that existed before the step did (inherit-all / no design system / -no seed), so the ask fires only for projects created after this shipped. +projects that predate inception records (inherit-all / no design system / +no seed), so the ask fires only for projects created since. -Rules are not a choice any more. ``exclude_always_on_rulebooks`` went with the -always-on tier (milestone 394), and ``subscribe_rulebooks`` went with -subscriptions (milestone 414): a rule in a rulebook is global and applies to -every project, and a project's own rules are written on it directly. Migration -0101 strips both keys from stored records. +Rules are not a choice: a rule in a rulebook is global and applies to every +project, and a project's own rules are written on it directly. Migration 0101 +strips the retired ``exclude_always_on_rulebooks`` and ``subscribe_rulebooks`` +keys from stored records. The shape and its validator are pure; ``decide`` composes the existing services — set_project_design_system and the standard Systems seed — checks @@ -31,7 +30,7 @@ half-applied decision is re-runnable rather than recorded as done. ``current_defaults`` is what the enter_project ask shows: what binds today if nobody decides. -``platforms`` (milestone 463) is which platforms the project IS — the answer +``platforms`` is which platforms the project IS — the answer that decides which family ideas reach it. A list of catalog SLUGS, not ids: the record is JSON, and a slug survives a backup restore onto an install whose ids differ, where an id inside JSON would come back naming another platform. @@ -54,7 +53,7 @@ CHOICE_KEYS = ("design_system_id", "seed_systems", "platforms") def validate_inception(choices) -> str | None: """The structural error an inception ``choices`` object would earn, or None. Pure and checked BEFORE any effect is applied: a decision either - applies whole or errors whole (the StrictArgs lesson, #2709). + applies whole or errors whole. Accepts three keys, each optional: ``design_system_id`` an int or None, ``seed_systems`` a bool, ``platforms`` a list of slugs or None. Unknown @@ -152,7 +151,7 @@ async def decide( choices: dict | None, via: str, ) -> dict: - """Record a project's inception decision and apply it (milestone 297). + """Record a project's inception decision and apply it. Owner-only. Validates the choices (pure) and every target (readable) first; then, each idempotent: point the project at the design system @@ -243,8 +242,8 @@ def _platform_line(defaults: dict) -> str: async def inception_ask(user_id: int, project_id: int) -> dict: - """The enter_project ask for an undecided project (milestone 297) — the - sibling of the systems-bootstrap ask (#2683): the project's OWN current + """The enter_project ask for an undecided project — the sibling of the + systems-bootstrap ask: the project's OWN current defaults, what to ask the operator, and the exact call that answers it. Fail-open: a hint must never break the call it rides on.""" try: