docs: amend constitution for capability-first plugins

Signed-off-by: barlind <tobias@barlind.se>
This commit is contained in:
barlind 2026-06-03 10:59:45 +02:00 committed by Bret Mogilefsky
parent 4d046b666d
commit e76927524a
No known key found for this signature in database
3 changed files with 78 additions and 20 deletions

View File

@ -1,13 +1,28 @@
<!--
Sync Impact Report
Version change: 1.1.0 -> 1.2.0
Modified principles:
- III. Plugins Are the Extension Point — Isolated by `load_sibling` -> III. Plugins Are the Extension Point — Capability-Declared and Isolated
Added sections: None
Removed sections: None
Templates requiring updates:
- [updated] .specify/templates/plan-template.md — Constitution Check now names Slopsmith's concrete gates, including plugin capability declarations.
- [reviewed] .specify/templates/spec-template.md — no structural update required; requirements remain feature-focused.
- [reviewed] .specify/templates/tasks-template.md — no structural update required; the plan gate drives capability-related tasks.
- [reviewed] .specify/templates/checklist-template.md — no structural update required.
Follow-up TODOs: None
-->
# Slopsmith Constitution
> Slopsmith is a self-hosted, single-user web app for browsing, playing, and
> practicing interactive music notation, built around its own open `.sloppak`
practicing interactive music notation, built around its own open `.sloppak`
> chart format (charts imported from Guitar Pro / MusicXML or authored in the
> built-in editor). This constitution captures the non-negotiable principles
> that govern its core (`server.py`, `lib/`, `static/`) and that all in-tree
> plugins (`plugins/<name>/`) inherit by default. It is a *retrospective*
> document — the codebase came first, the principles below were distilled from
> `CLAUDE.md`, `README.md`, and the shape of the existing implementation.
> `AGENTS.md`, `README.md`, and the shape of the existing implementation.
## Core Principles
@ -69,28 +84,45 @@ features extend `app.js` and the existing globals (`window.playSong`,
layout invariants (`#player` flex-column, `#highway` flex:1,
`#player-controls` at the bottom) MUST be preserved.
### III. Plugins Are the Extension Point — Isolated by `load_sibling`
### III. Plugins Are the Extension Point — Capability-Declared and Isolated
Functionality that is not part of the irreducible "browse + play charts"
loop ships as a plugin under `plugins/<name>/`, not as core code. Each
plugin is its own directory (typically a separate git repo), discovered
at startup via `plugin.json`, and free to add nav links, screens,
settings panels, and `/api/plugins/<id>/...` routes. Plugins MUST
isolate their backend Python imports via `context["load_sibling"]` so
two plugins shipping a generic `extractor.py` / `util.py` / `client.py`
do not collide in `sys.modules`.
settings panels, and `/api/plugins/<id>/...` routes. Slopsmith-facing
plugin behavior is capability-declared by default: manifests describe
the domains a plugin owns, provides, requests, observes, validates, or
contributes to before any runtime script hydrates. Runtime handlers,
legacy wrappers, and private globals are implementation details or
compatibility bridges, never the primary integration contract. Plugins
MUST isolate their backend Python imports via `context["load_sibling"]`
so two plugins shipping a generic `extractor.py` / `util.py` /
`client.py` do not collide in `sys.modules`.
**Non-negotiable rules**
- Generic features (practice journal, setlist, metronome, tone player,
tab view, MIDI control, stem mixing, editors, etc.) belong in a plugin
repo, not in `lib/` or `server.py`.
- Plugins with Slopsmith-facing behavior MUST declare
`standards: ["capability-pipelines.v1"]` plus redaction-safe
`capabilities`, `ui`, or `ui_contributions` metadata for the behavior
they expose. Metadata-only or transitional manifests that omit
capability participation MUST document why no current domain applies.
- Runtime participants, event listeners, wrapper hooks, timers, DOM
roots, diagnostics contributors, and media nodes MUST be idempotent
across repeated script hydration. If a wrapper or private global is
still required, it MUST be documented as a compatibility bridge or a
missing capability-domain gap.
- Plugin backend modules MUST use `context["load_sibling"]("name")` for
sibling imports. Bare `import sibling` works during transition but
triggers a startup warning when a name collides.
- Plugins MUST register routes under `/api/plugins/<plugin_id>/...`,
use `window.slopsmith.emit/on` for cross-plugin communication, and
prefix their `localStorage` keys with their plugin id.
use capability commands/events when an active domain exists, and
prefix their `localStorage` keys with their plugin id. General
`window.slopsmith.emit/on` events remain acceptable for host events or
domains that have not yet been promoted.
- Plugins inherit this constitution and may layer additional rules in
their own `CLAUDE.md`, but MUST NOT relax core principles (e.g. a
plugin cannot require a frontend framework in core).
@ -207,11 +239,12 @@ no `..`, no absolute paths).
`flex:1`; `#player-controls` sits at the bottom. Hiding the highway
collapses the layout — use `margin-top: auto` on controls if you
need to hide it.
- **Plugin load order**: alphabetical by directory name. The
`playSong` wrapper chain runs outermost-first (last-loaded wrapper
runs first). Plugins MUST tolerate dependent globals being absent
at load time and check at runtime
(`typeof window.X === 'function'`).
- **Plugin load order**: alphabetical by directory name. Scripts MUST
tolerate dependent globals being absent at load time and check at
runtime (`typeof window.X === 'function'`). Load order and the
`playSong` wrapper chain are implementation details, not integration
contracts; plugins declare roles, ownership, compatibility, and UI
contributions through capability metadata.
## Development Workflow
@ -225,8 +258,9 @@ no `..`, no absolute paths).
After pushing a fix, the CodeRabbit loop
(`feedback_coderabbit_review.md`) runs to silence.
- **Testing**: `pytest` for backend (`requirements-test.txt`),
Playwright for browser interactions (`tests/browser/`), CI runs both
on every push/PR to `main`.
Playwright for browser interactions (`tests/browser/`), and plugin
manifest schema validation for capability metadata. CI runs these on
every relevant push/PR to `main`.
- **CHANGELOG**: every PR updates `[Unreleased]`. Releases rename
`[Unreleased]` to `[X.Y.Z] - YYYY-MM-DD` (the VERSION bump itself is
automated).
@ -246,12 +280,13 @@ no `..`, no absolute paths).
explicit constitutional amendment in this file.
- Amendments require: (a) a PR that updates this file alongside the
code change, (b) an entry in `CHANGELOG.md` under "Migration notes"
if user-visible, and (c) a corresponding update to `CLAUDE.md` so
AI agents and humans see the same source of truth.
if user-visible, and (c) a corresponding update to `AGENTS.md` and any
agent-specific imports or instructions so AI agents and humans see the
same source of truth.
- The principles are listed in priority order. When two principles
conflict (e.g. "vanilla frontend" vs. a plugin that wants to ship
React), the lower-numbered principle wins by default; the
higher-numbered principle's escape hatch is to live in a plugin
with its own bundled assets.
**Version**: 1.1.0 | **Ratified**: 2026-05-09 | **Last Amended**: 2026-06-01
**Version**: 1.2.0 | **Ratified**: 2026-05-09 | **Last Amended**: 2026-06-03

