Files
FabledScribe/tests/test_routes_family.py
T
bvandeusenandClaude Opus 5.5 8aacc1824c
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / integration (push) Failing after 1m5s
CI & Build / Python tests (push) Successful in 1m58s
CI & Build / Build & push image (push) Skipped
feat(family): the promotion engine - triggers, the three criteria, the decision log and undo (milestone 463 step 3, #4989)
The agent promotes a family idea when all three criteria hold, and no person approves it. The criteria are product text: services/family.py states them, and the promote tool's docstring names every criterion the service enforces.

- Criteria: each one vetoes on its own when its reasoning is blank. Platform terms also needs an applies-when and a platform scope; proven also needs named evidence. A veto keeps the idea a candidate and is logged, so it becomes precedent.
- Precedent: every promotion stores the decisions on the nearest ideas by meaning, plus any the caller names.
- Promotion sets canon, the applicability test and the platform scope, and opens an unassessed ledger row for each member project the promoter can write. Re-promotion moves the version past every version the idea has held.
- Retire and undo: undo reverses only the latest idea-level decision, restores its recorded before-state, and logs itself with the undone decision as its precedent. Settled here: leaving canon closes the unassessed rows but keeps the judged ones, which read as needing a recheck after a re-promotion.
- Triggers open evaluations but never promote:
  - a cross-project lineage citation ("matching #N") on a note or task write;
  - a same-meaning record in another project on a shared platform, on create, at 0.80 (measured: the known pattern's builds scored 0.79-0.82, an unrelated project's best match 0.65);
  - a milestone closing on a platform.
  Each fails open and rides the response as family_hint.
- Doors: seven MCP tools, /api/family REST endpoints, and a Family page (nav, /family) showing the criteria, the ideas, and the decision log with undo and retire.
- utils/recordHref.ts holds the one copy of "where a record opens", now shared with LessonDetailView.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:33:30 -04:00

30 lines
1.3 KiB
Python

"""Structural tests for the family blueprint (milestone 463 step 3) — every
endpoint is routed, and every write hands the service its caller, where the
note's write gate lives. What the writes do is in
tests/test_integration_family_promotion.py."""
import inspect
def test_family_blueprint_routes_every_endpoint():
from scribe.app import create_app
app = create_app()
assert "family" in app.blueprints
rules = {(r.rule, m) for r in app.url_map.iter_rules() for m in r.methods}
for rule, method in (
("/api/family/ideas", "GET"),
("/api/family/ideas/<int:note_id>", "GET"),
("/api/family/ideas/<int:note_id>/propose", "POST"),
("/api/family/ideas/<int:note_id>/promote", "POST"),
("/api/family/ideas/<int:note_id>/retire", "POST"),
("/api/family/decisions", "GET"),
("/api/family/decisions/<int:decision_id>/undo", "POST"),
):
assert (rule, method) in rules, f"{method} {rule} is not routed"
def test_every_engine_write_takes_the_caller_and_who_decided():
from scribe.services import family as svc
for name in ("propose", "promote", "retire", "undo"):
params = inspect.signature(getattr(svc, name)).parameters
assert "user_id" in params and "decided_via" in params, name