Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8e0c662
docs(audio): define provider-neutral music planning layer
zedarvates Sep 13, 2026
ef37f79
feat(audio): add MusicPlan v1 interchange schema
zedarvates Sep 13, 2026
c026a4e
feat(audio): add model-free MusicPlan invariant validator
zedarvates Sep 13, 2026
da11709
test(audio): add full MusicPlan fixture
zedarvates Sep 13, 2026
f7dbfc0
test(audio): add guided MusicPlan fixture
zedarvates Sep 13, 2026
f9754b7
test(audio): add free MusicPlan fixture
zedarvates Sep 13, 2026
d492369
test(audio): validate MusicPlan invariants and licence boundary
zedarvates Sep 13, 2026
e9aa207
ci(audio): add isolated MusicPlan contract proof
zedarvates Sep 13, 2026
18a54be
feat(audio): add deterministic mock music provider contract
zedarvates Sep 13, 2026
8d8b86b
test(audio): prove explicit provider degradation contract
zedarvates Sep 13, 2026
ce7d71b
ci(audio): cover mock provider contract
zedarvates Sep 13, 2026
1151149
docs(audio): record mock provider proof boundary
zedarvates Sep 13, 2026
6c37dc3
ci(audio): include MusicPlan contract documentation in proof scope
zedarvates Sep 13, 2026
115ee32
fix(audio): compose JSON Schema validation and recursive execution de…
zedarvates Sep 13, 2026
1e41f9b
test(audio): cover schema and nested execution degradation
zedarvates Sep 13, 2026
d3b9bfe
ci(audio): validate Draft 2020-12 MusicPlan contract
zedarvates Sep 13, 2026
197f36b
feat(audio): add portable provider handoff envelope
zedarvates Sep 13, 2026
5cbaad5
test(audio): prove portable handoff stays non-activating
zedarvates Sep 13, 2026
079e063
test(audio): add deterministic MusicPlan mutation probe
zedarvates Sep 13, 2026
ea6867e
ci(audio): run controlled MusicPlan negative mutation probe
zedarvates Sep 13, 2026
401abcc
docs(audio): align MusicPlan proof and handoff status
zedarvates Sep 13, 2026
189d3d1
fix(ci): run MusicPlan mutation probe as a module
zedarvates Sep 13, 2026
d7af6f5
test(audio): add private holdout evaluator protocol
zedarvates Sep 13, 2026
385a2ea
ci(audio): verify private holdout evaluator protocol
zedarvates Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/music-plan-contract.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: MusicPlan Contract

on:
pull_request:
branches: [main]
paths:
- "schemas/music-plan-v1.schema.json"
- "src/music_plan.py"
- "src/music_provider.py"
- "scripts/music_plan_mutation_probe.py"
- "scripts/music_plan_holdout_evaluator.py"
- "tests/test_music_plan.py"
- "tests/fixtures/music_plan/**"
- "docs/music-planning-layer-v1.md"
- ".github/workflows/music-plan-contract.yml"

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install focused validation dependencies
run: python -m pip install 'pytest>=8,<10' 'jsonschema>=4.23,<5'
- name: Compile model-free contracts
run: python -m py_compile src/music_plan.py src/music_provider.py scripts/music_plan_mutation_probe.py scripts/music_plan_holdout_evaluator.py
- name: Run MusicPlan contract tests
run: python -m pytest -q tests/test_music_plan.py
- name: Run controlled negative mutation probe
run: python -m scripts.music_plan_mutation_probe
- name: Verify private holdout evaluator protocol
run: |
python - <<'PY'
import json
import subprocess
import sys
from pathlib import Path

plan = json.loads(Path('tests/fixtures/music_plan/full.json').read_text(encoding='utf-8'))
request = {"id": "public-protocol-smoke", "input": {"plan": plan, "expected_valid": True}}
r = subprocess.run(
[sys.executable, '-m', 'scripts.music_plan_holdout_evaluator'],
input=json.dumps(request), text=True, capture_output=True, check=True,
)
response = json.loads(r.stdout)
assert response == {"passed": True}, response
print('holdout evaluator protocol OK')
PY
- name: Verify Draft 2020-12 schema and reference fixtures
run: |
python - <<'PY'
import json
from pathlib import Path
from jsonschema import Draft202012Validator