View File

@ -31,7 +31,29 @@
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
[Gates determined based on constitution file]
Answer each gate with PASS / FAIL / N/A and a short justification:
- **Self-hosted Docker path**: Does the feature keep `DLC_DIR` and
`CONFIG_DIR` as the only required runtime inputs, with any new
dependency installable in the existing Docker image?
- **Vanilla/source-served frontend**: Does core remain plain JS and
committed CSS, with no framework, runtime CDN/JIT, bundler, or
serve-path build step? If plugin CSS is needed, does it ship through
`styles`?
- **Plugin and capability boundary**: If the feature is outside browse +
play CDLC, is it plugin-shaped? Does every Slopsmith-facing plugin
behavior declare `capability-pipelines.v1` metadata (`capabilities`,
`ui`, or `ui_contributions`) and use `load_sibling` for backend
siblings?
- **CDLC compatibility**: Are PSARC scanning, sloppak manifests,
arrangement IDs, and highway WebSocket messages kept backward-
compatible, or is a migration note planned?
- **Pure/testable core**: Are new `lib/` helpers flat-importable,
side-effect-light, and covered where practical?
- **Observability and diagnostics**: Does backend output use logging,
and do diagnostics remain redacted and versioned?
- **Settings/versioning**: Are settings imports/export paths safe,
additive/idempotent, and compatible with the `VERSION` workflow?
## Project Structure

