mirror of
https://github.com/mvschwarz/openrig-studio.git
synced 2026-11-07 05:56:21 -10:00
Carry the signals declaration, and warn when it names a verb nobody answers
A provider now declares which of its verbs honour ?since= and whether it mints a process identity. It is a declaration rather than something derived for the reason everything else in this format is: a verb answering ?since= is a property of the implementation, not of the route, so nothing can work it out by looking. Carried explicitly by the composer alongside seeds and supplies, so adding a field to the contract without carrying it fails the field-by-field test rather than going quiet one layer down — which is how seeds and supplies were being dropped before anyone noticed. The warning is the part that matters. A signals.verbs entry naming a verb this provider does not declare would have a consumer polling ?since= against a route the backend never answers: a declaration that quietly does nothing, which is the class this seam keeps removing and which arrives each time through a new field. Warned rather than refused, because the provider still works and the rest of the studio should not be held up by it. Prefix verbs satisfy a signals entry for their children, using the trailing-slash rule byte routes and calls already use, so a correctly declared prefix is not warned at — a false alarm is how a warning becomes something people learn to ignore. Both halves verified by planting: dropping the carry trips the field-by-field test, and disabling the warning branch trips the warning test. The first attempt at the second plant changed the warning's WORDING rather than its branch, so it applied, produced no defect, and reported the control as passing — the defect was measured directly before the suite result was believed. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
2b444e954a
commit
911eb2def8
4 files changed
+85
-1
No files matched your search
@@ -54,6 +54,7 @@ about itself; an app may not override any of this.
|
||||
},
|
||||
"serves": ["/media/", "/cutprev/", "/cuts/"],
|
||||
"verbs": ["/api/clips", "/api/cuts", "/api/export-status/"],
|
||||
"signals": { "verbs": ["/api/clips"], "boot": true },
|
||||
"supplies": ["ffmpeg", "ffprobe"]
|
||||
}
|
||||
```
|
||||
@@ -67,9 +68,30 @@ about itself; an app may not override any of this.
|
||||
| `run.companions[]` | stable | long-running processes started **with** the provider |
|
||||
| `serves[]` | stable | **byte-route prefixes**, matched by prefix |
|
||||
| `verbs[]` | stable | the `/api/` verbs this provider ANSWERS |
|
||||
| `signals` | stable | which verbs answer `?since=<marker>`, and whether this provider mints a process identity. See `change-signal.md` |
|
||||
| `supplies[]` | provisional | binaries this package ships, so the host need not have them |
|
||||
| `seeds` | provisional | how to make an empty root usable on first run |
|
||||
|
||||
### `signals` — what a consumer may poll, declared by the thing that knows
|
||||
|
||||
```json
|
||||
"signals": { "verbs": ["/api/clips"], "boot": true }
|
||||
```
|
||||
|
||||
`signals.verbs[]` are the observe verbs that honour `?since=<marker>` and answer
|
||||
`{ changed, marker, … }`. `signals.boot` says the provider mints a process
|
||||
identity, which is what makes a CODE change detectable at all. The semantics of
|
||||
both live in `change-signal.md`; this is only where a provider declares them.
|
||||
|
||||
**It is a declaration and not a derivation for the reason everything else here
|
||||
is: only the provider knows.** A verb answering `?since=` is a property of the
|
||||
implementation, not of the route, so nothing can work it out by looking.
|
||||
|
||||
A `signals.verbs` entry that names a verb this provider does not declare in
|
||||
`verbs[]` is **warned** — a consumer polling it would be polling something this
|
||||
backend never answers, which is a declaration that quietly does nothing rather
|
||||
than an error the operator can see.
|
||||
|
||||
### `seeds` — first-run scaffolding, declared by the package that knows how
|
||||
|
||||
```json
|
||||
|
||||
@@ -264,7 +264,7 @@ defect this repository has already paid for twice.
|
||||
| `/signal.js` serving `watchSignal` / `preserveAcross` / `deferWhileDirty` | **yes** |
|
||||
| latch-on-first-observation, marker-backwards-is-changed, degraded reporting | **yes**, with controls |
|
||||
| `?since=` on an observe verb | **not yet** — the reference runtime's verbs are fixture-backed and mint no marker. The consumer half is shipped and tested against a scripted transport |
|
||||
| `signals` in `provider.json`, carried through composition | **not yet** |
|
||||
| `signals` in `provider.json`, carried through composition | **yes** — documented in `app-manifest.md`, carried by the composer, and a `signals.verbs` entry the provider does not answer is warned |
|
||||
| `preserve` accepted as contract on a surface row and carried to the shell | **yes** — schema and runtime validation kept in step by a committed test |
|
||||
| the helper reading `preserve` from the row and driving capture/restore from it | **yes** — `declaredKinds` + `preserveDeclared`, with controls |
|
||||
| `standardAdapter` binding `scroll`, `form` and `playhead` to real DOM | **WRITTEN, NOT YET VERIFIED.** The code exists and its RULES are under control — which kinds it claims, and which fields it refuses. Its DOM behaviour has not been exercised in a browser, and a test runner cannot exercise it. **Do not cite the code as evidence it works; this row flips on a browser pass, not on the code landing.** |
|
||||
|
||||
@@ -112,6 +112,7 @@ test("EVERY contract field on a provider.json reaches the spec, not just the one
|
||||
verbs: ["/api/clips"],
|
||||
supplies: ["ffmpeg", "ffprobe"],
|
||||
seeds: { root: "project", marker: "timeline.json", entry: "new.mjs", export: "scaffold" },
|
||||
signals: { verbs: ["/api/clips"], boot: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -124,6 +125,49 @@ test("EVERY contract field on a provider.json reaches the spec, not just the one
|
||||
assert.deepEqual(spec.supplies, ["ffmpeg", "ffprobe"], "supplies was dropped — the install gate reads it");
|
||||
assert.deepEqual(spec.seeds, { root: "project", marker: "timeline.json", entry: "new.mjs", export: "scaffold" },
|
||||
"seeds was dropped, so a documented declaration could never reach the seeder");
|
||||
assert.deepEqual(spec.signals, { verbs: ["/api/clips"], boot: true },
|
||||
"signals was dropped, so a consumer could never learn which verbs answer ?since=");
|
||||
});
|
||||
|
||||
test("a signals verb the provider does not answer is WARNED, not carried silently", () => {
|
||||
// A declaration that quietly does nothing: a consumer would poll ?since=
|
||||
// against a route this backend never serves. The same class the seam has
|
||||
// removed repeatedly, arriving through a new field.
|
||||
const t = tree({
|
||||
apps: { alpha: { provider: { package: "@x/video" } } },
|
||||
providers: {
|
||||
video: {
|
||||
package: "@x/video", run: { entry: "v.mjs" },
|
||||
verbs: ["/api/clips"],
|
||||
signals: { verbs: ["/api/clips", "/api/nothing-answers-this"], boot: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
const r = t.run(["alpha"]);
|
||||
assert.ok(r.warnings.some((w) => /nothing-answers-this/.test(w)),
|
||||
`an unanswerable signal verb was carried without a word: ${JSON.stringify(r.warnings)}`);
|
||||
// The positive half: the verb that IS answered must not be warned about, or
|
||||
// the check is just noise that gets ignored.
|
||||
assert.ok(!r.warnings.some((w) => /signals\.verbs names \/api\/clips\b/.test(w)),
|
||||
"a correctly declared signal verb was warned about");
|
||||
});
|
||||
|
||||
test("a prefix-declared verb satisfies a signals declaration for its children", () => {
|
||||
// The trailing-slash rule already used by byte routes and calls. Without this
|
||||
// a provider declaring /api/export-status/ would be warned for signalling on
|
||||
// it, which is exactly the false alarm that teaches people to ignore warnings.
|
||||
const t = tree({
|
||||
apps: { alpha: { provider: { package: "@x/video" } } },
|
||||
providers: {
|
||||
video: {
|
||||
package: "@x/video", run: { entry: "v.mjs" },
|
||||
verbs: ["/api/export-status/"],
|
||||
signals: { verbs: ["/api/export-status/job7"], boot: false },
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.deepEqual(t.run(["alpha"]).warnings.filter((w) => /signals\.verbs/.test(w)), [],
|
||||
"a prefix declaration did not satisfy a signals entry for its child");
|
||||
});
|
||||
|
||||
// --------------------------------------------------- the unmatched-call ladder
|
||||
|
||||
@@ -218,7 +218,25 @@ export function composeRail({ appsRoot, enabled, doors = [], runtimeDir, studioR
|
||||
// silently one layer down.
|
||||
spec.seeds = decl.seeds ?? null;
|
||||
spec.supplies = decl.supplies ?? [];
|
||||
// Which verbs answer ?since=, and whether this provider mints a process
|
||||
// identity (contract/change-signal.md). Carried explicitly like every other
|
||||
// provider-owned field, so adding one to the contract without carrying it
|
||||
// here fails the composer's field-by-field test rather than going quiet.
|
||||
spec.signals = decl.signals ?? null;
|
||||
spec.declaredBy = `${pkg} (provider.json)`;
|
||||
|
||||
// A signal verb this provider does not actually answer is a declaration that
|
||||
// does nothing: a consumer would poll ?since= against a route this backend
|
||||
// never serves. Warned rather than refused, because the provider still works
|
||||
// and the rest of the studio should not be held up by it.
|
||||
for (const v of decl.signals?.verbs ?? []) {
|
||||
if (!(spec.verbs ?? []).some((d) => verbMatches(d, v))) {
|
||||
warnings.push(
|
||||
`${pkg}: signals.verbs names ${v}, which this provider does not declare in verbs[] — ` +
|
||||
`a consumer polling it would be polling a route this backend does not answer`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- the unmatched-call ladder ---------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user