schema_path = Path('schemas/music-plan-v1.schema.json')
schema = json.loads(schema_path.read_text(encoding='utf-8'))
Draft202012Validator.check_schema(schema)
validator = Draft202012Validator(schema)
for path in sorted(Path('tests/fixtures/music_plan').glob('*.json')):
data = json.loads(path.read_text(encoding='utf-8'))
validator.validate(data)
print(f'OK {path}')
PY
150 changes: 150 additions & 0 deletions docs/music-planning-layer-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# StoryCore Music Planning Layer v1

Status: **experimental contract; provider-neutral; no provider activated**.

## Goal

Insert an editable symbolic planning layer between narrative intent and any music
generator. StoryCore should be able to inspect, compare, revise and version a
score plan without coupling the project to one model or one licence regime.

```text
story / scene / shot intent
|
v
MusicPlan v1
|
v
Draft 2020-12 schema + deterministic invariants
|
v
provider capability / licence check
|
v
requested vs executed delta
|
v
candidate artifact + portable handoff
|
v
independent validation later
|
v
last-known-good promotion outside provider adapter
```

The design is inspired by open-source workflows that expose an editable
intermediate representation, but this contract is StoryCore-owned and does not
require or redistribute third-party model weights.

## Three execution modes

- `full`: strongest symbolic plan; use where continuity, motifs, timing or
reproducibility matter.
- `guided`: StoryCore fixes high-level structure and synchronization while the
provider may elaborate details.
- `free`: narrative/time brief remains binding, but detailed symbolic planning
is optional. It must not be represented as equivalent to a `full` render.

## Validation before inference

The model-free gate composes the repository's existing `jsonschema` dependency
with StoryCore-specific checks. It:

- validates the Draft 2020-12 schema itself;
- validates required fields and bounded values in a requested plan;
- rejects invalid section timing and overlaps;
- checks unique IDs and motif references;
- checks cue bounds;
- rejects declared non-commercial model weights on commercial targets;
- treats known unknown/unqualified model-weight licence markers as ineligible
for commercial use.

The reference fixtures cover `full`, `guided`, and `free`. No LLM, music model,
network request or weight download is needed for this gate.

## Explicit degradation

`execution_delta(requested, executed)` is recursive. It records mode changes,
removed or added fields, changed scalar values, list-length changes and nested
changes. Examples include:

```text
mode:full->guided
dropped:motifs
changed:duration_seconds
changed:sync_cues.length
changed:sync_cues[0].time_seconds
```

A provider must never silently change requested state.

`MockMusicProvider` is deterministic and performs no inference. A fully capable
mock keeps the requested state unchanged. A limited mock may use an explicit
fallback and list unsupported fields, but any material delta requires a
non-empty degradation reason or the preparation fails closed.

## Portable provider handoff

`build_provider_handoff()` creates a data-only interchange envelope containing:

- plan ID;
- provider/version/model;
- adapter/harness and hardware identity;
- requested/executed modes;
- unsupported fields, deltas, reason and acknowledgement;
- candidate artifact identity and optional digests;
- verification state and evidence references.

It does not import Botte Secrète, execute a provider or write memory. Provider
results remain candidates. The envelope always carries
`activation_allowed=false`, `promoted=false` and
`memory_write_performed=false`. A `verified` handoff requires evidence.

## Commercial boundary

Code, model weights, datasets and assets retain separate provenance/licences.
Permissive adapter code cannot make restricted model weights commercially
usable. Non-commercial or unknown/unqualified weights remain outside the
StoryCore/Obolune commercial path until independently qualified.

## Benchmark identity

Future measurements must retain the complete path:

`story fixture x model/provider x adapter/harness x hardware x parameters`

A model-only score is insufficient because the harness can materially change
plan preservation, failures, quality, latency and resource use.

## Current proof boundary

The isolated `MusicPlan Contract` workflow runs on Python 3.10 and 3.12. It
installs only focused test/schema dependencies, compiles the MusicPlan/provider
contracts, executes the focused regression suite, checks the Draft 2020-12
schema, and validates all three reference fixtures against it.

Exact-head CI remains the authority for PR claims. This contract does **not**
prove real audio quality, a production provider, output rights beyond the
explicit licence policy, hardware performance, end-to-end StoryCore integration,
or last-known-good audio recovery.