View File

@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`highway.getPhrases()` and `highway.getMastery()` public plugin API** — exposes phrase timing windows (`[{ index, start_time, end_time, max_difficulty }]`) and the current mastery slider value (`0..1`) as documented, stable plugin API. Both values were already in memory and reachable via internal names; this surfaces them with intent so plugins can implement section-aware logic (e.g. tracking accuracy per phrase, suppressing difficulty changes during a hard solo) without reaching into undocumented internals. Returns `null` when the song has no phrase data (GP imports, single-difficulty charts). Pair with the existing `hasPhraseData()` to gate phrase-aware code paths.
- **Tailwind freshness guard + wider plugin scan.** A new `tailwind-fresh` CI job (`.github/workflows/tests.yml`) rebuilds `static/tailwind.min.css` with the pinned `tailwindcss@3.4.19` and hard-fails on any diff, so the committed prebuilt stylesheet can no longer silently lag source (after PR #411 removed the runtime Play CDN, a stale file shipped unstyled elements with no guard). The `tailwind.config.js` plugin content glob is widened to `./plugins/**/*.{js,html}`, which also scans non-`screen.js` plugin JS (e.g. `plugins/app_tour_*/script.js`) that was previously invisible to the build. Regenerating under the wider glob is a no-op for runtime behaviour — it only adds classes that were already used in source. Groundwork for the plugin `styles` capability (constitution 1.1.0, Principle II): runtime-installed plugins ship their own compiled CSS rather than relying on core's build-time scan.
- **Plugin capability pipelines** — adds the first versioned capability coordination layer for plugin authors and support tooling. `/api/plugins` now exposes validated capability declarations, validation warnings, unsupported-version metadata, UI/runtime domain declarations, and compatibility shim summaries for legacy `nav` / `screen` / `settings` / `routes` / visualization surfaces. The browser runtime now tracks manifest participants separately from live handlers, explicit dispatch outcomes (`no-owner`, `no-handler`, `unsupported-command`, `incompatible-version`), claim lifecycle cleanup, manual override precedence, deterministic ownership conflicts, multi-provider ordering, shim hit counts, and a redaction-safe diagnostics snapshot capped at 64 KB. A bundled Capability Inspector plugin shows the live graph, and new docs cover the manifest schema, recipes, safety matrix, lifecycle cleanup, and diagnostics contract.
- **Constitution 1.2.0 codifies capability-first plugins.** Principle III now makes `capability-pipelines.v1` manifest declarations the default Slopsmith-facing plugin contract, treats wrappers/private globals as compatibility bridges or capability gaps, and adds a concrete spec-kit Constitution Check gate for plugin capability metadata.
- **Audio graph/session capability slice** — promotes `audio-mix`, `audio-input`, `audio-monitoring`, and coordinated `stems` diagnostics into the capability runtime. The new audio session host records song route/fader state, redaction-safe input sources, monitoring lifecycle outcomes, stem automation claims/overrides/orphans, and compatibility bridge hits for legacy faders, song volume, Stems master volume, 3D Highway analyser taps, audio startup barriers, and input source handoffs. `core.audio.session` coordinates `stems` without replacing the Stems plugin as the owner of actual stem playback/state.
- **Audio-mix control plane** — makes `audio-mix` the player mixer source of truth. Core now exposes `list-faders`, `get-fader-value`, `set-fader-value`, `inspect-route`, and `inspect-analyser` through the capability runtime, routes native and compatibility-backed fader provider operations with a 2-second timeout, reports committed values back to the mixer UI, suppresses matching legacy faders when a native participant owns the same logical control, and expands audio-session diagnostics/Capability Inspector rendering for fader availability, source modes, bridge hits, route/analyser summaries, and timeout failures.
- **Audio-input control plane** — makes `audio-input` the redaction-safe source of truth for instrument input discovery and lifecycle. Core now exposes `list-sources`, `select-source`, `open-source`, and `close-source` through the capability runtime, persists selected logical sources, keeps inspect/list/select prompt-free, routes provider `source.open`/`source.close` operations with bounded outcomes, shares compatible open sessions across requesters, suppresses compatibility-backed duplicate sources when a native provider owns the same logical key, and expands audio-session diagnostics/Capability Inspector rendering for selected input, open sessions, bridge hits, storage status, and permission/device failures without exposing raw device labels or live audio handles.