Commit Graph
7 Commits
Author SHA1 Message Date
bvandeusenandClaude Fable 5 77bb3729a3 refactor(tests): per-model fakes, FakeMCP and session mocks come from tests/helpers (#2825, milestone 296 area 1, batch 2)
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / Python lint (push) Successful in 4s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Failing after 36s
CI & Build / Build & push image (push) Skipped
Second pass over the tests/ ledger after bbee0d0. fake_record(**attrs) is the
one MagicMock-with-real-attributes builder (to_dict mirrors them; the
note-2109 hazard documented once); fake_note/fake_task/fake_snippet/
fake_project/fake_milestone/fake_system/fake_rulebook/fake_topic/fake_rule
carry each model's ordinary defaults on top of it, replacing 14 per-file
factories (two rulebook trios in tool-vs-service wordings, _fake_task, _fake_ms,
_fake_project, _plan_note, _fake_snippet, two _snippet adapters now one-liners
over fake_snippet). FakeMCP replaces the five closure-over-a-list registrar
fakes (+ _Recorder); loc() and design_token_stub() replace the paired _loc /
_token / _T stand-ins; every hand-built async_session mock (9 helper defs and
14 inline copies) now starts from make_mock_session(). Call sites rewritten by
AST with each file's former defaults made explicit, so behaviour is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 11:13:17 -04:00
bvandeusenandClaude Fable 5 bbee0d0db1 refactor(tests): one definition each for the copied fixtures and fakes (#2825, milestone 296 area 1)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 18s
The shape ledger showed the same test scaffolding defined over and over:
_bind_user x12 (byte-identical), _dispose_engine x10 in three wordings,
_no_supersession x3, _make_mock_session x7 in three subsets, a get-or-create
User helper x2 (+3 inlined), and fifteen hand-rolled MagicMock note factories
each re-explaining the same "an auto-MagicMock attribute is truthy" hazard
(note 2109).

Now: conftest.py carries _bind_user / _dispose_engine / _no_supersession as
opt-in fixtures (pytestmark = usefixtures(...) per module, so unit tests pay
nothing), and tests/helpers.py carries make_mock_session(), ensure_user() and
fake_note(**attrs) — the hazard documented once, real values on every
attribute the product reads. Call sites were rewritten by AST so titles with
dashes and commas survived; the three SimpleNamespace _note stand-ins that
only feed a single function stay local.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 11:03:48 -04:00
bvandeusenandClaude Opus 5 63c213b617 fix(processes): the least-equipped kind is the one that gets followed
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / integration (push) Successful in 18s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 41s
Survey pass 3 (#2250) tabulated capabilities per record kind. Processes came
out lowest on every column, and they are the kind with the most authority:
build_process_manifest turns each one into a skill file on the operator's
machine that auto-surfaces and is followed as written — its own docstring calls
it "the most consequential passive surface Scribe has."

Three gaps closed.

NO PULL TELEMETRY (#2476). get_process recorded nothing, while the auto-inject
menu header names get_process as the way to open that kind. Every note is
embedded regardless of note_type, so a Process is surfaceable — and the getter
the product points at was the one getter that recorded nothing, leaving every
Process permanently at zero pulls and looking like dead weight beside kinds
that merely had a counter.

get_note's own comment already listed processes as a reason to record pulls.
The fix for #2245 covered notes, tasks and snippets: it enumerated the kinds
someone thought of rather than the kinds that exist.

NO DEDUP GATE. create_process had no near-duplicate check and no force flag,
while notes, tasks, snippets and rules all have both. It matters more here than
elsewhere: two near-identical procedures don't just bloat the corpus, they
compete to be followed, and which one wins is decided by a slug collision.

NO DELETE. list/create/get/update, no delete — a kind that reads as one you
cannot retire. Deletion was always possible via delete_note, since a Process is
a note and the trash is kind-agnostic, so this was discoverability rather than
capability. delete_process checks note_type before trashing: the tool is
reached for by name, and letting it destroy an ordinary note whose id happened
to resolve would be a destructive action taken on a mistyped argument.

THE GUARD, which is the part that stops a fourth repeat.

tests/test_mcp_pull_telemetry.py discovers every get_* MCP tool by AST and
requires a record_pulled from any that loads a single note. Not a list of
getters — a get_<newkind> added tomorrow is covered the moment it loads a note
the way the others do. get_milestone is correctly excluded: it calls list_notes
for a milestone's steps, which is a surfacing, not an opening.

The loader NAMES are a list, and that residual weakness is pinned against a
rename rather than papered over. An earlier draft tried to discover new loaders
by return annotation and would have failed on create_note — which also returns
a Note. Readers and writers aren't distinguishable by type, so the honest
version is a pinned list, a non-empty assertion, and a docstring saying which
hole remains.

test_register_attaches_four_tools became a derived check of the module's public
coroutines, so the next tool added can't be left unregistered.

MCP _INSTRUCTIONS updated: product behaviour belongs in the instruction
surfaces, not in a rule (rule #119).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
2026-08-05 16:32:05 -04:00
bvandeusenandClaude Opus 5 4b5d9005fd test(processes): retarget the update_process patch at get_note_for_user
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 12s
CI & Build / integration (push) Successful in 21s
CI & Build / Python tests (push) Successful in 47s
CI & Build / Build & push image (push) Successful in 1m9s
Run 2899: 1 failed, 402 passed. test_update_process_rejects_non_process_note
still patched notes.get_note, which update_process no longer calls now that it
resolves shares — so the real get_note_for_user ran and reached for a database.

Retargeted at get_note_for_user (which returns (note, permission)), and added the
companion case the new behaviour deserves: a viewer grant is refused with the
read-only reason and never reaches update_note.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLwAaV4DQEmVyn496HnEvt
2026-07-26 00:07:08 -04:00
bvandeusenandClaude Opus 5 8b069cc93f fix(acl): make the visibility predicates pure builders; repair CI
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 21s
CI & Build / TypeScript typecheck (push) Successful in 49s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 1m5s
Run 2888 failed with 7 tests down, both causes mine.

The real problem was a design flaw, not the tests: readable_notes_clause and
browsable_notes_clause each opened their own DB session to fetch the caller's
group ids. That made them unmockable at the call site, so every unrelated
service test suddenly had to know they existed and stub them — four modules
broke the moment a service started calling one, and one of my own stubs patched
the wrong name (readable_* where the code had moved to browsable_*).

Fixed at the root: group membership is now a SUBQUERY rather than a fetched
list, so both clauses are synchronous pure functions with no session. One fewer
round-trip per query, membership folded into the statement the caller was
already running, and nothing for callers' tests to mock. The "no groups means no
group arm" special case disappears too — an empty subquery simply matches
nothing.

Also: _fake_note in the process tool tests had no real user_id, so its
auto-MagicMock attribute reached session.get(User, ...) through the new
provenance check and SQLAlchemy rejected it. The fixture now takes a real
user_id defaulting to the bound caller, which makes "is this shared?"
meaningful, and gains a case asserting another user's process comes back
flagged.

Test assertions on compiled SQL are deliberately loose about formatting: the
local env has no SQLAlchemy (rule #10), so they check that the arms exist rather
than guessing at exact rendering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLwAaV4DQEmVyn496HnEvt
2026-07-25 22:48:01 -04:00
bvandeusenandClaude Opus 4.8 b255a0f90e refactor: rename package fabledassistant -> scribe (code-only)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 1m14s
Renames src/fabledassistant -> src/scribe and all imports, plus the
default DB name and DB user/password (fabled -> scribe) in config +
compose. 952 refs / 154 files. Reverses the old 'internal name stays
fabledassistant' convention.

Code-only: live databases are still physically named 'fabledassistant'.
Deployed environments must set POSTGRES_DB / POSTGRES_USER (or rename the
DB) since the defaults now resolve to 'scribe'. Repo (FabledScribe), git
host (fabledsword), MCP (fabled-git) and the image name (fabledscribe)
are intentionally unchanged.

ruff check src/ clean locally; CI (typecheck + pytest) is the gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:48:35 -04:00
bvandeusenandClaude Opus 4.8 7b5a75989a feat(processes): MCP create/list/get/update_process tools
Task 2 of #582. New mcp/tools/processes.py mirrors entities.py — tools wrap
notes_svc directly. get_process is the fire mechanism (returns the full prompt
via resolve_process; surfaces other_matches on an ambiguous name). Registered
in register_all.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:27:41 -04:00