revert(design-systems): drop the rulebook import — a migration, not a feature
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 44s

Operator's call, and it corrects a scope error rather than a bug:

  "this is a path for a user to go from a rulebook to a design system. we don't
   need to build this path in the app itself ... you should be the one that does
   the import ... going forward no one else should have to do such a migration."

Right. Nobody starting from a design system will ever go rulebook -> system, so
the whole path was permanent product code serving a single act on one install.
Rule #22: remove it, don't flag it off. Gone from the service, the REST route,
the MCP tool, the UI panel, the API client and its tests.

There is a second consequence I had missed, and it is the better argument. The
parser was WORSE at this than doing it by hand. `propose_tokens` leaves radius
steps and type sizes valueless because "Small 4px" is not a hex and nothing here
parses it — a limitation I documented carefully and shipped anyway. But that
limitation only exists because the importer had to run unattended. Done as work
rather than as a feature, those values are just read and written, and the result
is a complete design system instead of one with a dozen blanks and a count
explaining them.

Scaffolding built around my own absence from the loop, when I am the loop.

KEPT: `extract_expectations` and `design_expectations` in
services/design_rulebook_import.py. The live drift panel still reads them until
it is repointed at a resolved design system (#2295), and removing them now would
take the /design page's only content with it. They go with that change, not this
one.
This commit is contained in:
2026-07-31 09:46:12 -04:00
parent 8eef9e7845
commit 23a385e2db
9 changed files with 5 additions and 749 deletions
-99
View File
@@ -276,105 +276,6 @@ async def test_create_token_records_the_literals_it_replaces():
assert captured["supersedes"] == ["#fff", "#ffffff"]
# --- import from a rulebook (step 3) ----------------------------------------
def _proposal(name, value=None):
from scribe.services.design_rulebook_import import ProposedToken
return ProposedToken(name=name, value_by_mode={"base": value} if value else {})
@pytest.mark.asyncio
async def test_import_denied_without_write_on_the_system():
with patch("scribe.services.design_systems.access") as acc:
acc.can_write_design_system = AsyncMock(return_value=False)
from scribe.services.design_systems import import_from_rulebook
assert await import_from_rulebook(1, 3, 9) is None
@pytest.mark.asyncio
async def test_preview_proposes_without_creating_anything():
"""apply=False is the default because an import is a PROPOSAL. A preview
that quietly wrote would make the review step decorative."""
created = AsyncMock()
with patch("scribe.services.design_systems.access") as acc, \
patch("scribe.services.design_systems.create_token", created), \
patch("scribe.services.design_systems.list_tokens", AsyncMock(return_value=[])), \
patch("scribe.services.rulebooks.list_rules", AsyncMock(return_value=[object()])), \
patch("scribe.services.design_rulebook_import.propose_tokens",
MagicMock(return_value=[_proposal("--fs-obsidian", "#14171a")])):
acc.can_write_design_system = AsyncMock(return_value=True)
from scribe.services.design_systems import import_from_rulebook
report = await import_from_rulebook(1, 3, 9, apply=False)
assert [p["name"] for p in report["proposed"]] == ["--fs-obsidian"]
assert report["created"] == []
created.assert_not_awaited()
@pytest.mark.asyncio
async def test_import_never_overwrites_a_token_the_system_already_defines():
"""LOAD-BEARING for re-running it. A value already in the record was put
there deliberately — very likely correcting this importer — and a second run
must fill gaps rather than undo the correction."""
existing = MagicMock()
existing.name = "--fs-obsidian"
created_token = MagicMock()
created_token.to_dict.return_value = {"name": "--fs-iron"}
with patch("scribe.services.design_systems.access") as acc, \
patch("scribe.services.design_systems.create_token",
AsyncMock(return_value=created_token)) as create, \
patch("scribe.services.design_systems.list_tokens",
AsyncMock(return_value=[existing])), \
patch("scribe.services.rulebooks.list_rules", AsyncMock(return_value=[object()])), \
patch("scribe.services.design_rulebook_import.propose_tokens",
MagicMock(return_value=[
_proposal("--fs-obsidian", "#000000"),
_proposal("--fs-iron", "#1e2228"),
])):
acc.can_write_design_system = AsyncMock(return_value=True)
from scribe.services.design_systems import import_from_rulebook
report = await import_from_rulebook(1, 3, 9, apply=True)
assert report["skipped"] == ["--fs-obsidian"]
assert [c["name"] for c in report["created"]] == ["--fs-iron"]
assert create.await_count == 1
@pytest.mark.asyncio
async def test_a_valueless_proposal_is_still_created():
"""The rulebook names it, so its absence from the system is itself a
finding. A named token with a blank value says "this exists and needs
deciding"; silence says nothing at all."""
token = MagicMock()
token.to_dict.return_value = {"name": "--fs-radius-sm"}
with patch("scribe.services.design_systems.access") as acc, \
patch("scribe.services.design_systems.create_token",
AsyncMock(return_value=token)) as create, \
patch("scribe.services.design_systems.list_tokens", AsyncMock(return_value=[])), \
patch("scribe.services.rulebooks.list_rules", AsyncMock(return_value=[object()])), \
patch("scribe.services.design_rulebook_import.propose_tokens",
MagicMock(return_value=[_proposal("--fs-radius-sm")])):
acc.can_write_design_system = AsyncMock(return_value=True)
from scribe.services.design_systems import import_from_rulebook
report = await import_from_rulebook(1, 3, 9, apply=True)
assert len(report["created"]) == 1
assert create.await_args.kwargs["value_by_mode"] == {}
@pytest.mark.asyncio
async def test_an_unreadable_or_empty_rulebook_reports_nothing_rather_than_failing():
"""An install can point this at any rulebook, and most rulebooks are not
design rulebooks (rule #115)."""
with patch("scribe.services.design_systems.access") as acc, \
patch("scribe.services.rulebooks.list_rules", AsyncMock(return_value=[])):
acc.can_write_design_system = AsyncMock(return_value=True)
from scribe.services.design_systems import import_from_rulebook
report = await import_from_rulebook(1, 3, 9, apply=True)
assert report == {"rulebook_id": 9, "proposed": [], "created": [], "skipped": []}
# --- the master sheet -------------------------------------------------------
@pytest.mark.asyncio