sweep EXIT FILE: 100 Summary [ 355.216s] 1953 tests run: 1952 passed (1 slow, 9 leaky), 1 failed, 0 skipped FAILs: 2 === W3 tests in the sweep === PASS [ 0.036s] (1011/1953) spt-proto shortform::tests::a_live_tags_body_is_carried_verbatim PASS [ 0.037s] (1012/1953) spt-proto shortform::tests::a_lone_backtick_suppresses_nothing PASS [ 0.045s] (1013/1953) spt-proto shortform::tests::a_plain_tag_splits_targets_from_body PASS [ 0.041s] (1014/1953) spt-proto shortform::tests::a_quoted_tag_and_a_live_tag_coexist PASS [ 0.054s] (1015/1953) spt-proto shortform::tests::a_space_inside_the_id_list_ends_the_list PASS [ 0.053s] (1016/1953) spt-proto shortform::tests::a_tag_inside_a_fenced_block_is_a_quotation PASS [ 0.059s] (1017/1953) spt-proto shortform::tests::a_tag_inside_inline_backticks_is_a_quotation PASS [ 0.063s] (1018/1953) spt-proto shortform::tests::a_tag_needs_both_a_target_and_a_body PASS [ 0.059s] (1019/1953) spt-proto shortform::tests::an_unclosed_fence_suppresses_to_the_end PASS [ 0.061s] (1020/1953) spt-proto shortform::tests::an_unterminated_tag_is_not_a_dispatch PASS [ 0.068s] (1021/1953) spt-proto shortform::tests::backticks_inside_a_fence_are_just_fenced_content PASS [ 0.059s] (1022/1953) spt-proto shortform::tests::several_tags_in_one_text_all_parse PASS [ 0.060s] (1023/1953) spt-proto shortform::tests::targets_are_comma_separated_and_ordered PASS [ 0.073s] (1026/1953) spt-runtime manifest::io_gate_tests::a_compliance_declaration_enables_shortform PASS [ 0.087s] (1028/1953) spt-runtime manifest::io_gate_tests::a_declaring_adapter_can_still_opt_out PASS [ 0.084s] (1029/1953) spt-runtime manifest::io_gate_tests::absent_io_section_means_shortform_is_off PASS [ 0.085s] (1030/1953) spt-runtime manifest::io_gate_tests::an_undeclared_io_section_is_still_off right: "{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"title\": \"spt-core runtime manifest\",\n \"description\": \"Per-adapter runtime manifest for the spt-core harness contract. Authored as TOML (this schema describes the equivalent data model). A manifest declares only what varies per harness/shell; command templates are opaque strings spt-core never parses. Cross-field invariants (kind<->[shell] agreement, strategy/avenue field requirements) are enforced by spt-core's validate step beyond this schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"adapter\": {\n \"$ref\": \"#/$defs/Adapter\"\n },\n \"hooks\": {\n \"description\": \"`[hooks.]` — inbound hook table, keyed by harness event name.\",\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/$defs/Hook\"\n }\n },\n \"session\": {\n \"description\": \"`[session]` — watched-dir keys plus the `[session.]` templates.\",\n \"$ref\": \"#/$defs/Session\"\n },\n \"env\": {\n \"description\": \"`[env.]` — env-var inject/read table.\",\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/$defs/EnvVar\"\n }\n },\n \"history\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/History\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"digest\": {\n \"description\": \"`[digest]` — the adapter-declared session-digest extractor seam (ADR-0019).\",\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/Digest\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"inject\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/Inject\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"io\": {\n \"description\": \"`[io]` — the adapter's IO-funnel compliance declaration (releases#22 W3,\\noperator rulings 8 and 9). Absent ⇒ core parses NOTHING of this\\nadapter's ingest, which is what makes the migration safe.\",\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/Io\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"message-idle-translation-binary\": {\n \"description\": \"`[message-idle-translation-binary]` — opt-in adapter idle-delivery\\ntranslation binary (ADR-0022 / REQ-MSG-IDLE-TRANSLATION-BINARY). A TABLE\\ncarrying a `path` scalar (modeled as a table, not a bare top-level scalar, so\\nan author who writes it after another section cannot have it silently\\nabsorbed — and so it stays N+1 extensible). The binary is a pure\\nstdin→stdout JSON-lines filter: spt-core feeds it `init`/`event`/`input`\\nlines and reads back `{key}`/`{delay_ms}`/`{text}` keystroke-commands, which\\nspt-core applies to the broker-held PTY atomically (spt-core owns every PTY\\nwrite). spt-core LIFECYCLE-manages it (spawn when the spt-hosted endpoint\\ncomes up, terminate when it goes down). A NEW manifest primitive — NOT\\ncollapsed into `[inject]`/`notif_command` — though it shares the poll-feed\\nsubstrate. Absent ⇒ no translation binary (idle inbound SPOOLS, poll-fed;\\nthe v0.11.0 raw-inject was removed as a delivery path, ADR-0022 amendment).\",\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/IdleTranslationBinary\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"service\": {\n \"description\": \"`[service]` — the daemon-supervised ResidentService this adapter owns\\n(ADR-0049). Absent ⇒ the adapter has no supervised binary. Independent of\\n`adapter.kind`: a shell adapter's service is the shape the first consumer\\nships, but nothing about the substrate requires a shell.\",\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/Service\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"identity\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/Identity\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"update\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/Update\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"shell\": {\n \"description\": \"`[shell]` body — present iff `adapter.kind = \\\"shell\\\"` (validated).\",\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/Shell\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"profiles\": {\n \"description\": \"`[profiles.]` — **shipped** profile overlays: sparse leaf-replace\\noverlays declared by the adapter dev inside the parent manifest, updating\\nas one unit with it. Stored raw ([`toml::Value`]); [`crate::profile::resolve`]\\nmerges one onto the base and re-validates the complete manifest. A bare\\n`adapter_name` ignores these (parent unmodified); the composite\\n`:` selects one. **Local** (node-local, user-authored)\\nprofiles live beside the adapter in the registry, never here.\\n(CONTEXT.md §adapter profile.)\",\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"strings\": {\n \"description\": \"`[strings]` — an adapter-authored KV tree of **opaque data** (spt-core\\nnever executes a string; command templates live in their own sections\\nbehind registration). Dot-path-readable via `spt adapter get-string`, and\\nit rides the same leaf-replace profile overlay as the rest of the manifest\\n(a shipped or local profile may override base strings). Node-local; no\\ncross-node sync. (CONTEXT.md §adapter strings.)\",\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"hints\": {\n \"description\": \"`[[hints]]` — once-per-session keyword hints (CONTEXT.md §keyword hints).\\n**Order is significant** (first match wins). A profile overlays this by\\nleaf-replace like any section — the array is replaced wholesale, never\\nspliced (override/extend = re-declare).\",\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/Hint\"\n }\n }\n },\n \"required\": [\n \"adapter\"\n ],\n \"$defs\": {\n \"Adapter\": {\n \"description\": \"`[adapter]` — the manifest header, readable before any update (compat gate).\",\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"kind\": {\n \"$ref\": \"#/$defs/AdapterKind\",\n \"default\": \"harness\"\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"min_spt_core_version\": {\n \"description\": \"Lowest spt-core version this adapter tolerates (compat gate).\",\n \"type\": \"string\"\n },\n \"hostable_types\": {\n \"description\": \"Endpoint types this adapter can host (`LiveAgent`, `Worker`, …).\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"shortcut_basename\": {\n \"description\": \"Optional basename for the `spt endpoint run` picker's `-`\\nlauncher shortcut (REQ-MANIFEST-7). Absent ⇒ the harness-agnostic default\\n`spt` (→ `spt-`); an adapter sets this to brand its shortcuts\\n(spt-claude-code → `cc`, giving `cc-`). Additive + N-1-safe (omitted\\nfrom serialization when absent). The picker reads it from the RESOLVED\\nmanifest of the selected adapter.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"host_binaries\": {\n \"description\": \"The harness executable basenames this `kind=\\\"harness\\\"` adapter hosts\\nagents inside (e.g. `host_binaries = [\\\"claude\\\"]`). The bind-time\\nadapter-resolution match-key (REQ-MANIFEST-8 / ADR-0021): a harness\\nsession's parent pid → its exe basename selects the candidate adapters\\nwhose `host_binaries` contains it (case-insensitive, `.exe`-stripped), so\\n`listen`/`poll` resolve the owning adapter at bind with no mandatory\\n`--adapter`. Additive + N-1-safe (omitted from serialization when empty,\\nlike `shortcut_basename`); an empty list is harmless (the adapter is never\\na bind-time candidate, only reachable via the explicit `--adapter`).\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"required\": [\n \"name\",\n \"version\",\n \"min_spt_core_version\"\n ]\n },\n \"AdapterKind\": {\n \"description\": \"The two adapter kinds. A `harness` hosts agents; a `shell` provides a driven\\nsurface (MANIFEST §Shell adapters).\",\n \"type\": \"string\",\n \"enum\": [\n \"harness\",\n \"shell\"\n ]\n },\n \"Hook\": {\n \"description\": \"`[hooks.]` — one harness event → the `api` command it fires, the\\nstdin fields it maps, and whether it can surface context to the agent.\",\n \"type\": \"object\",\n \"properties\": {\n \"fires\": {\n \"description\": \"Opaque `api …` command line the harness invokes for this event.\",\n \"type\": \"string\"\n },\n \"reads\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"can_inject\": {\n \"description\": \"Whether this hook can inject context (false ⇒ sentinel/relay fallback).\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\n \"fires\"\n ]\n },\n \"Session\": {\n \"description\": \"`[session]` — the watched-dir keys (`commune_dir`/`signoff_dir`) co-located\\nwith the fixed set of `[session.]` command templates.\",\n \"type\": \"object\",\n \"properties\": {\n \"commune_dir\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"signoff_dir\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"self\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/SessionRole\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"resume\": {\n \"description\": \"`[session.resume]` — the agent's OWN-session NATIVE resume (the `self_`\\nsibling, mirroring `psyche_init`→`psyche_resume`). Selected over `self_`\\nonly when a bringup carries `--resume ` AND this role is declared;\\nabsent ⇒ fall back to `[session.self]` (full back-compat). Keys spt-core\\nfills are the SAME catalog as `self`: `{id}`, `{session_id}` (the resumed\\nid), `{session_name}`, `{adapter_name}` (REQ-SESSION-RESUME-TEMPLATE).\",\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/SessionRole\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"psyche_init\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/SessionRole\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"psyche_resume\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/SessionRole\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"echo_commune\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/SessionRole\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"signoff\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/SessionRole\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"notif\": {\n \"description\": \"`[session.notif]` — the endpoint-native notification render\\n(ADR-0007's `notif_command` seam, REQ-NOTIF-2): an OS toast, a\\nGameRobot `alert-symbol`, anything the adapter can run. Spawned\\ndetached when a notif surfaces at this endpoint, combinable with the\\nagent-surface delivery. Keys spt-core fills: `{notif_id}`,\\n`{notif_from}`, `{notif_subnet}`, `{notif_body}`.\",\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/SessionRole\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n }\n },\n \"SessionRole\": {\n \"description\": \"`[session.]` — one opaque outbound command template plus its spawn\\ncontext. Model/tools/flags all live inside `command`, never as fields\\n(MANIFEST §session roles). No nested tables here (keeps TOML round-trip\\nemission scalar-before-table clean).\",\n \"type\": \"object\",\n \"properties\": {\n \"command\": {\n \"description\": \"Opaque command line, with `{key}` substitution placeholders.\",\n \"type\": \"string\"\n },\n \"cwd\": {\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"recursion_guard_env\": {\n \"description\": \"Env var stamped (`=1`) on this role's spawned process so the child's own\\nhooks bail instead of recursing (recursion guard). Honored for ANY role\\nthat declares the field — core keys on the field, never on a role name.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"detach\": {\n \"type\": \"boolean\",\n \"default\": false\n },\n \"env_remove\": {\n \"description\": \"Env vars to strip from the child's inherited environment.\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"keys\": {\n \"description\": \"Substitution keys spt-core guarantees to fill for this role.\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"invocation_budget_secs\": {\n \"description\": \"How long a BOUNDED invocation of this role may run before spt-core kills it,\\nin seconds. The adapter declares it because the adapter is the only party that\\nknows what its own model costs; spt-core clamps it so a declaration cannot buy\\nan unbounded spawn.\\n\\nAbsent ⇒ [`DEFAULT_INVOCATION_BUDGET_SECS`]. Above the ceiling ⇒ clamped to\\n[`MAX_INVOCATION_BUDGET_SECS`]. Read through [`invocation_budget`] at every\\nbounded spawn — the resolution keys on THIS FIELD, never on which role it is\\n(the same discipline `recursion_guard_env` carries).\",\n \"type\": [\n \"integer\",\n \"null\"\n ],\n \"format\": \"uint64\",\n \"minimum\": 0\n }\n },\n \"required\": [\n \"command\"\n ]\n },\n \"EnvVar\": {\n \"description\": \"`[env.]` — a single env-var directive.\",\n \"type\": \"object\",\n \"properties\": {\n \"direction\": {\n \"$ref\": \"#/$defs/EnvDirection\"\n },\n \"value\": {\n \"description\": \"Value to inject (with substitution); required for `inject`.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"channel\": {\n \"description\": \"Harness-hosted injection channel (spt-hosted inherits from the broker).\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"required\": [\n \"direction\"\n ]\n },\n \"EnvDirection\": {\n \"type\": \"string\",\n \"enum\": [\n \"inject\",\n \"read\"\n ]\n },\n \"History\": {\n \"description\": \"`[history]` — transcript access strategy.\",\n \"type\": \"object\",\n \"properties\": {\n \"strategy\": {\n \"$ref\": \"#/$defs/HistoryStrategy\"\n },\n \"fetcher\": {\n \"description\": \"`fetcher` strategy: adapter binary emitting normalized history.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"locate_template\": {\n \"description\": \"`locate_normalize` strategy: where the raw transcript lives.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"normalize_command\": {\n \"description\": \"`locate_normalize` strategy: command normalizing the raw transcript.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"required\": [\n \"strategy\"\n ]\n },\n \"HistoryStrategy\": {\n \"oneOf\": [\n {\n \"description\": \"spt-core asks the adapter (pull, adapter binary emits normalized).\",\n \"type\": \"string\",\n \"const\": \"fetcher\"\n },\n {\n \"description\": \"spt-core locates the raw transcript then normalizes it.\",\n \"type\": \"string\",\n \"const\": \"locate_normalize\"\n },\n {\n \"description\": \"Adapter pushes via `api history-log`; spt-core stores (Path-B).\",\n \"type\": \"string\",\n \"const\": \"native\"\n }\n ]\n },\n \"Digest\": {\n \"description\": \"`[digest]` — the session-digest extractor seam (ADR-0019). Reverses M9's\\n\\\"no manifest seam\\\" stance: the digest gets its **own** adapter-declared\\nextractor, distinct from `[history]` (which stays opaque + single-session and\\nfeeds the echo-commune verbatim). The extractor maps the harness's **native**\\nlog → the published `{role, text, tool, ts}` digest-record contract\\n([`spt_term::DigestRecord`]).\\n\\n**Imperative, not a DSL** (ADR-0019 §Decision): real harness logs are nested\\n(one line → many entries, mixed block lists, types to filter) — a flat\\ndeclarative map cannot express them, and a map powerful enough is a reinvented\\nlanguage. So the extractor is an opaque command spt-core never parses, exactly\\nlike every other manifest template.\\n\\n**Source.** By default the extractor reads the **same files as `[history]`**\\n(the `locate_template`; DRY). An adapter may override with `source` (the\\nown-source escape hatch). `api digest-entry` push remains the always-available\\nfallback for a log-less adapter (which declares no `[digest]` at all).\\n\\n**Presentation.** `window_turns`, `arg_truncation`, and `sprint_collapse` are\\nadapter-declared **defaults** any consumer may override at pull/subscribe;\\nspt-core ships fallback defaults ([`spt_term::DigestConfig`]) when absent. The\\nfixed \\\"~3 turns\\\" is no longer an spt-core requirement (ADR-0019).\",\n \"type\": \"object\",\n \"properties\": {\n \"strategy\": {\n \"description\": \"Which side locates the transcript (REQ-DIGEST-FETCHER-STRATEGY). Absent ⇒\\n`locate_normalize` (spt-core locates + pre-reads `source`) — the original\\nbehavior, so every pre-strategy manifest parses unchanged.\",\n \"$ref\": \"#/$defs/DigestStrategy\",\n \"default\": \"locate_normalize\"\n },\n \"extractor\": {\n \"description\": \"Opaque extractor command: native harness log → the `{role,text,tool,ts}`\\ncontract (one JSON record per output line). `{key}` substitution applies\\n(`{session_id}`, the perch-bound `{cwd}`, captured `[env] direction=\\\"read\\\"`\\nvars, and — in `locate_normalize` mode — `{source}` for the resolved path).\",\n \"type\": \"string\"\n },\n \"source\": {\n \"description\": \"Own-source escape hatch (`locate_normalize` only): a `locate_template` for\\nthe log file the extractor reads. Absent ⇒ reuse `[history].locate_template`\\n(DRY). Ignored under `fetcher` (the extractor locates itself).\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"window_turns\": {\n \"description\": \"Adapter-default window depth (user turns kept). Absent ⇒ spt-core fallback.\",\n \"type\": [\n \"integer\",\n \"null\"\n ],\n \"format\": \"uint\",\n \"minimum\": 0\n },\n \"arg_truncation\": {\n \"description\": \"Adapter-default tool-arg truncation width. Absent ⇒ spt-core fallback.\",\n \"type\": [\n \"integer\",\n \"null\"\n ],\n \"format\": \"uint\",\n \"minimum\": 0\n },\n \"sprint_collapse\": {\n \"description\": \"Adapter-default for collapsing consecutive tool records into one sprint.\\nAbsent ⇒ spt-core fallback (collapse on).\",\n \"type\": [\n \"boolean\",\n \"null\"\n ]\n }\n },\n \"required\": [\n \"extractor\"\n ]\n },\n \"DigestStrategy\": {\n \"description\": \"`[digest]` locate-ownership strategy (REQ-DIGEST-FETCHER-STRATEGY) — mirrors\\n[`HistoryStrategy`]'s locate/normalize split, applied to the digest extractor.\\nWhich side LOCATES the transcript:\\n- `locate_normalize` (default, back-compat): **spt-core** resolves the single\\n `source` template to one file, reads it, and pipes the bytes to the extractor\\n on stdin. Correct for a trivial single-file, fully-templatable layout.\\n- `fetcher`: the **adapter's** extractor locates + reads + emits normalized\\n records itself; spt-core only runs it bounded and consumes stdout (no locate,\\n no pre-read). Required for a PARTITIONED layout spt-core cannot express as one\\n template — e.g. Claude Code's `projects//.jsonl` or a\\n date-globbed rollout tree — the CONTEXT §history \\\"the adapter owns the locate\\\"\\n case. spt-core feeds the extractor only the harness-NEUTRAL inputs it owns\\n (`{session_id}`, the perch-bound `{cwd}`, and captured `[env] direction=\\\"read\\\"`\\n vars), never a harness-specific project slug.\",\n \"oneOf\": [\n {\n \"description\": \"spt-core locates the single `source` file and pipes its bytes to the\\nextractor (the original ADR-0019 behavior — the default for back-compat).\",\n \"type\": \"string\",\n \"const\": \"locate_normalize\"\n },\n {\n \"description\": \"The adapter's extractor locates + reads + emits; spt-core runs it bounded\\nand consumes stdout (no `source`, no pre-read).\",\n \"type\": \"string\",\n \"const\": \"fetcher\"\n }\n ]\n },\n \"Inject\": {\n \"description\": \"`[inject]` — inject-input methods per activity state.\",\n \"type\": \"object\",\n \"properties\": {\n \"activity\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/InjectMethod\"\n }\n },\n \"idle\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/InjectMethod\"\n }\n }\n }\n },\n \"InjectMethod\": {\n \"type\": \"string\",\n \"enum\": [\n \"pty\",\n \"hook\",\n \"relay\",\n \"http\"\n ]\n },\n \"Io\": {\n \"description\": \"`[io]` — the adapter's IO-funnel declaration (releases#22 W3).\\n\\n**The default is OFF, and that is the whole point.** Operator ruling 9\\nenables core-side shortform per-adapter only on an explicit compliance\\ndeclaration, because an adapter shipping its own parser must remain the ONLY\\nparser until its own release deletes that parser and declares here, in one\\nchange. A default-on core would double-fire against every adapter already in\\nthe field the moment core upgraded — the exact window the ruling forbids.\\n\\n`shortform` is ruling 8's opt-out for an exotic harness whose output cannot\\nlive with the grammar: absent means enabled (declaring compliance is a\\nstatement that the adapter is ready), `false` disables just this reader while\\nleaving the declaration standing.\",\n \"type\": \"object\",\n \"properties\": {\n \"compliance\": {\n \"description\": \"The adapter declares it is IO-funnel compliant: its own local parsers are\\ngone as of this release, so core may parse its ingest.\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"shortform\": {\n \"description\": \"Opt out of core-side `@<…@>` shortform parsing while staying compliant.\\nAbsent ⇒ enabled.\",\n \"type\": [\n \"boolean\",\n \"null\"\n ]\n }\n }\n },\n \"IdleTranslationBinary\": {\n \"description\": \"`[message-idle-translation-binary]` — the opt-in idle-delivery translation\\nbinary (ADR-0022). A table so the contract degrades gracefully: spt-core does\\nNOT `deny_unknown_fields`, so a newer adapter declaring a future key (e.g. a\\nspawn timeout) against an older spt-core parses fine — the unknown key is\\nignored, never a hard manifest failure (a lifecycle-binary contract perri\\nbuilds blind from docs must be forward-compatible). Known keys: `command`\\n(preferred) and the deprecated `path`.\",\n \"type\": \"object\",\n \"properties\": {\n \"command\": {\n \"description\": \"The opaque command spt-core spawns and lifecycle-manages (ADR-0029):\\nprogram + args, with `{adapter_dir}`/`{adapter_name}` substitution; the\\nprogram token resolves against `install_dir` like `[digest].extractor` /\\n`[session.psyche_resume]`. The spawn + stdin/stdout JSON-lines protocol is\\nunchanged — `command` only alters how the executable+args are located.\\nFolds `claude-spt translate` into the one consolidated adapter binary.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"path\": {\n \"description\": \"**Deprecated** (ADR-0029): the bare binary PATH spt-core spawns. Keeps\\nparsing (manifest forward/back-compat) but warns at registration steering\\nto `command`. Resolved against `install_dir`; a single program token (no\\nsubstitution, never re-tokenized). Exactly one of `{command, path}` —\\nboth-set is refused at validation; neither = no translation binary.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n }\n },\n \"Service\": {\n \"description\": \"`[service]` — a **ResidentService**: a binary the DAEMON supervises on the\\nadapter's behalf (ADR-0049). Core-owned from birth — spawned job-neutrally\\nby the daemon, so it is never a shell's child (`/T` tree-kill cannot reach\\nit) and never inside a launching terminal's Job Object. It has no perch, no\\nidentity, and no address; a service needing a two-way agent-facing surface\\nhas one at its adapter's endpoint/shell layer.\\n\\nA table, and deliberately NOT `deny_unknown_fields` (the\\n[`IdleTranslationBinary`] reasoning): a newer adapter declaring a future key\\nagainst an older spt-core parses fine rather than hard-failing an adapter\\nwhose author built blind from the published docs.\",\n \"type\": \"object\",\n \"properties\": {\n \"command\": {\n \"description\": \"The opaque command the daemon spawns: program + args, with\\n`{adapter_dir}`/`{adapter_name}` substitution, the program token\\nresolving against `install_dir` before `PATH` exactly as\\n`[digest].extractor` and `[message-idle-translation-binary].command` do\\n(REQ-INSTALL-11 — one resolution primitive, no parallel path).\",\n \"type\": \"string\"\n },\n \"start\": {\n \"description\": \"When the supervisor reconciles this service toward running. REQUIRED —\\nthere is deliberately no default: the choice decides whether a\\nleast-trusted third-party binary rises with the daemon itself, and an\\nadapter author must say so rather than inherit it.\",\n \"$ref\": \"#/$defs/ServiceStart\"\n },\n \"stop_grace_ms\": {\n \"description\": \"Cooperative-exit grace, in milliseconds, before the supervisor stops\\nwaiting and force-kills (quiesce is exit-when-safe + deadline: delay is\\npossible, veto is not).\",\n \"type\": \"integer\",\n \"format\": \"uint64\",\n \"minimum\": 0,\n \"default\": 30000\n }\n },\n \"required\": [\n \"command\",\n \"start\"\n ]\n },\n \"ServiceStart\": {\n \"description\": \"`[service].start` — the reconcile trigger (ADR-0049).\",\n \"oneOf\": [\n {\n \"description\": \"**Desired-state-running**, not an event: the supervisor reconciles\\ntoward running at daemon boot, at adapter registration against a live\\ndaemon (installing an adapter never requires restarting spt to bring its\\nservice up), at update-hold release, and at first shell bind as the\\ndefensive ensure.\",\n \"type\": \"string\",\n \"const\": \"boot\"\n },\n {\n \"description\": \"Lazy: starts at the adapter's first shell bind. Supervised identically\\nonce running.\",\n \"type\": \"string\",\n \"const\": \"bind\"\n }\n ]\n },\n \"Identity\": {\n \"description\": \"`[identity]` — how the harness's session id is obtained.\",\n \"type\": \"object\",\n \"properties\": {\n \"session_id_source\": {\n \"$ref\": \"#/$defs/SessionIdSource\"\n },\n \"parent_ancestor_name\": {\n \"description\": \"Process-tree anchor name when `session_id` is absent.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"required\": [\n \"session_id_source\"\n ]\n },\n \"SessionIdSource\": {\n \"oneOf\": [\n {\n \"description\": \"Discovered after spawn (process-tree / wrapper handoff).\",\n \"type\": \"string\",\n \"const\": \"post_spawn\"\n },\n {\n \"description\": \"Injected as a UUID the harness echoes back.\",\n \"type\": \"string\",\n \"const\": \"uuid_inject\"\n }\n ]\n },\n \"Update\": {\n \"description\": \"`[update]` — adapter self-update directive (parsed in M2a; conducted in M3).\",\n \"type\": \"object\",\n \"properties\": {\n \"avenue\": {\n \"$ref\": \"#/$defs/UpdateAvenue\"\n },\n \"command\": {\n \"description\": \"`delegated` avenue: the command spt-core delegates to.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"repo\": {\n \"description\": \"`file_pull` / `gh_release` avenue: source repo. For `gh_release` this is\\nthe `user/repo` whose **GitHub releases** the adapter ships updates from.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"path_regex\": {\n \"description\": \"`file_pull` avenue: path selector.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"asset\": {\n \"description\": \"`gh_release` avenue: the release **asset** name to fetch (the adapter\\n`.spt` archive). Absent ⇒ the default `adapter.spt`, matching the\\n`spt adapter add --release` acquisition primitive (REQ-INSTALL-9). Not\\napplicable to `delegated` / `file_pull`.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"signing_key\": {\n \"description\": \"The adapter's Ed25519 **content-signing public key** (64 hex chars / 32\\nbytes). spt-core verifies a pulled payload against this per-adapter key\\nbefore applying it (REQ-UPD-5 adapter content signing, ADR-0004 §D) — the\\nadapter author signs their own releases; spt-core's release key stays\\nscoped to spt-core. **Required for `file_pull`** (there are bytes to\\nverify); **optional for `gh_release`** (absent ⇒ HTTPS+GitHub\\nfirst-acquisition trust, the same trust `spt adapter add --release` and\\nthe installer first-fetch place; present ⇒ the fetched `.spt` is verified\\nfail-closed against this key, REQ-UPD-9); not applicable to `delegated`\\n(opaque updater).\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"self_verifies\": {\n \"description\": \"`delegated` avenue: the adapter attests its own updater verifies the\\ncontent it installs (e.g. `claude.exe plugin update` checks its own\\nsignatures). spt-core cannot see a delegated updater's bytes, so it\\ndelegates the trust **only** when this is set; an unattested delegated\\nupdate is skipped as unverifiable (REQ-UPD-5).\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"uninstall\": {\n \"description\": \"Optional inverse of install — run by `spt adapter remove` once the adapter\\nis quiesced (the mirror of `spt adapter add`, which reuses this section as\\nthe install mechanism). Absent ⇒ spt-core's default cleanup. (Modeled in\\nM2a; conducted with adapter-registration later.)\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"message\": {\n \"description\": \"Optional plain (multi-line) human notice surfaced to stdout —\\nmarkdown-rendered (the helpfmt prose path) — **only when `spt adapter\\nupdate` actually APPLIES an update** (version changed), never on a\\nno-op. Read from the newly-installed manifest; avenue-agnostic\\n(`gh_release` / `delegated` / `file_pull`). No `{key}` substitution.\\nUse: an adapter telling the operator a post-update action (e.g. \\\"run\\n`/reload-plugins` in any ongoing sessions\\\").\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"post\": {\n \"description\": \"`[update.post]` — an **avenue-agnostic** delegated post-step (ADR-0029) run\\nAFTER the primary avenue resolves, in the same `spt adapter update`.\\nAbsent ⇒ today's behavior exactly (an applied update fires `message`).\",\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/UpdatePost\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"transport\": {\n \"description\": \"`gh_release` avenue: the fetch **transport** — `https` (direct reqwest,\\npublic), `gh` (shell the pre-authorized `gh` CLI, the private-repo path),\\nor `auto` (default: prefer `gh` when installed+authed, else HTTPS). `gh`\\nhonors OAuth + `GH_TOKEN`, so spt-core custodies no token. Additive over\\nthe existing fetch path; not applicable to `delegated` / `file_pull`.\\nAbsent ⇒ `auto` (N-1-safe).\",\n \"$ref\": \"#/$defs/Transport\"\n }\n },\n \"required\": [\n \"avenue\"\n ]\n },\n \"UpdateAvenue\": {\n \"oneOf\": [\n {\n \"description\": \"Delegate to the adapter's own updater (e.g. `claude plugin update`).\",\n \"type\": \"string\",\n \"const\": \"delegated\"\n },\n {\n \"description\": \"spt-core pulls files from a repo.\",\n \"type\": \"string\",\n \"const\": \"file_pull\"\n },\n {\n \"description\": \"spt-core ships updates from the adapter's own **GitHub releases**\\n(REQ-UPD-9): compare the repo's latest release version against the\\ninstalled adapter version and, when newer, fetch the release `.spt`\\narchive (the REQ-INSTALL-9 `--release` primitive), verify it against an\\noptional `signing_key` (else HTTPS+GitHub trust), and re-register. No\\nsigning tooling or plugin coupling required of the adapter author.\",\n \"type\": \"string\",\n \"const\": \"gh_release\"\n }\n ]\n },\n \"UpdatePost\": {\n \"description\": \"`[update.post]` — an avenue-agnostic delegated post-step (ADR-0029) run after\\nthe primary `[update]` avenue (gh_release / file_pull / delegated) resolves,\\nin the same `spt adapter update`. Runs **unconditionally** — even when the\\nadapter pull was a version no-op — because the post-step does its own\\nidempotent check (e.g. `claude plugin update`). spt-core feeds it the update\\noutcome as one stdin JSON line (`adapter_applied`, `adapter_name`,\\n`profile_name`, `version`, `previous_version`, `adapter_dir`; additive keys)\\nand reads its stdout to arbitrate the post-update notice (custom text\\nsupersedes `[update].message`; the reserved sentinel fires the static\\n`[update].message`; empty = no notice). exit code is orthogonal (0 ok /\\nnonzero failed). Failure-isolated: a committed `gh_release` pull is never\\nrolled back if the post-step fails. (A table so unknown future keys degrade\\ngracefully — no `deny_unknown_fields`.)\",\n \"type\": \"object\",\n \"properties\": {\n \"command\": {\n \"description\": \"The command spt-core runs after pull+re-register. Opaque; `{adapter_dir}`/\\n`{adapter_name}` substitution; the program token resolves against the\\ninstall dir (REQ-INSTALL-11). Validated non-empty.\",\n \"type\": \"string\"\n },\n \"self_verifies\": {\n \"description\": \"The post-step attests it verifies the content it installs (mirrors\\n`[update].self_verifies` for the delegated avenue — e.g. `claude plugin\\nupdate` checks its own signatures). Attestation metadata; the post-step\\nruns unconditionally regardless (the trust model around delegated content\\nthe post-step installs, not an execution gate).\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\n \"command\"\n ]\n },\n \"Transport\": {\n \"description\": \"Fetch transport for the `gh_release` avenue (and `spt adapter add --release`):\\nhow spt-core retrieves the release asset bytes + the latest-release version.\\n(REQ-ADAPTER-GH-TRANSPORT)\",\n \"oneOf\": [\n {\n \"description\": \"Direct HTTPS via reqwest — the public-repo path (the original behavior).\",\n \"type\": \"string\",\n \"const\": \"https\"\n },\n {\n \"description\": \"Shell the pre-authorized `gh` CLI (`gh release download` for the asset,\\n`gh api` for the version) — the private-repo path. `gh` honors OAuth +\\n`GH_TOKEN`, so spt-core never custodies a token.\",\n \"type\": \"string\",\n \"const\": \"gh\"\n },\n {\n \"description\": \"Prefer `gh` when it is installed and authenticated, else fall back to\\nHTTPS. The default.\",\n \"type\": \"string\",\n \"const\": \"auto\"\n }\n ]\n },\n \"Shell\": {\n \"description\": \"`[shell]` — the body of a `kind = \\\"shell\\\"` adapter (a driven surface).\",\n \"type\": \"object\",\n \"properties\": {\n \"spawn\": {\n \"description\": \"Broker-launched opaque spawn command.\",\n \"type\": \"string\"\n },\n \"ephemeral\": {\n \"description\": \"Ephemeral ⇒ no offline perch + no history retention.\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"broadcast\": {\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/Broadcast\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"command_receipt\": {\n \"description\": \"How the shell receives agent commands.\",\n \"anyOf\": [\n {\n \"$ref\": \"#/$defs/CommandReceipt\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"pre_close\": {\n \"description\": \"Instruction sent to the binary on link-break.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"close_timeout_ms\": {\n \"description\": \"Graceful-termination window before force-close.\",\n \"type\": [\n \"integer\",\n \"null\"\n ],\n \"format\": \"uint64\",\n \"minimum\": 0\n },\n \"persistent\": {\n \"description\": \"Auto-online whenever the owner endpoint is online.\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"wake_command\": {\n \"description\": \"Long-running wake-watcher run WHILE offline; exit ⇒ revive.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"can_shutdown\": {\n \"description\": \"Whether the shell may fire `api owner-shutdown` to suspend its owner.\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"require_approval\": {\n \"description\": \"Per-spawn user approval gate (floor; a node/endpoint setting may tighten).\\nAbsent ⇒ `none`. (Modeled now; conducted when shells land.)\",\n \"$ref\": \"#/$defs/ShellApproval\"\n },\n \"max_instances_per_owner\": {\n \"description\": \"Optional ceiling on concurrent-existing instances per owner endpoint\\n(online + offline both count). Absent ⇒ unlimited.\",\n \"type\": [\n \"integer\",\n \"null\"\n ],\n \"format\": \"uint32\",\n \"minimum\": 0\n },\n \"over_cap\": {\n \"description\": \"What happens at the cap: `reject` (default) or `approve` (per-spawn\\napproval beyond the cap; does not raise it). Only meaningful with a cap.\",\n \"$ref\": \"#/$defs/OverCap\"\n },\n \"capabilities\": {\n \"description\": \"`[shell.capabilities]` — the command vocabulary (agent→shell).\",\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/$defs/ShellCapability\"\n }\n },\n \"sensory\": {\n \"description\": \"`[shell.sensory]` — the sensory vocabulary (shell→agent).\",\n \"$ref\": \"#/$defs/Sensory\"\n },\n \"drive\": {\n \"description\": \"`[shell.drive]` — the drive vocabulary (agent→shell, REST-only ephemeral\\ncontrol; M11-W2, REQ-SHELL-3). The owner→shell mirror of `[shell.sensory]`.\",\n \"$ref\": \"#/$defs/Drive\"\n },\n \"tunnel\": {\n \"description\": \"`[shell.tunnel]` — opt-in for the opaque reliable-ordered byte tunnel\\n(M11-W3, REQ-SHELL-4). Absent ⇒ no tunnel.\",\n \"$ref\": \"#/$defs/Tunnel\"\n }\n },\n \"required\": [\n \"spawn\"\n ]\n },\n \"Broadcast\": {\n \"type\": \"string\",\n \"enum\": [\n \"subnet\",\n \"same-node\",\n \"none\"\n ]\n },\n \"CommandReceipt\": {\n \"type\": \"string\",\n \"enum\": [\n \"http\",\n \"stdin\",\n \"relay\"\n ]\n },\n \"ShellApproval\": {\n \"description\": \"Per-shell instantiation-approval mode (`require_approval`). Reuses the consent\\nplumbing: `remembered` lets allow-always write a persistent grant; `always`\\nsuppresses allow-always (prompt every spawn).\",\n \"oneOf\": [\n {\n \"description\": \"No approval (default — matches the system's everything-opt-in posture).\",\n \"type\": \"string\",\n \"const\": \"none\"\n },\n {\n \"description\": \"Prompt; allow-always persists a grant, later spawns auto-allow.\",\n \"type\": \"string\",\n \"const\": \"remembered\"\n },\n {\n \"description\": \"Prompt on every spawn; allow-always suppressed (no persistent grant).\",\n \"type\": \"string\",\n \"const\": \"always\"\n }\n ]\n },\n \"OverCap\": {\n \"description\": \"What happens when an owner is at its `max_instances_per_owner` cap.\",\n \"oneOf\": [\n {\n \"description\": \"Refuse the spawn outright (default).\",\n \"type\": \"string\",\n \"const\": \"reject\"\n },\n {\n \"description\": \"Require per-spawn approval beyond the cap (does not raise the cap).\",\n \"type\": \"string\",\n \"const\": \"approve\"\n }\n ]\n },\n \"ShellCapability\": {\n \"description\": \"One entry in `[shell.capabilities]` — a command and its argument names.\\n\\nA capability may carry its own **act-gate** (M11, REQ-CONSENT-3): the same\\n`require_approval` enum the spawn gate uses, riding the individual op so the\\n*dangerous act* is gated, not just the spawn. An optional `class_key` scopes\\nthe written grant finer than the op id — granted per `(owner × class × node)`\\n(the usbip `attach`'s device class), so a remembered HID-class grant never\\nauthorizes a storage-class act. Spawn gates govern existence; capability\\ngates govern acts (CONTEXT §\\\"per-capability approval gates\\\").\",\n \"type\": \"object\",\n \"properties\": {\n \"args\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"require_approval\": {\n \"description\": \"Per-act approval gate (floor; a node/endpoint setting may tighten).\\nAbsent ⇒ `none` (ungated).\",\n \"$ref\": \"#/$defs/ShellApproval\"\n },\n \"class_key\": {\n \"description\": \"Optional grant-qualifier class so a grant scopes finer than the op id\\n(`(owner × class × node)`). Only meaningful with a gated `require_approval`.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n }\n },\n \"Sensory\": {\n \"description\": \"`[shell.sensory]` — the sensory payload types a shell may emit.\",\n \"type\": \"object\",\n \"properties\": {\n \"types\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"Drive\": {\n \"description\": \"`[shell.drive]` — the drive payload types an agent may push to a shell\\n(M11-W2, REQ-SHELL-3). The owner→shell mirror of [`Sensory`]: REST-only,\\nephemeral latest-wins, never spooled.\",\n \"type\": \"object\",\n \"properties\": {\n \"types\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"Tunnel\": {\n \"description\": \"`[shell.tunnel]` — the opt-in for the opaque reliable-ordered byte tunnel\\n(M11-W3, REQ-SHELL-4): a dedicated QUIC stream pair bound to the owner↔shell\\nlink, carrying wire protocol traffic the channel taxonomy must NOT reinterpret\\n(first consumer: USB/IP URB traffic). Not enveloped, not MAC-framed, not\\nspooled; the link lifecycle governs it (a link-break closes the tunnel).\\nReliable-ordered ⇒ congestion surfaces as lag never loss ⇒ on-LAN posture.\",\n \"type\": \"object\",\n \"properties\": {\n \"enable\": {\n \"description\": \"Whether this shell opens the opaque tunnel on link-up.\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"protocol\": {\n \"description\": \"Optional diagnostic label for the opaque wire protocol (e.g. `usbip-urb`).\\nInformational only — the substrate never interprets tunnel bytes.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n }\n },\n \"Hint\": {\n \"description\": \"`[[hints]]` — one once-per-session keyword hint (CONTEXT.md §keyword hints).\\nThe adapter's user-prompt hook pipes the full user message to `spt api hint`;\\na matching keyword surfaces `text` to the agent's context channel, at most\\nonce per session and once per message.\",\n \"type\": \"object\",\n \"properties\": {\n \"keywords\": {\n \"description\": \"Keywords that fire the hint — literal **case-insensitive substrings** by\\ndefault; compiled as **regex** patterns when `regex = true`.\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"text\": {\n \"description\": \"The hint text surfaced when a keyword matches.\",\n \"type\": \"string\"\n },\n \"regex\": {\n \"description\": \"Treat `keywords` as regex patterns instead of literal substrings.\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\n \"text\"\n ]\n }\n },\n \"$id\": \"http://localhost:5474/manifest.schema.json\"\n}\n" PASS [ 0.056s] (1284/1953) spt-store dispatchresults::tests::a_torn_last_line_never_hides_the_rows_before_it PASS [ 0.041s] (1286/1953) spt-store dispatchresults::tests::no_file_reads_as_no_results PASS [ 0.047s] (1290/1953) spt-store dispatchresults::tests::rows_accumulate_per_target_across_dispatches