## Relationship to Botte Secrète

StoryCore remains standalone. A future Botte integration should consume the
portable handoff as data rather than importing Botte as a hard runtime
dependency. Conceptually:

- MusicPlan + narrative refs -> context snapshot;
- provider capabilities/licence/hardware -> constraints;
- provider handoff -> execution delta + candidate artifact;
- independent validators -> evidence;
- accepted soundtrack -> recovery point;
- measured provider runs -> Capability Atlas observations.

## Next bounded slice

Before any real provider is connected, add deterministic artifact-digest and
independent-verification fixtures around the portable handoff. Real local or
external music inference waits for explicit licence/hardware qualification and
must remain non-promoting by default.
121 changes: 121 additions & 0 deletions schemas/music-plan-v1.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://storycore.local/schemas/music-plan-v1.schema.json",
"title": "StoryCore MusicPlan v1",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"plan_id",
"mode",
"duration_seconds",
"sections",
"sync_cues",
"provenance"
],
"properties": {
"schema_version": {"const": 1},
"plan_id": {"type": "string", "minLength": 1, "maxLength": 128},
"mode": {"enum": ["full", "guided", "free"]},
"title": {"type": "string", "maxLength": 256},
"narrative_intent": {"type": "string", "maxLength": 4000},
"duration_seconds": {"type": "number", "exclusiveMinimum": 0, "maximum": 7200},
"tempo_bpm": {"type": ["number", "null"], "minimum": 20, "maximum": 300},
"meter": {"type": ["string", "null"], "maxLength": 32},
"tonal_center": {"type": ["string", "null"], "maxLength": 64},
"sections": {
"type": "array",
"minItems": 1,
"maxItems": 128,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "start_seconds", "end_seconds", "purpose"],
"properties": {
"id": {"type": "string", "minLength": 1, "maxLength": 128},
"start_seconds": {"type": "number", "minimum": 0},
"end_seconds": {"type": "number", "exclusiveMinimum": 0},
"purpose": {"type": "string", "minLength": 1, "maxLength": 1000},
"energy": {"type": ["number", "null"], "minimum": 0, "maximum": 1},
"tension": {"type": ["number", "null"], "minimum": 0, "maximum": 1},
"dialogue_safe": {"type": "boolean", "default": false},
"motif_refs": {
"type": "array",
"items": {"type": "string", "minLength": 1, "maxLength": 128},
"uniqueItems": true,
"default": []
},
"instrument_roles": {
"type": "array",
"items": {"type": "string", "minLength": 1, "maxLength": 128},
"uniqueItems": true,
"default": []
}
}
}
},
"motifs": {
"type": "array",
"maxItems": 64,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "intent"],
"properties": {
"id": {"type": "string", "minLength": 1, "maxLength": 128},
"intent": {"type": "string", "minLength": 1, "maxLength": 1000},
"symbolic_hint": {"type": ["string", "null"], "maxLength": 2000}
}
},
"default": []
},
"sync_cues": {
"type": "array",
"maxItems": 256,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "time_seconds", "intent"],
"properties": {
"id": {"type": "string", "minLength": 1, "maxLength": 128},
"time_seconds": {"type": "number", "minimum": 0},
"intent": {"type": "string", "minLength": 1, "maxLength": 1000},
"story_ref": {"type": ["string", "null"], "maxLength": 512}
}
}
},
"lyrics": {
"type": ["object", "null"],
"additionalProperties": false,
"required": ["language", "text"],
"properties": {
"language": {"type": "string", "minLength": 2, "maxLength": 35},
"text": {"type": "string", "maxLength": 20000}
}
},
"provenance": {
"type": "object",
"additionalProperties": false,
"required": ["commercial_target", "dependencies"],
"properties": {
"commercial_target": {"type": "boolean"},
"dependencies": {
"type": "array",
"maxItems": 128,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["name", "kind", "license"],
"properties": {
"name": {"type": "string", "minLength": 1, "maxLength": 256},
"kind": {"enum": ["code", "model-weights", "dataset", "asset", "other"]},
"license": {"type": "string", "minLength": 1, "maxLength": 256},
"version": {"type": ["string", "null"], "maxLength": 128},
"source": {"type": ["string", "null"], "maxLength": 1000}
}
}
}
}
}
}
}
Loading
Loading