40	/// How the manifest is held: a copy under `adapters/<name>/`, or a pointer
41	/// re-read from the source tree per load.
42	#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43	#[serde(rename_all = "snake_case")]
44	pub enum RecordMode {
45	    Copy,
46	    Pointer,
47	}
48
49	/// One registered adapter's durable record (`adapters/<name>/record.toml`).
50	#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51	pub struct AdapterRecord {
52	    pub name: String,
53	    pub kind: AdapterKind,
54	    pub mode: RecordMode,
55	    /// Where the adapter was registered from (the manifest's directory) —
56	    /// the live manifest location in pointer mode, provenance in copy mode.
57	    pub source_dir: String,
58	    pub registered_at_ms: u64,
59	    /// `false` = soft-deregistered: hidden from new-creation/discovery;
60	    /// existing instances keep running (REQ-INSTALL-4).
61	    pub active: bool,
62	}
63
64	/// Registration failures — adapter-author-facing, so they carry the why.
65	#[derive(Debug)]
66	pub enum RegistryError {
67	    /// The source manifest failed to read.
68	    Io(std::io::Error),
69	    /// The source manifest failed parse/validation — nothing was recorded.
70	    Manifest(crate::manifest::ManifestError),
71	    /// `remove`/lookup named an adapter that has no record.
72	    NotRegistered(String),
73	    /// A record on disk is unreadable/corrupt (named so the operator can fix).
74	    BadRecord(String),
75	    /// A local profile name is not a safe single path segment.
76	    InvalidProfileName(String),
77	    /// A local profile would shadow a shipped profile of the same name (refused
78	    /// at registration — CONTEXT.md §adapter profile).
79	    ProfileShadowsShipped(String),
80	    /// `delete-profile`/`get-string` named a local profile that has no file.
81	    ProfileNotFound(String),
82	    /// `delete-profile` named a **shipped** profile (immutable — CLI operates on
83	    /// local profiles only).
84	    ShippedProfileImmutable(String),
85	}
86
87	impl std::fmt::Display for RegistryError {
88	    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89	        match self {
90	            RegistryError::Io(e) => write!(f, "io: {e}"),
91	            RegistryError::Manifest(e) => write!(f, "manifest invalid: {e}"),
92	            RegistryError::NotRegistered(n) => write!(f, "adapter not registered: {n}"),
93	            RegistryError::BadRecord(n) => write!(f, "corrupt adapter record: {n}"),
94	            RegistryError::InvalidProfileName(n) => {
95	                write!(f, "invalid profile name '{n}' (no ':', path separators, or whitespace)")
96	            }
97	            RegistryError::ProfileShadowsShipped(n) => {
98	                write!(f, "local profile '{n}' would shadow a shipped profile of the same name")
99	            }
100	            RegistryError::ProfileNotFound(n) => write!(f, "local profile not found: {n}"),
101	            RegistryError::ShippedProfileImmutable(n) => {
102	                write!(f, "profile '{n}' is shipped (adapter-owned); only local profiles are editable")
103	            }
104	        }
105	    }
106	}
107
108	impl From<std::io::Error> for RegistryError {
109	    fn from(e: std::io::Error) -> Self {
110	        RegistryError::Io(e)
111	    }
112	}
113
114	fn record_file(adapters_dir: &Path, name: &str) -> PathBuf {

/// How the manifest is held: a copy under `adapters/<name>/`, or a pointer
/// re-read from the source tree per load.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RecordMode {
    Copy,
    Pointer,
}

/// One registered adapter's durable record (`adapters/<name>/record.toml`).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AdapterRecord {
    pub name: String,
    pub kind: AdapterKind,
    pub mode: RecordMode,
    /// Where the adapter was registered from (the manifest's directory) —
    /// the live manifest location in pointer mode, provenance in copy mode.
    pub source_dir: String,
    pub registered_at_ms: u64,
    /// `false` = soft-deregistered: hidden from new-creation/discovery;
    /// existing instances keep running (REQ-INSTALL-4).
    pub active: bool,
}

/// Registration failures — adapter-author-facing, so they carry the why.
#[derive(Debug)]
pub enum RegistryError {
    /// The source manifest failed to read.
    Io(std::io::Error),
    /// The source manifest failed parse/validation — nothing was recorded.
    Manifest(crate::manifest::ManifestError),
    /// `remove`/lookup named an adapter that has no record.
    NotRegistered(String),
    /// A record on disk is unreadable/corrupt (named so the operator can fix).
    BadRecord(String),
    /// A local profile name is not a safe single path segment.
    InvalidProfileName(String),
    /// A local profile would shadow a shipped profile of the same name (refused
    /// at registration — CONTEXT.md §adapter profile).
    ProfileShadowsShipped(String),
    /// `delete-profile`/`get-string` named a local profile that has no file.
    ProfileNotFound(String),
    /// `delete-profile` named a **shipped** profile (immutable — CLI operates on
    /// local profiles only).
    ShippedProfileImmutable(String),
}

impl std::fmt::Display for RegistryError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RegistryError::Io(e) => write!(f, "io: {e}"),
            RegistryError::Manifest(e) => write!(f, "manifest invalid: {e}"),
            RegistryError::NotRegistered(n) => write!(f, "adapter not registered: {n}"),
            RegistryError::BadRecord(n) => write!(f, "corrupt adapter record: {n}"),
            RegistryError::InvalidProfileName(n) => {
                write!(f, "invalid profile name '{n}' (no ':', path separators, or whitespace)")
            }
            RegistryError::ProfileShadowsShipped(n) => {
                write!(f, "local profile '{n}' would shadow a shipped profile of the same name")
            }
            RegistryError::ProfileNotFound(n) => write!(f, "local profile not found: {n}"),
            RegistryError::ShippedProfileImmutable(n) => {
                write!(f, "profile '{n}' is shipped (adapter-owned); only local profiles are editable")
            }
        }
    }
}

impl From<std::io::Error> for RegistryError {
    fn from(e: std::io::Error) -> Self {
        RegistryError::Io(e)
    }
}

fn record_file(adapters_dir: &Path, name: &str) -> PathBuf {


proceed with W1.5 next steps. first verify the plan with doyle


Strings base dir depends on RecordMode (Pointer=source_dir, Copy=adapters/<name>/). Read register() body to wire validation + Copy-mode strings/ copy:


130	}
131
132	/// Register (or re-register) the adapter whose manifest lives at `source`
133	/// (a directory containing `manifest.toml`, or the manifest path itself).
134	/// Validate-first: an invalid manifest records **nothing**. Mode falls out of
135	/// the `[update]` avenue — `delegated` ⇒ pointer, `file_pull`/absent ⇒ copy.
136	/// Re-adding an existing name refreshes the record and re-activates a
137	/// soft-deregistered one. Returns the record; the caller conducts
138	/// install-is-first-update through the manifest's `[update]` avenue.
139	// [impl->REQ-INSTALL-4]
140	pub fn register(
141	    adapters_dir: &Path,
142	    source: &Path,
143	    now_ms: u64,
144	) -> Result<(AdapterRecord, Manifest), RegistryError> {
145	    let manifest_file = source_manifest_file(source);
146	    let toml_src = std::fs::read_to_string(&manifest_file)?;
147	    let manifest = Manifest::from_toml_str(&toml_src).map_err(RegistryError::Manifest)?;
148
149	    let mode = match manifest.update.as_ref().map(|u| u.avenue) {
150	        Some(UpdateAvenue::Delegated) => RecordMode::Pointer,
151	        Some(UpdateAvenue::FilePull) | None => RecordMode::Copy,
152	    };
153	    let source_dir = manifest_file
154	        .parent()
155	        .unwrap_or(Path::new("."))
156	        .to_string_lossy()
157	        .into_owned();
158
159	    let record = AdapterRecord {
160	        name: manifest.adapter.name.clone(),
161	        kind: manifest.adapter.kind,
162	        mode,
163	        source_dir,
164	        registered_at_ms: now_ms,
165	        active: true,
166	    };
167
168	    let dir = adapters_dir.join(&record.name);
169	    std::fs::create_dir_all(&dir)?;
170	    if mode == RecordMode::Copy {
171	        atomic_write_string(&manifest_copy_file(adapters_dir, &record.name), &toml_src)?;
172	    }
173	    save_record(adapters_dir, &record)?;
174	    Ok((record, manifest))
175	}
176
177	/// Soft-deregister `name`: flip `active = false` (the record + any manifest
178	/// copy stay — existing instances keep running off them). Returns the
179	/// manifest's optional `[update] uninstall` template for the caller to
180	/// conduct once the adapter is quiesced (`spt adapter remove`'s optional
181	/// second half); `None` = spt-core default cleanup. Idempotent on an
182	/// already-inactive record.
183	// [impl->REQ-INSTALL-4]
184	pub fn deregister(adapters_dir: &Path, name: &str) -> Result<Option<String>, RegistryError> {
185	    let mut record = load_record(adapters_dir, name)?;
186	    record.active = false;
187	    save_record(adapters_dir, &record)?;
188	    let uninstall = load_manifest(adapters_dir, &record)
189	        .ok()
190	        .and_then(|m| m.update.and_then(|u| u.uninstall));
191	    Ok(uninstall)
192	}
193
194	/// One adapter's record by name.
195	pub fn load_record(adapters_dir: &Path, name: &str) -> Result<AdapterRecord, RegistryError> {
196	    let path = record_file(adapters_dir, name);
197	    if !path.exists() {
198	        return Err(RegistryError::NotRegistered(name.to_string()));
199	    }
200	    let s = std::fs::read_to_string(&path)?;
201	    toml::from_str(&s).map_err(|_| RegistryError::BadRecord(name.to_string()))
202	}
203
204	fn save_record(adapters_dir: &Path, record: &AdapterRecord) -> Result<(), RegistryError> {
205	    let s = toml::to_string_pretty(record)
206	        .map_err(|e| RegistryError::BadRecord(format!("{}: {e}", record.name)))?;
207	    atomic_write_string(&record_file(adapters_dir, &record.name), &s)?;
208	    Ok(())
209	}
210
211	/// The record's manifest: the held copy in copy mode, the live source tree in
212	/// pointer mode (a delegated adapter's self-updates are always honored).
213	// [impl->REQ-INSTALL-4]
214	pub fn load_manifest(
215	    adapters_dir: &Path,
216	    record: &AdapterRecord,
217	) -> Result<Manifest, RegistryError> {
218	    let path = match record.mode {
219	        RecordMode::Copy => manifest_copy_file(adapters_dir, &record.name),
220	        RecordMode::Pointer => source_manifest_file(Path::new(&record.source_dir)),
221	    };
222	    let s = std::fs::read_to_string(&path)?;
223	    Manifest::from_toml_str(&s).map_err(RegistryError::Manifest)
224	}

}

/// Register (or re-register) the adapter whose manifest lives at `source`
/// (a directory containing `manifest.toml`, or the manifest path itself).
/// Validate-first: an invalid manifest records **nothing**. Mode falls out of
/// the `[update]` avenue — `delegated` ⇒ pointer, `file_pull`/absent ⇒ copy.
/// Re-adding an existing name refreshes the record and re-activates a
/// soft-deregistered one. Returns the record; the caller conducts
/// install-is-first-update through the manifest's `[update]` avenue.
// [impl->REQ-INSTALL-4]
pub fn register(
    adapters_dir: &Path,
    source: &Path,
    now_ms: u64,
) -> Result<(AdapterRecord, Manifest), RegistryError> {
    let manifest_file = source_manifest_file(source);
    let toml_src = std::fs::read_to_string(&manifest_file)?;
    let manifest = Manifest::from_toml_str(&toml_src).map_err(RegistryError::Manifest)?;

    let mode = match manifest.update.as_ref().map(|u| u.avenue) {
        Some(UpdateAvenue::Delegated) => RecordMode::Pointer,
        Some(UpdateAvenue::FilePull) | None => RecordMode::Copy,
    };
    let source_dir = manifest_file
        .parent()
        .unwrap_or(Path::new("."))
        .to_string_lossy()
        .into_owned();

    let record = AdapterRecord {
        name: manifest.adapter.name.clone(),
        kind: manifest.adapter.kind,
        mode,
        source_dir,
        registered_at_ms: now_ms,
        active: true,
    };

    let dir = adapters_dir.join(&record.name);
    std::fs::create_dir_all(&dir)?;
    if mode == RecordMode::Copy {
        atomic_write_string(&manifest_copy_file(adapters_dir, &record.name), &toml_src)?;
    }
    save_record(adapters_dir, &record)?;
    Ok((record, manifest))
}

/// Soft-deregister `name`: flip `active = false` (the record + any manifest
/// copy stay — existing instances keep running off them). Returns the
/// manifest's optional `[update] uninstall` template for the caller to
/// conduct once the adapter is quiesced (`spt adapter remove`'s optional
/// second half); `None` = spt-core default cleanup. Idempotent on an
/// already-inactive record.
// [impl->REQ-INSTALL-4]
pub fn deregister(adapters_dir: &Path, name: &str) -> Result<Option<String>, RegistryError> {
    let mut record = load_record(adapters_dir, name)?;
    record.active = false;
    save_record(adapters_dir, &record)?;
    let uninstall = load_manifest(adapters_dir, &record)
        .ok()
        .and_then(|m| m.update.and_then(|u| u.uninstall));
    Ok(uninstall)
}

/// One adapter's record by name.
pub fn load_record(adapters_dir: &Path, name: &str) -> Result<AdapterRecord, RegistryError> {
    let path = record_file(adapters_dir, name);
    if !path.exists() {
        return Err(RegistryError::NotRegistered(name.to_string()));
    }
    let s = std::fs::read_to_string(&path)?;
    toml::from_str(&s).map_err(|_| RegistryError::BadRecord(name.to_string()))
}

fn save_record(adapters_dir: &Path, record: &AdapterRecord) -> Result<(), RegistryError> {
    let s = toml::to_string_pretty(record)
        .map_err(|e| RegistryError::BadRecord(format!("{}: {e}", record.name)))?;
    atomic_write_string(&record_file(adapters_dir, &record.name), &s)?;
    Ok(())
}

/// The record's manifest: the held copy in copy mode, the live source tree in
/// pointer mode (a delegated adapter's self-updates are always honored).
// [impl->REQ-INSTALL-4]
pub fn load_manifest(
    adapters_dir: &Path,
    record: &AdapterRecord,
) -> Result<Manifest, RegistryError> {
    let path = match record.mode {
        RecordMode::Copy => manifest_copy_file(adapters_dir, &record.name),
        RecordMode::Pointer => source_manifest_file(Path::new(&record.source_dir)),
    };
    let s = std::fs::read_to_string(&path)?;
    Manifest::from_toml_str(&s).map_err(RegistryError::Manifest)
}


Full layout understood. Implement T3.1. First `as_file_pointer` detector in profile.rs:


Edit

/// Set `value` at a dot-separated `key_path` in a `[strings]` tree, creating
/// intermediate tables as needed (the write half of `set-string`). Errors on an
/// empty path segment or a path that traverses an existing non-table leaf.
// [impl->REQ-MANIFEST-3]
pub fn set_string_at(

/// A `[strings]` value that is a **file pointer** (M12-W3, REQ-MANIFEST-5):
/// a value-position table carrying a `file` string key, e.g.
/// `key = { file = "rel/path" }`. Returns the relative path when so. This table
/// shape IS reserved as the pointer form — a literal `{ file = … }` value can't
/// double as inline data (documented in the manifest schema). The pointer is
/// resolved to the file's contents lazily at `get-string` time, never here.
// [impl->REQ-MANIFEST-5]
pub fn as_file_pointer(value: &Value) -> Option<&str> {
    value.as_table()?.get("file")?.as_str()
}

/// Set `value` at a dot-separated `key_path` in a `[strings]` tree, creating
/// intermediate tables as needed (the write half of `set-string`). Errors on an
/// empty path segment or a path that traverses an existing non-table leaf.
// [impl->REQ-MANIFEST-3]
pub fn set_string_at(


<owl_messages>
<owl_message from="doyle" priority="high">W3 DESIGN RULING (doyle) — verified your claims vs source first (strings opaque toml::Table manifest.rs:66-75; get_string rides resolve_option adapter:profile-aware registry.rs:377; fallback_adapter docs-only, NO .rs consumer). Full ruling on disk: M12-W3-RULING.md. Summary:

T3.1 file-backed strings — APPROVED w/ guards:
- Q1 syntax: inline table key={file="rel/path"}, DISAMBIGUATED — pointer recognized ONLY when value is a table with EXACTLY one key `file` (typed StringFile{file:String}); any other table = opaque nested tree (back-compat). Leaf-replace already swaps pointer<->literal cleanly, no new overlay logic.
- Q2 location: adapters/<adapter>/strings/ sibling of profiles/, paths relative to it. REQUIRED HAZARD-class guard: reject `..` + absolute paths, ship the escape-attempt unit test as hazard evidence.
- Q3 timing: validate-exists @register, LAZY read @get-string, skip-diag (emit diagnostic, no silent drop, don't error) on missing/unreadable — matches [digest]. get_string returns Value::String(contents).
- REQ-MANIFEST-5 in traceable-reqs.toml FIRST. ADR-0001 wire: additive, N-1-safe, regen manifest.schema.json (CI drift-gated).

T3.2 fallback target adapter:profile — RULE (a) contract-only, NOT (b):
- resolve_option ALREADY resolves <adapter>:<profile>. So T3.2 = contract lock: unit test proving fallback-target resolves adapter:profile-aware (incl bare `ccs` and `ccs:<profile>`) + reconcile CONTEXT.md:137 (ccs = own adapter). DO NOT add a config field with no reader. No new impl REQ unless real new surface — tag unit+doc only.
- ESCALATION: if spt-claude-code is genuinely blocked without an authored fallback seam, STOP + escalate to me, don't build speculative config.

Gate: build uncommitted, I gate (reproduce traceable + read load-bearing tests) before commit. Suite + clippy -D + traceable EXIT=0. Operator calls the commit. Build away.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\profile.rs has been updated successfully. (file state is current in your context — no need to Read it back)

/// A `[strings]` value that is a **file pointer** (M12-W3, REQ-MANIFEST-5):
/// a value-position table carrying a `file` string key, e.g.
/// `key = { file = "rel/path" }`. Returns the relative path when so. This table
/// shape IS reserved as the pointer form — a literal `{ file = … }` value can't
/// double as inline data (documented in the manifest schema). The pointer is
/// resolved to the file's contents lazily at `get-string` time, never here.
// [impl->REQ-MANIFEST-5]
pub fn as_file_pointer(value: &Value) -> Option<&str> {
    value.as_table()?.get("file")?.as_str()
}

/// Set `value` at a dot-separated `key_path` in a `[strings]` tree, creating
/// intermediate tables as needed (the write half of `set-string`). Errors on an
/// empty path segment or a path that traverses an existing non-table leaf.
// [impl->REQ-MANIFEST-3]
pub fn set_string_at(

+/// A `[strings]` value that is a **file pointer** (M12-W3, REQ-MANIFEST-5):

+/// a value-position table carrying a `file` string key, e.g.

+/// `key = { file = "rel/path" }`. Returns the relative path when so. This table

+/// shape IS reserved as the pointer form — a literal `{ file = … }` value can't

+/// double as inline data (documented in the manifest schema). The pointer is

+/// resolved to the file's contents lazily at `get-string` time, never here.

+pub fn as_file_pointer(value: &Value) -> Option<&str> {

+    value.as_table()?.get("file")?.as_str()


<task-notification>
<task-id>b104dalv4</task-id>
<tool-use-id>toolu_01GRGyPFsrT2FU9sQCSxXLA3</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\b104dalv4.output</output-file>
<status>completed</status>
<summary>Background command "Ack doyle full W3 ruling" completed (exit code 0)</summary>
</task-notification>


doyle's full ruling received — key refinement: file pointer recognized **only when the table has EXACTLY one key `file`** (any other table stays opaque nested tree → back-compat). Plus containment guard is hazard-class (ship escape-attempt test as evidence). T3.2 contract-only, unit+doc. My earlier `as_file_pointer` is too loose — must fix.

Ack doyle + read the ruling file:


Bash

$OWL send doyle todlando <<'EOF'
W3 full ruling ACK — incorporating. Key refinement caught: file pointer recognized ONLY when value is a table with EXACTLY one key `file` (string) — any other table stays opaque nested tree (back-compat). Fixing my as_file_pointer (was too loose — matched any table with a `file` key). Containment guard = hazard-class: shipping the `..`/absolute escape-attempt unit as hazard evidence. Q3 skip-diag (emit + None, never error/crash) at read; validate-exists + containment fail-fast at register. T3.2 = contract-only, MANIFEST-6 doc+unit (no config field, no reader); will escalate to you instead of building speculative fallback config if spt-claude-code turns out blocked. Regen manifest.schema.json (additive, N-1). Build uncommitted; you gate before operator commit. Building now.
EOF

Ack doyle full W3 ruling


Command running in background with ID: b104dalv4. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\b104dalv4.output. You will be notified when it completes. To check interim output, use Read on that file path.


1	# M12 W3 — doyle design ruling
2
3	> doyle 2026-06-14, gating todlando's W3 plan (M12-W3-PLAN.md). Claims independently verified
4	> against source before ruling:
5	> - `strings: toml::Table` opaque, REQ-MANIFEST-3, dot-path via `profile::string_at` — manifest.rs:66-75 ✓
6	> - `get_string` resolves through `resolve_option` (already `<adapter>:<profile>`-aware) — registry.rs:377-384 ✓
7	> - `fallback_adapter` exists ONLY in docs (CONTEXT.md:137, MANIFEST.md) — NO `.rs` consumer ✓
8
9	## T3.1 — file-backed `[strings]` — APPROVED with guards
10
11	**Q1 syntax — APPROVE inline table, disambiguated.** `key = { file = "rel/path" }`. Pointer
12	recognized ONLY when the value is a table with EXACTLY one key `file` → typed `StringFile { file: String }`.
13	Any other table shape stays an opaque nested strings tree (back-compat: existing nested string
14	trees untouched). Leaf-replace overlay already replaces the whole node, so a profile cleanly swaps
15	pointer→literal string OR literal→pointer. No new overlay logic.
16
17	**Q2 location — APPROVE `adapters/<adapter>/strings/`** (sibling of `profiles/`). File paths resolve
18	relative to that dir. **REQUIRED guard (HAZARD-class): reject `..` and absolute paths** (path
19	traversal) — ship a dedicated unit test for the escape attempt, tag it as the hazard evidence.
20	Shipped adapter ships its strings file in-package; a local profile may override the pointer
21	(different file) OR inline a literal (leaf-replace). Confirmed.
22
23	**Q3 timing — APPROVE.** Validate-exists at register; read LAZILY at `get-string` (edits reflect
24	without re-register); skip-diagnostics (emit a diagnostic, NO silent drop, do NOT error) on
25	missing/unreadable — matches the `[digest]` stance. `get_string` returns `Value::String(file_contents)`
26	on a healthy read.
27
28	**REQ:** mint REQ-MANIFEST-5 in traceable-reqs.toml FIRST. ADR-0001 wire territory — additive,
29	N-1-safe, regen `manifest.schema.json` (CI drift-gated). Stages activate as evidence lands.
30
31	## T3.2 — cross-adapter fallback target `<adapter>:<profile>` — RULE (a), minimal contract-only
32
33	NOT (b). No consumer exists; standing up the node-wide setting + invocation-switch now is
34	speculative wiring → violates "activate, don't pre-fail." The consuming milestone (echo/psyche
35	rate-limit fallback) owns the switch.
36
37	For W3: `resolve_option` ALREADY resolves `<adapter>:<profile>`. So T3.2 is a **contract lock**:
38	- Unit test proving a fallback-target address resolves adapter:profile-aware via `resolve_option`
39	  (incl. bare `ccs` and `ccs:<profile>`).
40	- Reconcile CONTEXT.md:137 wording — ccs is its own adapter, addressable bare `ccs` or `ccs:<profile>`.
41	- **Do NOT add a config field with no reader.** No new impl REQ unless genuinely new surface appears
42	  — tag unit + doc stages only (fold under an existing MANIFEST/registry REQ if one fits).
43
44	**Escalation clause:** if during build you find spt-claude-code is genuinely BLOCKED without an
45	authored fallback seam, STOP and escalate to me — don't build speculative config.
46
47	## Gate posture
48	Build uncommitted. I gate (reproduce traceable + read the load-bearing tests) before any commit.
49	Full suite + clippy -D + traceable EXIT=0 required. Operator calls the commit.
50

# M12 W3 — doyle design ruling

> doyle 2026-06-14, gating todlando's W3 plan (M12-W3-PLAN.md). Claims independently verified
> against source before ruling:
> - `strings: toml::Table` opaque, REQ-MANIFEST-3, dot-path via `profile::string_at` — manifest.rs:66-75 ✓
> - `get_string` resolves through `resolve_option` (already `<adapter>:<profile>`-aware) — registry.rs:377-384 ✓
> - `fallback_adapter` exists ONLY in docs (CONTEXT.md:137, MANIFEST.md) — NO `.rs` consumer ✓

## T3.1 — file-backed `[strings]` — APPROVED with guards

**Q1 syntax — APPROVE inline table, disambiguated.** `key = { file = "rel/path" }`. Pointer
recognized ONLY when the value is a table with EXACTLY one key `file` → typed `StringFile { file: String }`.
Any other table shape stays an opaque nested strings tree (back-compat: existing nested string
trees untouched). Leaf-replace overlay already replaces the whole node, so a profile cleanly swaps
pointer→literal string OR literal→pointer. No new overlay logic.

**Q2 location — APPROVE `adapters/<adapter>/strings/`** (sibling of `profiles/`). File paths resolve
relative to that dir. **REQUIRED guard (HAZARD-class): reject `..` and absolute paths** (path
traversal) — ship a dedicated unit test for the escape attempt, tag it as the hazard evidence.
Shipped adapter ships its strings file in-package; a local profile may override the pointer
(different file) OR inline a literal (leaf-replace). Confirmed.

**Q3 timing — APPROVE.** Validate-exists at register; read LAZILY at `get-string` (edits reflect
without re-register); skip-diagnostics (emit a diagnostic, NO silent drop, do NOT error) on
missing/unreadable — matches the `[digest]` stance. `get_string` returns `Value::String(file_contents)`
on a healthy read.

**REQ:** mint REQ-MANIFEST-5 in traceable-reqs.toml FIRST. ADR-0001 wire territory — additive,
N-1-safe, regen `manifest.schema.json` (CI drift-gated). Stages activate as evidence lands.

## T3.2 — cross-adapter fallback target `<adapter>:<profile>` — RULE (a), minimal contract-only

NOT (b). No consumer exists; standing up the node-wide setting + invocation-switch now is
speculative wiring → violates "activate, don't pre-fail." The consuming milestone (echo/psyche
rate-limit fallback) owns the switch.

For W3: `resolve_option` ALREADY resolves `<adapter>:<profile>`. So T3.2 is a **contract lock**:
- Unit test proving a fallback-target address resolves adapter:profile-aware via `resolve_option`
  (incl. bare `ccs` and `ccs:<profile>`).
- Reconcile CONTEXT.md:137 wording — ccs is its own adapter, addressable bare `ccs` or `ccs:<profile>`.
- **Do NOT add a config field with no reader.** No new impl REQ unless genuinely new surface appears
  — tag unit + doc stages only (fold under an existing MANIFEST/registry REQ if one fits).

**Escalation clause:** if during build you find spt-claude-code is genuinely BLOCKED without an
authored fallback seam, STOP and escalate to me — don't build speculative config.

## Gate posture
Build uncommitted. I gate (reproduce traceable + read the load-bearing tests) before any commit.
Full suite + clippy -D + traceable EXIT=0 required. Operator calls the commit.


Prompt:
<task-notification>
<task-id>b104dalv4</task-id>
<tool-use-id>toolu_01GRGyPFsrT2FU9sQCSxXLA3</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\b104dalv4.output</output-file>
<status>completed</status>
<summary>Background command "Ack doyle full W3 ruling" completed (exit code 0)</summary>
</task-notification>


Ruling matches my plan + the exactly-one-key refinement. Fix `as_file_pointer` — require a table with EXACTLY one key `file`:


Edit

/// A `[strings]` value that is a **file pointer** (M12-W3, REQ-MANIFEST-5):
/// a value-position table carrying a `file` string key, e.g.
/// `key = { file = "rel/path" }`. Returns the relative path when so. This table
/// shape IS reserved as the pointer form — a literal `{ file = … }` value can't
/// double as inline data (documented in the manifest schema). The pointer is
/// resolved to the file's contents lazily at `get-string` time, never here.
// [impl->REQ-MANIFEST-5]
pub fn as_file_pointer(value: &Value) -> Option<&str> {
    value.as_table()?.get("file")?.as_str()
}

/// A `[strings]` value that is a **file pointer** (M12-W3, REQ-MANIFEST-5):
/// a value-position table with EXACTLY one key, `file`, whose value is a string —
/// e.g. `key = { file = "rel/path" }`. Returns that relative path. The
/// exactly-one-key rule is the disambiguation (doyle): any OTHER table shape
/// (including one that merely *contains* a `file` key alongside others) stays an
/// opaque nested strings tree, so existing nested trees are untouched
/// (back-compat). This single-key table shape IS reserved as the pointer form —
/// a literal `{ file = … }` value cannot double as inline data. The pointer is
/// resolved to the file's contents lazily at `get-string` time, never here.
// [impl->REQ-MANIFEST-5]
pub fn as_file_pointer(value: &Value) -> Option<&str> {
    let table = value.as_table()?;
    if table.len() != 1 {
        return None;
    }
    table.get("file")?.as_str()
}


/// A `[strings]` value that is a **file pointer** (M12-W3, REQ-MANIFEST-5):
/// a value-position table carrying a `file` string key, e.g.
/// `key = { file = "rel/path" }`. Returns the relative path when so. This table
/// shape IS reserved as the pointer form — a literal `{ file = … }` value can't
/// double as inline data (documented in the manifest schema). The pointer is
/// resolved to the file's contents lazily at `get-string` time, never here.
// [impl->REQ-MANIFEST-5]
pub fn as_file_pointer(value: &Value) -> Option<&str> {
    value.as_table()?.get("file")?.as_str()
}

/// A `[strings]` value that is a **file pointer** (M12-W3, REQ-MANIFEST-5):
/// a value-position table with EXACTLY one key, `file`, whose value is a string —
/// e.g. `key = { file = "rel/path" }`. Returns that relative path. The
/// exactly-one-key rule is the disambiguation (doyle): any OTHER table shape
/// (including one that merely *contains* a `file` key alongside others) stays an
/// opaque nested strings tree, so existing nested trees are untouched
/// (back-compat). This single-key table shape IS reserved as the pointer form —
/// a literal `{ file = … }` value cannot double as inline data. The pointer is
/// resolved to the file's contents lazily at `get-string` time, never here.
// [impl->REQ-MANIFEST-5]
pub fn as_file_pointer(value: &Value) -> Option<&str> {
    let table = value.as_table()?;
    if table.len() != 1 {
        return None;
    }
    table.get("file")?.as_str()
}

-/// a value-position table carrying a `file` string key, e.g.

-/// `key = { file = "rel/path" }`. Returns the relative path when so. This table

-/// shape IS reserved as the pointer form — a literal `{ file = … }` value can't

-/// double as inline data (documented in the manifest schema). The pointer is

+/// a value-position table with EXACTLY one key, `file`, whose value is a string —

+/// e.g. `key = { file = "rel/path" }`. Returns that relative path. The

+/// exactly-one-key rule is the disambiguation (doyle): any OTHER table shape

+/// (including one that merely *contains* a `file` key alongside others) stays an

+/// opaque nested strings tree, so existing nested trees are untouched

+/// (back-compat). This single-key table shape IS reserved as the pointer form —

+/// a literal `{ file = … }` value cannot double as inline data. The pointer is

-    value.as_table()?.get("file")?.as_str()

/// Set `value` at a dot-separated `key_path` in a `[strings]` tree, creating


Now registry.rs. Check imports (need `std::path::Component`):


28	//! `spt_store::atomic`).
29	// [impl->REQ-INSTALL-4]
30
31	use serde::{Deserialize, Serialize};
32	use std::path::{Path, PathBuf};
33	use toml::Value;
34
35	use spt_store::atomic::atomic_write_string;
36
37	use crate::manifest::{AdapterKind, Manifest, UpdateAvenue};
38	use crate::profile;
39
40	/// How the manifest is held: a copy under `adapters/<name>/`, or a pointer
41	/// re-read from the source tree per load.

//! `spt_store::atomic`).
// [impl->REQ-INSTALL-4]

use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use toml::Value;

use spt_store::atomic::atomic_write_string;

use crate::manifest::{AdapterKind, Manifest, UpdateAvenue};
use crate::profile;

/// How the manifest is held: a copy under `adapters/<name>/`, or a pointer
/// re-read from the source tree per load.


use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use toml::Value;

use serde::{Deserialize, Serialize};
use std::path::{Component, Path, PathBuf};
use toml::Value;


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime\src\registry.rs has been updated successfully. (file state is current in your context — no need to Read it back)

use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use toml::Value;

use serde::{Deserialize, Serialize};
use std::path::{Component, Path, PathBuf};
use toml::Value;

+use std::path::{Component, Path, PathBuf};


Add the `BadStringPointer` error variant + its Display:


    /// `delete-profile` named a **shipped** profile (immutable — CLI operates on
    /// local profiles only).
    ShippedProfileImmutable(String),
}

    /// `delete-profile` named a **shipped** profile (immutable — CLI operates on
    /// local profiles only).
    ShippedProfileImmutable(String),
    /// A `[strings]` file pointer (REQ-MANIFEST-5) is invalid at registration —
    /// it escapes the adapter's `strings/` dir (`..`/absolute — the HAZARD-class
    /// containment guard) or names a file that does not exist. Fail-fast: a bad
    /// pointer records nothing (the validate-at-register half of the contract).
    BadStringPointer(String),
}


<owl_messages>
<owl_message from="doyle" priority="high">ACK your refinements — all correct. Two confirms: (1) MANIFEST-6 as doc+unit-only for T3.2 is fine (locks the contract, no impl/config) — good call. (2) "validate-exists + containment fail-fast at register" — yes, register REFUSES a pointer escaping the strings dir (loud), read-time stays skip-diag. Build away; ping when W3 is suite-green + I'll gate (reproduce traceable + read the load-bearing tests: as_file_pointer single-key, the escape-attempt hazard unit, lazy-read-reflects-edit, profile-override-of-pointer, fallback resolve adapter:profile). doyle on experimplate meanwhile.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


    /// `delete-profile` named a **shipped** profile (immutable — CLI operates on
    /// local profiles only).
    ShippedProfileImmutable(String),
}

    /// `delete-profile` named a **shipped** profile (immutable — CLI operates on
    /// local profiles only).
    ShippedProfileImmutable(String),
    /// A `[strings]` file pointer (REQ-MANIFEST-5) is invalid at registration —
    /// it escapes the adapter's `strings/` dir (`..`/absolute — the HAZARD-class
    /// containment guard) or names a file that does not exist. Fail-fast: a bad
    /// pointer records nothing (the validate-at-register half of the contract).
    BadStringPointer(String),
}

+    /// A `[strings]` file pointer (REQ-MANIFEST-5) is invalid at registration —

+    /// it escapes the adapter's `strings/` dir (`..`/absolute — the HAZARD-class

+    /// containment guard) or names a file that does not exist. Fail-fast: a bad

+    /// pointer records nothing (the validate-at-register half of the contract).

impl std::fmt::Display for RegistryError {


<task-notification>
<task-id>bk9mrwj3x</task-id>
<tool-use-id>toolu_01WmJwM7whsLtrayeUKsJv7Y</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bk9mrwj3x.output</output-file>
<status>completed</status>
<summary>Background command "Ack doyle confirms" completed (exit code 0)</summary>
</task-notification>


doyle confirms MANIFEST-6 doc+unit + register-refuses-escape (loud) / read-time skip-diag. Test checklist noted: single-key detector, escape-attempt hazard unit, lazy-read-reflects-edit, profile-override-of-pointer, fallback adapter:profile resolve. Quick ack + continue:


Bash

$OWL send doyle todlando <<'EOF'
ACK both confirms. Building all 5 load-bearing tests you'll gate: (1) as_file_pointer single-key disambiguation, (2) escape-attempt hazard unit (..+absolute → register refuses loud), (3) lazy-read-reflects-edit (edit file post-register, get-string sees it), (4) profile-override-of-pointer (leaf-replace swaps pointer↔literal), (5) fallback resolves adapter:profile (bare ccs + ccs:profile). Ping when suite-green. Go do experimplate.
EOF

Ack doyle confirms


Command running in background with ID: bk9mrwj3x. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bk9mrwj3x.output. You will be notified when it completes. To check interim output, use Read on that file path.


Prompt:
<task-notification>
<task-id>bk9mrwj3x</task-id>
<tool-use-id>toolu_01WmJwM7whsLtrayeUKsJv7Y</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bk9mrwj3x.output</output-file>
<status>completed</status>
<summary>Background command "Ack doyle confirms" completed (exit code 0)</summary>
</task-notification>


Add Display for the new error variant:


Edit

            RegistryError::ShippedProfileImmutable(n) => {
                write!(f, "profile '{n}' is shipped (adapter-owned); only local profiles are editable")
            }
        }
    }
}

            RegistryError::ShippedProfileImmutable(n) => {
                write!(f, "profile '{n}' is shipped (adapter-owned); only local profiles are editable")
            }
            RegistryError::BadStringPointer(m) => {
                write!(f, "invalid [strings] file pointer: {m}")
            }
        }
    }
}


            RegistryError::ShippedProfileImmutable(n) => {
                write!(f, "profile '{n}' is shipped (adapter-owned); only local profiles are editable")
            }
        }
    }
}

            RegistryError::ShippedProfileImmutable(n) => {
                write!(f, "profile '{n}' is shipped (adapter-owned); only local profiles are editable")
            }
            RegistryError::BadStringPointer(m) => {
                write!(f, "invalid [strings] file pointer: {m}")
            }
        }
    }
}

+            RegistryError::BadStringPointer(m) => {

+                write!(f, "invalid [strings] file pointer: {m}")


Now the core helpers. Read the path-helper area to place them:


118	        RegistryError::Io(e)
119	    }
120	}
121
122	fn record_file(adapters_dir: &Path, name: &str) -> PathBuf {
123	    adapters_dir.join(name).join("record.toml")
124	}
125
126	fn manifest_copy_file(adapters_dir: &Path, name: &str) -> PathBuf {
127	    adapters_dir.join(name).join("manifest.toml")
128	}
129
130	/// Resolve the manifest file inside a source: `<dir>/manifest.toml`, or the
131	/// path itself when it already names a file.
132	fn source_manifest_file(source: &Path) -> PathBuf {
133	    if source.is_dir() {
134	        source.join("manifest.toml")
135	    } else {
136	        source.to_path_buf()
137	    }
138	}
139

        RegistryError::Io(e)
    }
}

fn record_file(adapters_dir: &Path, name: &str) -> PathBuf {
    adapters_dir.join(name).join("record.toml")
}

fn manifest_copy_file(adapters_dir: &Path, name: &str) -> PathBuf {
    adapters_dir.join(name).join("manifest.toml")
}

/// Resolve the manifest file inside a source: `<dir>/manifest.toml`, or the
/// path itself when it already names a file.
fn source_manifest_file(source: &Path) -> PathBuf {
    if source.is_dir() {
        source.join("manifest.toml")
    } else {
        source.to_path_buf()
    }
}


/// Resolve the manifest file inside a source: `<dir>/manifest.toml`, or the
/// path itself when it already names a file.
fn source_manifest_file(source: &Path) -> PathBuf {
    if source.is_dir() {
        source.join("manifest.toml")
    } else {
        source.to_path_buf()
    }
}

/// Resolve the manifest file inside a source: `<dir>/manifest.toml`, or the
/// path itself when it already names a file.
fn source_manifest_file(source: &Path) -> PathBuf {
    if source.is_dir() {
        source.join("manifest.toml")
    } else {
        source.to_path_buf()
    }
}

// ───────────────────── File-backed [strings] (REQ-MANIFEST-5) ────────────────

/// The directory that holds the adapter's LIVE manifest — and, as a sibling,
/// its `strings/` aux dir. Copy mode: the held copy under `adapters/<name>/`;
/// pointer mode: the live source tree (self-updates honored, same as the
/// manifest itself).
fn manifest_dir(adapters_dir: &Path, record: &AdapterRecord) -> PathBuf {
    match record.mode {
        RecordMode::Copy => adapters_dir.join(&record.name),
        RecordMode::Pointer => PathBuf::from(&record.source_dir),
    }
}

/// The adapter-shipped string-file storage `<manifest-dir>/strings/`
/// (REQ-MANIFEST-5). Adapter updates overwrite this dir — never put user files
/// here (a local profile's pointers resolve to its own user-owned dir instead).
fn shipped_strings_dir(adapters_dir: &Path, record: &AdapterRecord) -> PathBuf {
    manifest_dir(adapters_dir, record).join("strings")
}

/// Validate a `[strings]` file-pointer relative path against the HAZARD-class
/// containment guard (doyle, REQ-MANIFEST-5): it must be a plain relative path
/// that cannot escape its `strings/` dir — reject absolute paths and any `..`
/// (or prefix/root) component. Returns the joined absolute path on success.
fn contained_string_file(base: &Path, rel: &str) -> Result<PathBuf, String> {
    let relp = Path::new(rel);
    let escapes = relp.components().any(|c| {
        matches!(
            c,
            Component::ParentDir | Component::RootDir | Component::Prefix(_)
        )
    });
    if relp.as_os_str().is_empty() || relp.is_absolute() || escapes {
        return Err(format!(
            "pointer '{rel}' must be a relative path inside the strings/ dir \
             (no absolute paths, no `..` traversal)"
        ));
    }
    Ok(base.join(relp))
}

/// Read a file-pointer's contents at `get-string` time (REQ-MANIFEST-5). The
/// containment guard is re-applied here too (defense in depth — a pointer could
/// arrive via a local overlay that never passed register-time validation).
/// **Skip-diagnostics** on a contained-but-missing/unreadable file: emit a
/// diagnostic and return `None` (never a silent drop, never a hard error) —
/// mirrors the `[digest]` stance. A containment violation here is also a skip
/// (the loud refusal is the register-time guard's job).
fn read_string_file(base: &Path, rel: &str) -> Option<Value> {
    let full = match contained_string_file(base, rel) {
        Ok(p) => p,
        Err(why) => {
            eprintln!("STRINGS_FILE_SKIP: {why}");
            return None;
        }
    };
    match std::fs::read_to_string(&full) {
        Ok(contents) => Some(Value::String(contents)),
        Err(e) => {
            eprintln!(
                "STRINGS_FILE_SKIP: cannot read string file {} ({e})",
                full.display()
            );
            None
        }
    }
}

/// Walk every file pointer in a `[strings]` tree (recursing nested tables,
/// treating a single-key `{file=…}` node as a leaf), invoking `f` with each
/// relative path. The register-time validation traversal.
fn for_each_string_pointer(strings: &toml::Table, f: &mut impl FnMut(&str)) {
    for value in strings.values() {
        if let Some(rel) = profile::as_file_pointer(value) {
            f(rel);
        } else if let Some(nested) = value.as_table() {
            for_each_string_pointer(nested, f);
        }
    }
}

/// Validate (at register, fail-fast) that every file pointer in `strings` is
/// contained AND exists under `strings_dir`. A bad pointer refuses the whole
/// registration (records nothing) — the loud half of the contract.
fn validate_string_pointers(
    strings: &toml::Table,
    strings_dir: &Path,
) -> Result<(), RegistryError> {
    let mut bad: Option<String> = None;
    for_each_string_pointer(strings, &mut |rel| {
        if bad.is_some() {
            return;
        }
        match contained_string_file(strings_dir, rel) {
            Err(why) => bad = Some(why),
            Ok(full) if !full.exists() => {
                bad = Some(format!(
                    "pointer '{rel}' names a file that does not exist at {}",
                    full.display()
                ));
            }
            Ok(_) => {}
        }
    });
    match bad {
        Some(m) => Err(RegistryError::BadStringPointer(m)),
        None => Ok(()),
    }
}

/// Recursively copy `src` into `dst` (used to bring an adapter's shipped
/// `strings/` dir into the held copy at register, so file pointers resolve
/// post-copy and survive the source going away — Copy mode only).
fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> {
    std::fs::create_dir_all(dst)?;
    for entry in std::fs::read_dir(src)? {
        let entry = entry?;
        let from = entry.path();
        let to = dst.join(entry.file_name());
        if entry.file_type()?.is_dir() {
            copy_dir_all(&from, &to)?;
        } else {
            std::fs::copy(&from, &to)?;
        }
    }
    Ok(())
}


/// Resolve the manifest file inside a source: `<dir>/manifest.toml`, or the
/// path itself when it already names a file.
fn source_manifest_file(source: &Path) -> PathBuf {
    if source.is_dir() {
        source.join("manifest.toml")
    } else {
        source.to_path_buf()
    }
}

// ───────────────────── File-backed [strings] (REQ-MANIFEST-5) ────────────────

/// The directory that holds the adapter's LIVE manifest — and, as a sibling,
/// its `strings/` aux dir. Copy mode: the held copy under `adapters/<name>/`;
/// pointer mode: the live source tree (self-updates honored, same as the
/// manifest itself).
fn manifest_dir(adapters_dir: &Path, record: &AdapterRecord) -> PathBuf {
    match record.mode {
        RecordMode::Copy => adapters_dir.join(&record.name),
        RecordMode::Pointer => PathBuf::from(&record.source_dir),
    }
}

/// The adapter-shipped string-file storage `<manifest-dir>/strings/`
/// (REQ-MANIFEST-5). Adapter updates overwrite this dir — never put user files
/// here (a local profile's pointers resolve to its own user-owned dir instead).
fn shipped_strings_dir(adapters_dir: &Path, record: &AdapterRecord) -> PathBuf {
    manifest_dir(adapters_dir, record).join("strings")
}

/// Validate a `[strings]` file-pointer relative path against the HAZARD-class
/// containment guard (doyle, REQ-MANIFEST-5): it must be a plain relative path
/// that cannot escape its `strings/` dir — reject absolute paths and any `..`
/// (or prefix/root) component. Returns the joined absolute path on success.
fn contained_string_file(base: &Path, rel: &str) -> Result<PathBuf, String> {
    let relp = Path::new(rel);
    let escapes = relp.components().any(|c| {
        matches!(
            c,
            Component::ParentDir | Component::RootDir | Component::Prefix(_)
        )
    });
    if relp.as_os_str().is_empty() || relp.is_absolute() || escapes {
        return Err(format!(
            "pointer '{rel}' must be a relative path inside the strings/ dir \
             (no absolute paths, no `..` traversal)"
        ));
    }
    Ok(base.join(relp))
}

/// Read a file-pointer's contents at `get-string` time (REQ-MANIFEST-5). The
/// containment guard is re-applied here too (defense in depth — a pointer could
/// arrive via a local overlay that never passed register-time validation).
/// **Skip-diagnostics** on a contained-but-missing/unreadable file: emit a
/// diagnostic and return `None` (never a silent drop, never a hard error) —
/// mirrors the `[digest]` stance. A containment violation here is also a skip
/// (the loud refusal is the register-time guard's job).
fn read_string_file(base: &Path, rel: &str) -> Option<Value> {
    let full = match contained_string_file(base, rel) {
        Ok(p) => p,
        Err(why) => {
            eprintln!("STRINGS_FILE_SKIP: {why}");
            return None;
        }
    };
    match std::fs::read_to_string(&full) {
        Ok(contents) => Some(Value::String(contents)),
        Err(e) => {
            eprintln!(
                "STRINGS_FILE_SKIP: cannot read string file {} ({e})",
                full.display()
            );
            None
        }
    }
}

/// Walk every file pointer in a `[strings]` tree (recursing nested tables,
/// treating a single-key `{file=…}` node as a leaf), invoking `f` with each
/// relative path. The register-time validation traversal.
fn for_each_string_pointer(strings: &toml::Table, f: &mut impl FnMut(&str)) {
    for value in strings.values() {
        if let Some(rel) = profile::as_file_pointer(value) {
            f(rel);
        } else if let Some(nested) = value.as_table() {
            for_each_string_pointer(nested, f);
        }
    }
}

/// Validate (at register, fail-fast) that every file pointer in `strings` is
/// contained AND exists under `strings_dir`. A bad pointer refuses the whole
/// registration (records nothing) — the loud half of the contract.
fn validate_string_pointers(
    strings: &toml::Table,
    strings_dir: &Path,
) -> Result<(), RegistryError> {
    let mut bad: Option<String> = None;
    for_each_string_pointer(strings, &mut |rel| {
        if bad.is_some() {
            return;
        }
        match contained_string_file(strings_dir, rel) {
            Err(why) => bad = Some(why),
            Ok(full) if !full.exists() => {
                bad = Some(format!(
                    "pointer '{rel}' names a file that does not exist at {}",
                    full.display()
                ));
            }
            Ok(_) => {}
        }
    });
    match bad {
        Some(m) => Err(RegistryError::BadStringPointer(m)),
        None => Ok(()),
    }
}

/// Recursively copy `src` into `dst` (used to bring an adapter's shipped
/// `strings/` dir into the held copy at register, so file pointers resolve
/// post-copy and survive the source going away — Copy mode only).
fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> {
    std::fs::create_dir_all(dst)?;
    for entry in std::fs::read_dir(src)? {
        let entry = entry?;
        let from = entry.path();
        let to = dst.join(entry.file_name());
        if entry.file_type()?.is_dir() {
            copy_dir_all(&from, &to)?;
        } else {
            std::fs::copy(&from, &to)?;
        }
    }
    Ok(())
}

+// ───────────────────── File-backed [strings] (REQ-MANIFEST-5) ────────────────

+/// The directory that holds the adapter's LIVE manifest — and, as a sibling,

+/// its `strings/` aux dir. Copy mode: the held copy under `adapters/<name>/`;

+/// pointer mode: the live source tree (self-updates honored, same as the

+fn manifest_dir(adapters_dir: &Path, record: &AdapterRecord) -> PathBuf {

+        RecordMode::Copy => adapters_dir.join(&record.name),

+        RecordMode::Pointer => PathBuf::from(&record.source_dir),

+/// The adapter-shipped string-file storage `<manifest-dir>/strings/`

+/// (REQ-MANIFEST-5). Adapter updates overwrite this dir — never put user files

+/// here (a local profile's pointers resolve to its own user-owned dir instead).

+fn shipped_strings_dir(adapters_dir: &Path, record: &AdapterRecord) -> PathBuf {

+    manifest_dir(adapters_dir, record).join("strings")

+/// Validate a `[strings]` file-pointer relative path against the HAZARD-class

+/// containment guard (doyle, REQ-MANIFEST-5): it must be a plain relative path

+/// that cannot escape its `strings/` dir — reject absolute paths and any `..`

+/// (or prefix/root) component. Returns the joined absolute path on success.

+fn contained_string_file(base: &Path, rel: &str) -> Result<PathBuf, String> {

+    let escapes = relp.components().any(|c| {

+            Component::ParentDir | Component::RootDir | Component::Prefix(_)

+    if relp.as_os_str().is_empty() || relp.is_absolute() || escapes {

+            "pointer '{rel}' must be a relative path inside the strings/ dir \

+             (no absolute paths, no `..` traversal)"

+/// Read a file-pointer's contents at `get-string` time (REQ-MANIFEST-5). The

+/// containment guard is re-applied here too (defense in depth — a pointer could

+/// arrive via a local overlay that never passed register-time validation).

+/// **Skip-diagnostics** on a contained-but-missing/unreadable file: emit a

+/// diagnostic and return `None` (never a silent drop, never a hard error) —

+/// mirrors the `[digest]` stance. A containment violation here is also a skip

+/// (the loud refusal is the register-time guard's job).

+fn read_string_file(base: &Path, rel: &str) -> Option<Value> {

+    let full = match contained_string_file(base, rel) {

+            eprintln!("STRINGS_FILE_SKIP: {why}");

+    match std::fs::read_to_string(&full) {

+        Ok(contents) => Some(Value::String(contents)),

+                "STRINGS_FILE_SKIP: cannot read string file {} ({e})",

+/// Walk every file pointer in a `[strings]` tree (recursing nested tables,

+/// treating a single-key `{file=…}` node as a leaf), invoking `f` with each

+/// relative path. The register-time validation traversal.

+fn for_each_string_pointer(strings: &toml::Table, f: &mut impl FnMut(&str)) {

+        if let Some(rel) = profile::as_file_pointer(value) {

+        } else if let Some(nested) = value.as_table() {

+            for_each_string_pointer(nested, f);

+/// Validate (at register, fail-fast) that every file pointer in `strings` is

+/// contained AND exists under `strings_dir`. A bad pointer refuses the whole

+/// registration (records nothing) — the loud half of the contract.

+    let mut bad: Option<String> = None;

+    for_each_string_pointer(strings, &mut |rel| {

+        match contained_string_file(strings_dir, rel) {

+            Err(why) => bad = Some(why),

+            Ok(full) if !full.exists() => {

+                    "pointer '{rel}' names a file that does not exist at {}",

+        Some(m) => Err(RegistryError::BadStringPointer(m)),

+/// Recursively copy `src` into `dst` (used to bring an adapter's shipped

+/// `strings/` dir into the held copy at register, so file pointers resolve

+/// post-copy and survive the source going away — Copy mode only).

+fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> {

+    for entry in std::fs::read_dir(src)? {

+        let to = dst.join(entry.file_name());

+        if entry.file_type()?.is_dir() {

+            std::fs::copy(&from, &to)?;

/// Register (or re-register) the adapter whose manifest lives at `source`

/// (a directory containing `manifest.toml`, or the manifest path itself).

/// Validate-first: an invalid manifest records **nothing**. Mode falls out of


Wire validation + Copy-mode strings copy into `register()`:


    let source_dir = manifest_file
        .parent()
        .unwrap_or(Path::new("."))
        .to_string_lossy()
        .into_owned();

    let record = AdapterRecord {
        name: manifest.adapter.name.clone(),
        kind: manifest.adapter.kind,
        mode,
        source_dir,
        registered_at_ms: now_ms,
        active: true,
    };

    let dir = adapters_dir.join(&record.name);
    std::fs::create_dir_all(&dir)?;
    if mode == RecordMode::Copy {
        atomic_write_string(&manifest_copy_file(adapters_dir, &record.name), &toml_src)?;
    }
    save_record(adapters_dir, &record)?;
    Ok((record, manifest))

    let source_dir = manifest_file
        .parent()
        .unwrap_or(Path::new("."))
        .to_string_lossy()
        .into_owned();

    // Validate-first (REQ-MANIFEST-5): every `[strings]` file pointer — in the
    // base manifest and in each shipped profile's overlay — must be contained
    // and exist under the SOURCE `strings/` dir. A bad/escaping/missing pointer
    // refuses the whole registration (records nothing) — the loud half of the
    // contract; read-time stays skip-diagnostic.
    let src_strings_dir = Path::new(&source_dir).join("strings");
    validate_string_pointers(&manifest.strings, &src_strings_dir)?;
    for prof in manifest.profiles.values() {
        if let Some(strings) = prof.get("strings").and_then(|v| v.as_table()) {
            validate_string_pointers(strings, &src_strings_dir)?;
        }
    }

    let record = AdapterRecord {
        name: manifest.adapter.name.clone(),
        kind: manifest.adapter.kind,
        mode,
        source_dir: source_dir.clone(),
        registered_at_ms: now_ms,
        active: true,
    };

    let dir = adapters_dir.join(&record.name);
    std::fs::create_dir_all(&dir)?;
    if mode == RecordMode::Copy {
        atomic_write_string(&manifest_copy_file(adapters_dir, &record.name), &toml_src)?;
        // Bring the shipped strings/ dir into the held copy so pointers resolve
        // post-copy and survive the source going away (adapter updates overwrite
        // this — REQ-MANIFEST-5). Pointer mode reads strings/ live from source.
        if src_strings_dir.is_dir() {
            copy_dir_all(&src_strings_dir, &shipped_strings_dir(adapters_dir, &record))
                .map_err(RegistryError::Io)?;
        }
    }
    save_record(adapters_dir, &record)?;
    Ok((record, manifest))


    let source_dir = manifest_file
        .parent()
        .unwrap_or(Path::new("."))
        .to_string_lossy()
        .into_owned();

    let record = AdapterRecord {
        name: manifest.adapter.name.clone(),
        kind: manifest.adapter.kind,
        mode,
        source_dir,
        registered_at_ms: now_ms,
        active: true,
    };

    let dir = adapters_dir.join(&record.name);
    std::fs::create_dir_all(&dir)?;
    if mode == RecordMode::Copy {
        atomic_write_string(&manifest_copy_file(adapters_dir, &record.name), &toml_src)?;
    }
    save_record(adapters_dir, &record)?;
    Ok((record, manifest))

    let source_dir = manifest_file
        .parent()
        .unwrap_or(Path::new("."))
        .to_string_lossy()
        .into_owned();

    // Validate-first (REQ-MANIFEST-5): every `[strings]` file pointer — in the
    // base manifest and in each shipped profile's overlay — must be contained
    // and exist under the SOURCE `strings/` dir. A bad/escaping/missing pointer
    // refuses the whole registration (records nothing) — the loud half of the
    // contract; read-time stays skip-diagnostic.
    let src_strings_dir = Path::new(&source_dir).join("strings");
    validate_string_pointers(&manifest.strings, &src_strings_dir)?;
    for prof in manifest.profiles.values() {
        if let Some(strings) = prof.get("strings").and_then(|v| v.as_table()) {
            validate_string_pointers(strings, &src_strings_dir)?;
        }
    }

    let record = AdapterRecord {
        name: manifest.adapter.name.clone(),
        kind: manifest.adapter.kind,
        mode,
        source_dir: source_dir.clone(),
        registered_at_ms: now_ms,
        active: true,
    };

    let dir = adapters_dir.join(&record.name);
    std::fs::create_dir_all(&dir)?;
    if mode == RecordMode::Copy {
        atomic_write_string(&manifest_copy_file(adapters_dir, &record.name), &toml_src)?;
        // Bring the shipped strings/ dir into the held copy so pointers resolve
        // post-copy and survive the source going away (adapter updates overwrite
        // this — REQ-MANIFEST-5). Pointer mode reads strings/ live from source.
        if src_strings_dir.is_dir() {
            copy_dir_all(&src_strings_dir, &shipped_strings_dir(adapters_dir, &record))
                .map_err(RegistryError::Io)?;
        }
    }
    save_record(adapters_dir, &record)?;
    Ok((record, manifest))

+    // Validate-first (REQ-MANIFEST-5): every `[strings]` file pointer — in the

+    // base manifest and in each shipped profile's overlay — must be contained

+    // and exist under the SOURCE `strings/` dir. A bad/escaping/missing pointer

+    // refuses the whole registration (records nothing) — the loud half of the

+    // contract; read-time stays skip-diagnostic.

+    let src_strings_dir = Path::new(&source_dir).join("strings");

+    validate_string_pointers(&manifest.strings, &src_strings_dir)?;

+    for prof in manifest.profiles.values() {

+        if let Some(strings) = prof.get("strings").and_then(|v| v.as_table()) {

+            validate_string_pointers(strings, &src_strings_dir)?;

+        source_dir: source_dir.clone(),

+        // Bring the shipped strings/ dir into the held copy so pointers resolve

+        // post-copy and survive the source going away (adapter updates overwrite

+        // this — REQ-MANIFEST-5). Pointer mode reads strings/ live from source.

+            copy_dir_all(&src_strings_dir, &shipped_strings_dir(adapters_dir, &record))

+                .map_err(RegistryError::Io)?;


Update `get_string` to resolve file pointers with provenance (local-profile vs shipped base):


pub fn get_string(
    adapters_dir: &Path,
    option: &str,
    key_path: &str,
) -> Result<Option<Value>, RegistryError> {
    let (_, manifest) = resolve_option(adapters_dir, option)?;
    Ok(profile::string_at(&manifest.strings, key_path).cloned())
}

pub fn get_string(
    adapters_dir: &Path,
    option: &str,
    key_path: &str,
) -> Result<Option<Value>, RegistryError> {
    let (record, manifest) = resolve_option(adapters_dir, option)?;
    let Some(value) = profile::string_at(&manifest.strings, key_path) else {
        return Ok(None);
    };
    // A literal value reads as-is. A single-key `{file=…}` pointer resolves to
    // the file's contents (REQ-MANIFEST-5), lazily — so live edits reflect.
    let Some(rel) = profile::as_file_pointer(value) else {
        return Ok(Some(value.clone()));
    };
    // Provenance (the update-safety guard): if this key is supplied as a file
    // pointer by a LOCAL profile, resolve against its user-owned dir (survives
    // adapter updates); otherwise against the adapter-shipped `strings/` dir.
    let (adapter, profile_seg) = profile::split_option(option);
    let base = match profile_seg {
        Some(p) if local_profile_supplies_pointer(adapters_dir, adapter, p, key_path) => {
            local_profiles_dir(adapters_dir, adapter)
        }
        _ => shipped_strings_dir(adapters_dir, &record),
    };
    Ok(read_string_file(&base, rel))
}

/// Whether `<adapter>:<profile>`'s LOCAL overlay file itself supplies `key_path`
/// as a file pointer — the provenance test that routes a pointer to the
/// user-owned profiles dir instead of the adapter-shipped `strings/` dir
/// (REQ-MANIFEST-5 update-safety). A shipped profile (no local file) is `false`.
fn local_profile_supplies_pointer(
    adapters_dir: &Path,
    adapter: &str,
    profile: &str,
    key_path: &str,
) -> bool {
    let Ok(overlay) = load_local_overlay(adapters_dir, adapter, profile) else {
        return false;
    };
    overlay
        .as_table()
        .and_then(|t| t.get("strings"))
        .and_then(|s| s.as_table())
        .and_then(|st| profile::string_at(st, key_path))
        .map(|v| profile::as_file_pointer(v).is_some())
        .unwrap_or(false)
}


pub fn get_string(
    adapters_dir: &Path,
    option: &str,
    key_path: &str,
) -> Result<Option<Value>, RegistryError> {
    let (_, manifest) = resolve_option(adapters_dir, option)?;
    Ok(profile::string_at(&manifest.strings, key_path).cloned())
}

pub fn get_string(
    adapters_dir: &Path,
    option: &str,
    key_path: &str,
) -> Result<Option<Value>, RegistryError> {
    let (record, manifest) = resolve_option(adapters_dir, option)?;
    let Some(value) = profile::string_at(&manifest.strings, key_path) else {
        return Ok(None);
    };
    // A literal value reads as-is. A single-key `{file=…}` pointer resolves to
    // the file's contents (REQ-MANIFEST-5), lazily — so live edits reflect.
    let Some(rel) = profile::as_file_pointer(value) else {
        return Ok(Some(value.clone()));
    };
    // Provenance (the update-safety guard): if this key is supplied as a file
    // pointer by a LOCAL profile, resolve against its user-owned dir (survives
    // adapter updates); otherwise against the adapter-shipped `strings/` dir.
    let (adapter, profile_seg) = profile::split_option(option);
    let base = match profile_seg {
        Some(p) if local_profile_supplies_pointer(adapters_dir, adapter, p, key_path) => {
            local_profiles_dir(adapters_dir, adapter)
        }
        _ => shipped_strings_dir(adapters_dir, &record),
    };
    Ok(read_string_file(&base, rel))
}

/// Whether `<adapter>:<profile>`'s LOCAL overlay file itself supplies `key_path`
/// as a file pointer — the provenance test that routes a pointer to the
/// user-owned profiles dir instead of the adapter-shipped `strings/` dir
/// (REQ-MANIFEST-5 update-safety). A shipped profile (no local file) is `false`.
fn local_profile_supplies_pointer(
    adapters_dir: &Path,
    adapter: &str,
    profile: &str,
    key_path: &str,
) -> bool {
    let Ok(overlay) = load_local_overlay(adapters_dir, adapter, profile) else {
        return false;
    };
    overlay
        .as_table()
        .and_then(|t| t.get("strings"))
        .and_then(|s| s.as_table())
        .and_then(|st| profile::string_at(st, key_path))
        .map(|v| profile::as_file_pointer(v).is_some())
        .unwrap_or(false)
}

-    let (_, manifest) = resolve_option(adapters_dir, option)?;

-    Ok(profile::string_at(&manifest.strings, key_path).cloned())

+    let (record, manifest) = resolve_option(adapters_dir, option)?;

+    let Some(value) = profile::string_at(&manifest.strings, key_path) else {

+    // A literal value reads as-is. A single-key `{file=…}` pointer resolves to

+    // the file's contents (REQ-MANIFEST-5), lazily — so live edits reflect.

+    let Some(rel) = profile::as_file_pointer(value) else {

+        return Ok(Some(value.clone()));

+    // Provenance (the update-safety guard): if this key is supplied as a file

+    // pointer by a LOCAL profile, resolve against its user-owned dir (survives

+    // adapter updates); otherwise against the adapter-shipped `strings/` dir.

+    let (adapter, profile_seg) = profile::split_option(option);

+        Some(p) if local_profile_supplies_pointer(adapters_dir, adapter, p, key_path) => {

+            local_profiles_dir(adapters_dir, adapter)

+        _ => shipped_strings_dir(adapters_dir, &record),

+/// Whether `<adapter>:<profile>`'s LOCAL overlay file itself supplies `key_path`

+/// as a file pointer — the provenance test that routes a pointer to the

+/// user-owned profiles dir instead of the adapter-shipped `strings/` dir

+/// (REQ-MANIFEST-5 update-safety). A shipped profile (no local file) is `false`.

+    let Ok(overlay) = load_local_overlay(adapters_dir, adapter, profile) else {

+        .and_then(|t| t.get("strings"))

+        .and_then(|st| profile::string_at(st, key_path))

+        .map(|v| profile::as_file_pointer(v).is_some())

/// Set a `[strings]` dot-path on a **local** profile's overlay — the

/// `spt adapter set-string` sugar. Edits only the node-local overlay file

/// (`profiles/<profile>.toml`); a shipped profile name is refused (adapter-owned,


Core T3.1 impl done. Compile-check spt-runtime:


Bash

cargo check -p spt-runtime 2>&1 | tail -30

cargo check spt-runtime


Command running in background with ID: bsio91eow. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bsio91eow.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bsio91eow</task-id>
<tool-use-id>toolu_01QMh3d3zUKZNrsa56gw9hMQ</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bsio91eow.output</output-file>
<status>completed</status>
<summary>Background command "cargo check spt-runtime" completed (exit code 0)</summary>
</task-notification>


While it compiles, look at the registry test harness to model the new tests on:


920
921	        assert!(matches!(
922	            resolve_option_in(&set, &adapters, "ghost:work"),
923	            Err(RegistryError::NotRegistered(n)) if n == "ghost"
924	        ));
925	    }
926
927	    // [unit->REQ-MANIFEST-2] a local profile survives adapter re-registration
928	    // (the manifest copy is overwritten; the user's profiles/ dir is not).
929	    #[test]
930	    fn local_profile_survives_readd() {
931	        let tmp = tempfile::tempdir().unwrap();
932	        let adapters = registered_shell(tmp.path());
933	        create_local_profile(&adapters, "mock-shell", "work", "[shell]\nrequire_approval = \"always\"\n")
934	            .unwrap();
935
936	        // Re-register the adapter (adapter update / re-add).
937	        let src = seed_source(tmp.path(), "ms-src2", SHELL_COPY_PROFILED);
938	        register(&adapters, &src, 2000).unwrap();
939
940	        assert!(
941	            resolve_option(&adapters, "mock-shell:work").is_ok(),
942	            "local profile still resolvable after re-add"
943	        );
944	    }
945
946	    // [unit->REQ-MANIFEST-2] create-time guards: shadow a shipped name, loosen
947	    // the floor, or an invalid name — each refused, nothing written.
948	    #[test]
949	    fn create_local_profile_guards() {
950	        let tmp = tempfile::tempdir().unwrap();
951	        let adapters = registered_shell(tmp.path());
952
953	        // Shadow a shipped profile.
954	        assert!(matches!(
955	            create_local_profile(&adapters, "mock-shell", "locked", "[shell]\nrequire_approval = \"always\"\n"),
956	            Err(RegistryError::ProfileShadowsShipped(n)) if n == "locked"
957	        ));
958
959	        // Loosen the consent floor (remembered -> none).
960	        assert!(matches!(
961	            create_local_profile(&adapters, "mock-shell", "loose", "[shell]\nrequire_approval = \"none\"\n"),
962	            Err(RegistryError::Manifest(_))
963	        ));
964
965	        // Invalid name (contains the address separator).
966	        assert!(matches!(
967	            create_local_profile(&adapters, "mock-shell", "a:b", "[shell]\nrequire_approval = \"always\"\n"),
968	            Err(RegistryError::InvalidProfileName(_))
969	        ));
970
971	        assert!(
972	            local_profile_names(&adapters, "mock-shell").is_empty(),
973	            "no file written for any refused create"
974	        );
975	    }
976
977	    // [unit->REQ-MANIFEST-2] delete-profile: shipped is immutable, local is
978	    // removed, a missing local errors.
979	    #[test]
980	    fn delete_local_profile_rules() {
981	        let tmp = tempfile::tempdir().unwrap();
982	        let adapters = registered_shell(tmp.path());
983	        create_local_profile(&adapters, "mock-shell", "work", "[shell]\nrequire_approval = \"always\"\n")
984	            .unwrap();
985
986	        assert!(matches!(
987	            delete_local_profile(&adapters, "mock-shell", "locked"),
988	            Err(RegistryError::ShippedProfileImmutable(_))
989	        ));
990	        assert!(delete_local_profile(&adapters, "mock-shell", "work").is_ok());
991	        assert!(matches!(
992	            resolve_option(&adapters, "mock-shell:work"),
993	            Err(RegistryError::ProfileNotFound(_))
994	        ));
995	        assert!(matches!(
996	            delete_local_profile(&adapters, "mock-shell", "ghost"),
997	            Err(RegistryError::ProfileNotFound(_))
998	        ));
999	    }

        assert!(matches!(
            resolve_option_in(&set, &adapters, "ghost:work"),
            Err(RegistryError::NotRegistered(n)) if n == "ghost"
        ));
    }

    // [unit->REQ-MANIFEST-2] a local profile survives adapter re-registration
    // (the manifest copy is overwritten; the user's profiles/ dir is not).
    #[test]
    fn local_profile_survives_readd() {
        let tmp = tempfile::tempdir().unwrap();
        let adapters = registered_shell(tmp.path());
        create_local_profile(&adapters, "mock-shell", "work", "[shell]\nrequire_approval = \"always\"\n")
            .unwrap();

        // Re-register the adapter (adapter update / re-add).
        let src = seed_source(tmp.path(), "ms-src2", SHELL_COPY_PROFILED);
        register(&adapters, &src, 2000).unwrap();

        assert!(
            resolve_option(&adapters, "mock-shell:work").is_ok(),
            "local profile still resolvable after re-add"
        );
    }

    // [unit->REQ-MANIFEST-2] create-time guards: shadow a shipped name, loosen
    // the floor, or an invalid name — each refused, nothing written.
    #[test]
    fn create_local_profile_guards() {
        let tmp = tempfile::tempdir().unwrap();
        let adapters = registered_shell(tmp.path());

        // Shadow a shipped profile.
        assert!(matches!(
            create_local_profile(&adapters, "mock-shell", "locked", "[shell]\nrequire_approval = \"always\"\n"),
            Err(RegistryError::ProfileShadowsShipped(n)) if n == "locked"
        ));

        // Loosen the consent floor (remembered -> none).
        assert!(matches!(
            create_local_profile(&adapters, "mock-shell", "loose", "[shell]\nrequire_approval = \"none\"\n"),
            Err(RegistryError::Manifest(_))
        ));

        // Invalid name (contains the address separator).
        assert!(matches!(
            create_local_profile(&adapters, "mock-shell", "a:b", "[shell]\nrequire_approval = \"always\"\n"),
            Err(RegistryError::InvalidProfileName(_))
        ));

        assert!(
            local_profile_names(&adapters, "mock-shell").is_empty(),
            "no file written for any refused create"
        );
    }

    // [unit->REQ-MANIFEST-2] delete-profile: shipped is immutable, local is
    // removed, a missing local errors.
    #[test]
    fn delete_local_profile_rules() {
        let tmp = tempfile::tempdir().unwrap();
        let adapters = registered_shell(tmp.path());
        create_local_profile(&adapters, "mock-shell", "work", "[shell]\nrequire_approval = \"always\"\n")
            .unwrap();

        assert!(matches!(
            delete_local_profile(&adapters, "mock-shell", "locked"),
            Err(RegistryError::ShippedProfileImmutable(_))
        ));
        assert!(delete_local_profile(&adapters, "mock-shell", "work").is_ok());
        assert!(matches!(
            resolve_option(&adapters, "mock-shell:work"),
            Err(RegistryError::ProfileNotFound(_))
        ));
        assert!(matches!(
            delete_local_profile(&adapters, "mock-shell", "ghost"),
            Err(RegistryError::ProfileNotFound(_))
        ));
    }


Prompt:
<task-notification>
<task-id>bsio91eow</task-id>
<tool-use-id>toolu_01QMh3d3zUKZNrsa56gw9hMQ</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\bsio91eow.output</output-file>
<status>completed</status>
<summary>Background command "cargo check spt-runtime" completed (exit code 0)</summary>
</task-notification>


1	    Checking spt-proto v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-proto)
2	    Checking spt-store v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
3	    Checking spt-msg v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
4	    Checking spt-runtime v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
5	    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.43s
6

    Checking spt-proto v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-proto)
    Checking spt-store v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
    Checking spt-msg v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
    Checking spt-runtime v0.6.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.43s


Compiles clean. Read the test-module helpers (seed_source, registered_shell, consts) to model the new tests:


820	        assert!(!adapters.join("bad").exists(), "nothing recorded");
821	        assert!(registered(&adapters).is_empty());
822	    }
823
824	    // [unit->REQ-INSTALL-4] remove is a soft-deregister: hidden from the
825	    // active set, record + copy retained (live instances keep running), the
826	    // manifest's uninstall template surfaced to the caller; re-add
827	    // re-activates.
828	    #[test]
829	    fn deregister_is_soft_and_readd_reactivates() {
830	        let tmp = tempfile::tempdir().unwrap();
831	        let adapters = tmp.path().join("adapters");
832	        let shell_src = seed_source(tmp.path(), "s-src", SHELL_DELEGATED);
833	        register(&adapters, &shell_src, 1000).unwrap();
834
835	        let uninstall = deregister(&adapters, "mock-shell").unwrap();
836	        assert_eq!(uninstall.as_deref(), Some("mock-shell-uninstaller --purge"));
837	        assert!(
838	            registered(&adapters).is_empty(),
839	            "hidden from the active set"
840	        );
841	        let rec = load_record(&adapters, "mock-shell").unwrap();
842	        assert!(!rec.active, "record retained, inactive");
843	        // resolve_option honors the active-only view: a soft-deregistered
844	        // adapter refuses new resolution (spawn/drive/relaunch).
845	        assert!(matches!(
846	            resolve_option(&adapters, "mock-shell"),
847	            Err(RegistryError::NotRegistered(_))
848	        ));
849
850	        // Unknown name errs; double-deregister is idempotent.
851	        assert!(matches!(
852	            deregister(&adapters, "ghost"),
853	            Err(RegistryError::NotRegistered(_))
854	        ));
855	        assert!(deregister(&adapters, "mock-shell").is_ok());
856
857	        let (re, _) = register(&adapters, &shell_src, 2000).unwrap();
858	        assert!(
859	            re.active && re.registered_at_ms == 2000,
860	            "re-add re-activates + refreshes"
861	        );
862	        assert_eq!(registered(&adapters).len(), 1);
863	    }
864
865	    /// Register the profiled shell adapter into a fresh `adapters/` dir.
866	    fn registered_shell(tmp: &Path) -> PathBuf {
867	        let adapters = tmp.join("adapters");
868	        let src = seed_source(tmp, "ms-src", SHELL_COPY_PROFILED);
869	        register(&adapters, &src, 1000).unwrap();
870	        adapters
871	    }
872
873	    fn approval(m: &Manifest) -> crate::manifest::ShellApproval {
874	        m.shell.as_ref().unwrap().require_approval
875	    }
876
877	    // [unit->REQ-MANIFEST-2] resolve_option: bare name = parent unmodified;
878	    // a shipped profile and a local profile each resolve to the merged view.
879	    #[test]
880	    fn resolve_option_bare_shipped_and_local() {
881	        use crate::manifest::ShellApproval;
882	        let tmp = tempfile::tempdir().unwrap();
883	        let adapters = registered_shell(tmp.path());
884
885	        // Bare = parent.
886	        let (_, base) = resolve_option(&adapters, "mock-shell").unwrap();
887	        assert_eq!(approval(&base), ShellApproval::Remembered, "bare = parent");
888
889	        // Shipped profile.
890	        let (_, shipped) = resolve_option(&adapters, "mock-shell:locked").unwrap();
891	        assert_eq!(approval(&shipped), ShellApproval::Always, "shipped resolves");
892
893	        // Local profile (tightens further-or-equal — always over remembered).
894	        create_local_profile(&adapters, "mock-shell", "work", "[shell]\nrequire_approval = \"always\"\n")
895	            .unwrap();
896	        let (_, local) = resolve_option(&adapters, "mock-shell:work").unwrap();
897	        assert_eq!(approval(&local), ShellApproval::Always, "local resolves");
898	        assert_eq!(local_profile_names(&adapters, "mock-shell"), vec!["work"]);
899	    }
900
901	    // [unit->REQ-MANIFEST-2] resolve_option_in (the daemon seam) resolves the
902	    // same merged view as resolve_option against a **preloaded** registered
903	    // slice — bare = parent, shipped + local both overlay; an unregistered
904	    // parent is NotRegistered.
905	    #[test]
906	    fn resolve_option_in_matches_disk() {
907	        use crate::manifest::ShellApproval;
908	        let tmp = tempfile::tempdir().unwrap();
909	        let adapters = registered_shell(tmp.path());
910	        create_local_profile(&adapters, "mock-shell", "work", "[shell]\nrequire_approval = \"always\"\n")
911	            .unwrap();
912	        let set = registered(&adapters);
913
914	        let bare = resolve_option_in(&set, &adapters, "mock-shell").unwrap();
915	        assert_eq!(approval(&bare), ShellApproval::Remembered, "bare = parent");
916	        let shipped = resolve_option_in(&set, &adapters, "mock-shell:locked").unwrap();
917	        assert_eq!(approval(&shipped), ShellApproval::Always, "shipped overlays");
918	        let local = resolve_option_in(&set, &adapters, "mock-shell:work").unwrap();
919	        assert_eq!(approval(&local), ShellApproval::Always, "local overlays");

        assert!(!adapters.join("bad").exists(), "nothing recorded");
        assert!(registered(&adapters).is_empty());
    }

    // [unit->REQ-INSTALL-4] remove is a soft-deregister: hidden from the
    // active set, record + copy retained (live instances keep running), the
    // manifest's uninstall template surfaced to the caller; re-add
    // re-activates.
    #[test]
    fn deregister_is_soft_and_readd_reactivates() {
        let tmp = tempfile::tempdir().unwrap();
        let adapters = tmp.path().join("adapters");
        let shell_src = seed_source(tmp.path(), "s-src", SHELL_DELEGATED);
        register(&adapters, &shell_src, 1000).unwrap();

        let uninstall = deregister(&adapters, "mock-shell").unwrap();
        assert_eq!(uninstall.as_deref(), Some("mock-shell-uninstaller --purge"));
        assert!(
            registered(&adapters).is_empty(),
            "hidden from the active set"
        );
        let rec = load_record(&adapters, "mock-shell").unwrap();
        assert!(!rec.active, "record retained, inactive");
        // resolve_option honors the active-only view: a soft-deregistered
        // adapter refuses new resolution (spawn/drive/relaunch).
        assert!(matches!(
            resolve_option(&adapters, "mock-shell"),
            Err(RegistryError::NotRegistered(_))
        ));

        // Unknown name errs; double-deregister is idempotent.
        assert!(matches!(
            deregister(&adapters, "ghost"),
            Err(RegistryError::NotRegistered(_))
        ));
        assert!(deregister(&adapters, "mock-shell").is_ok());

        let (re, _) = register(&adapters, &shell_src, 2000).unwrap();
        assert!(
            re.active && re.registered_at_ms == 2000,
            "re-add re-activates + refreshes"
        );
        assert_eq!(registered(&adapters).len(), 1);
    }

    /// Register the profiled shell adapter into a fresh `adapters/` dir.
    fn registered_shell(tmp: &Path) -> PathBuf {
        let adapters = tmp.join("adapters");
        let src = seed_source(tmp, "ms-src", SHELL_COPY_PROFILED);
        register(&adapters, &src, 1000).unwrap();
        adapters
    }

    fn approval(m: &Manifest) -> crate::manifest::ShellApproval {
        m.shell.as_ref().unwrap().require_approval
    }

    // [unit->REQ-MANIFEST-2] resolve_option: bare name = parent unmodified;
    // a shipped profile and a local profile each resolve to the merged view.
    #[test]
    fn resolve_option_bare_shipped_and_local() {
        use crate::manifest::ShellApproval;
        let tmp = tempfile::tempdir().unwrap();
        let adapters = registered_shell(tmp.path());

        // Bare = parent.
        let (_, base) = resolve_option(&adapters, "mock-shell").unwrap();
        assert_eq!(approval(&base), ShellApproval::Remembered, "bare = parent");

        // Shipped profile.
        let (_, shipped) = resolve_option(&adapters, "mock-shell:locked").unwrap();
        assert_eq!(approval(&shipped), ShellApproval::Always, "shipped resolves");

        // Local profile (tightens further-or-equal — always over remembered).
        create_local_profile(&adapters, "mock-shell", "work", "[shell]\nrequire_approval = \"always\"\n")
            .unwrap();
        let (_, local) = resolve_option(&adapters, "mock-shell:work").unwrap();
        assert_eq!(approval(&local), ShellApproval::Always, "local resolves");
        assert_eq!(local_profile_names(&adapters, "mock-shell"), vec!["work"]);
    }

    // [unit->REQ-MANIFEST-2] resolve_option_in (the daemon seam) resolves the
    // same merged view as resolve_option against a **preloaded** registered
    // slice — bare = parent, shipped + local both overlay; an unregistered
    // parent is NotRegistered.
    #[test]
    fn resolve_option_in_matches_disk() {
        use crate::manifest::ShellApproval;
        let tmp = tempfile::tempdir().unwrap();
        let adapters = registered_shell(tmp.path());
        create_local_profile(&adapters, "mock-shell", "work", "[shell]\nrequire_approval = \"always\"\n")
            .unwrap();
        let set = registered(&adapters);

        let bare = resolve_option_in(&set, &adapters, "mock-shell").unwrap();
        assert_eq!(approval(&bare), ShellApproval::Remembered, "bare = parent");
        let shipped = resolve_option_in(&set, &adapters, "mock-shell:locked").unwrap();
        assert_eq!(approval(&shipped), ShellApproval::Always, "shipped overlays");
        let local = resolve_option_in(&set, &adapters, "mock-shell:work").unwrap();
        assert_eq!(approval(&local), ShellApproval::Always, "local overlays");


Read seed_source + the manifest consts (top of test module):


740	[adapter]
741	name = "mock-shell"
742	kind = "shell"
743	version = "1.0.0"
744	min_spt_core_version = "0.0.1"
745
746	[update]
747	avenue = "delegated"
748	command = "mock-shell-updater"
749	self_verifies = true
750	uninstall = "mock-shell-uninstaller --purge"
751
752	[shell]
753	spawn = "mock-shell --link {link_token}"
754	"#;
755
756	    fn seed_source(dir: &Path, name: &str, toml_src: &str) -> PathBuf {
757	        let src = dir.join(name);
758	        std::fs::create_dir_all(&src).unwrap();
759	        std::fs::write(src.join("manifest.toml"), toml_src).unwrap();
760	        src
761	    }
762
763	    // [unit->REQ-INSTALL-4] register validates-first and records by avenue:
764	    // an avenue-less harness is copied; a delegated shell is a pointer whose
765	    // manifest reads live from the source tree. One dir serves both kinds.
766	    #[test]
767	    fn register_copy_vs_pointer_by_avenue() {
768	        let tmp = tempfile::tempdir().unwrap();
769	        let adapters = tmp.path().join("adapters");
770	        let harness_src = seed_source(tmp.path(), "h-src", HARNESS_COPY);
771	        let shell_src = seed_source(tmp.path(), "s-src", SHELL_DELEGATED);
772
773	        let (h, hm) = register(&adapters, &harness_src, 1000).unwrap();
774	        assert_eq!((h.mode, h.kind), (RecordMode::Copy, AdapterKind::Harness));
775	        assert_eq!(hm.adapter.name, "mock-harness");
776	        assert!(
777	            adapters.join("mock-harness").join("manifest.toml").exists(),
778	            "copy held"
779	        );
780
781	        let (s, _) = register(&adapters, &shell_src, 2000).unwrap();
782	        assert_eq!((s.mode, s.kind), (RecordMode::Pointer, AdapterKind::Shell));
783	        assert!(
784	            !adapters.join("mock-shell").join("manifest.toml").exists(),
785	            "no copy"
786	        );
787	        // Pointer reads live: mutate the source, the loaded manifest follows.
788	        std::fs::write(
789	            shell_src.join("manifest.toml"),
790	            SHELL_DELEGATED.replace("1.0.0", "1.1.0"),
791	        )
792	        .unwrap();
793	        let live = load_manifest(&adapters, &s).unwrap();
794	        assert_eq!(live.adapter.version, "1.1.0");
795
796	        let set = registered(&adapters);
797	        assert_eq!(
798	            set.iter().map(|(r, _)| r.name.as_str()).collect::<Vec<_>>(),
799	            vec!["mock-harness", "mock-shell"],
800	            "stable name order"
801	        );
802	    }
803
804	    // [unit->REQ-INSTALL-4] an invalid manifest records nothing — the
805	    // validate-first rule (manifest-first, then install).
806	    #[test]
807	    fn invalid_manifest_records_nothing() {
808	        let tmp = tempfile::tempdir().unwrap();
809	        let adapters = tmp.path().join("adapters");
810	        // kind = "shell" without a [shell] body fails validation.
811	        let bad = seed_source(
812	            tmp.path(),
813	            "bad-src",
814	            "[adapter]\nname = \"bad\"\nkind = \"shell\"\nversion = \"1\"\nmin_spt_core_version = \"0\"\n",
815	        );
816	        assert!(matches!(
817	            register(&adapters, &bad, 0),
818	            Err(RegistryError::Manifest(_))
819	        ));
820	        assert!(!adapters.join("bad").exists(), "nothing recorded");
821	        assert!(registered(&adapters).is_empty());
822	    }
823
824	    // [unit->REQ-INSTALL-4] remove is a soft-deregister: hidden from the
825	    // active set, record + copy retained (live instances keep running), the
826	    // manifest's uninstall template surfaced to the caller; re-add
827	    // re-activates.
828	    #[test]
829	    fn deregister_is_soft_and_readd_reactivates() {

[adapter]
name = "mock-shell"
kind = "shell"
version = "1.0.0"
min_spt_core_version = "0.0.1"

[update]
avenue = "delegated"
command = "mock-shell-updater"
self_verifies = true
uninstall = "mock-shell-uninstaller --purge"

[shell]
spawn = "mock-shell --link {link_token}"
"#;

    fn seed_source(dir: &Path, name: &str, toml_src: &str) -> PathBuf {
        let src = dir.join(name);
        std::fs::create_dir_all(&src).unwrap();
        std::fs::write(src.join("manifest.toml"), toml_src).unwrap();
        src
    }

    // [unit->REQ-INSTALL-4] register validates-first and records by avenue:
    // an avenue-less harness is copied; a delegated shell is a pointer whose
    // manifest reads live from the source tree. One dir serves both kinds.
    #[test]
    fn register_copy_vs_pointer_by_avenue() {
        let tmp = tempfile::tempdir().unwrap();
        let adapters = tmp.path().join("adapters");
        let harness_src = seed_source(tmp.path(), "h-src", HARNESS_COPY);
        let shell_src = seed_source(tmp.path(), "s-src", SHELL_DELEGATED);

        let (h, hm) = register(&adapters, &harness_src, 1000).unwrap();
        assert_eq!((h.mode, h.kind), (RecordMode::Copy, AdapterKind::Harness));
        assert_eq!(hm.adapter.name, "mock-harness");
        assert!(
            adapters.join("mock-harness").join("manifest.toml").exists(),
            "copy held"
        );

        let (s, _) = register(&adapters, &shell_src, 2000).unwrap();
        assert_eq!((s.mode, s.kind), (RecordMode::Pointer, AdapterKind::Shell));
        assert!(
            !adapters.join("mock-shell").join("manifest.toml").exists(),
            "no copy"
        );
        // Pointer reads live: mutate the source, the loaded manifest follows.
        std::fs::write(
            shell_src.join("manifest.toml"),
            SHELL_DELEGATED.replace("1.0.0", "1.1.0"),
        )
        .unwrap();
        let live = load_manifest(&adapters, &s).unwrap();
        assert_eq!(live.adapter.version, "1.1.0");

        let set = registered(&adapters);
        assert_eq!(
            set.iter().map(|(r, _)| r.name.as_str()).collect::<Vec<_>>(),
            vec!["mock-harness", "mock-shell"],
            "stable name order"
        );
    }

    // [unit->REQ-INSTALL-4] an invalid manifest records nothing — the
    // validate-first rule (manifest-first, then install).
    #[test]
    fn invalid_manifest_records_nothing() {
        let tmp = tempfile::tempdir().unwrap();
        let adapters = tmp.path().join("adapters");
        // kind = "shell" without a [shell] body fails validation.
        let bad = seed_source(
            tmp.path(),
            "bad-src",
            "[adapter]\nname = \"bad\"\nkind = \"shell\"\nversion = \"1\"\nmin_spt_core_version = \"0\"\n",
        );
        assert!(matches!(
            register(&adapters, &bad, 0),
            Err(RegistryError::Manifest(_))
        ));
        assert!(!adapters.join("bad").exists(), "nothing recorded");
        assert!(registered(&adapters).is_empty());
    }

    // [unit->REQ-INSTALL-4] remove is a soft-deregister: hidden from the
    // active set, record + copy retained (live instances keep running), the
    // manifest's uninstall template surfaced to the caller; re-add
    // re-activates.
    #[test]
    fn deregister_is_soft_and_readd_reactivates() {


Harness understood. Write tests. First the `as_file_pointer` single-key unit in profile.rs:


360	    /// is a loosen).
361	    // [unit->REQ-MANIFEST-2]
362	    #[test]
363	    fn tighten_only_refuses_loosened_over_cap() {
364	        let parent = Manifest::from_toml_str(PROFILED_SHELL).expect("parent parses");
365	        let err = resolve(&parent, "caploose").expect_err("reject -> approve must be refused");
366	        assert!(matches!(err, ManifestError::Validation(m) if m.contains("over_cap")));
367	    }
368
369	    /// `[strings]` dot-path read/write: nested traversal, missing key, and the
370	    /// write building intermediate tables; a non-table mid-path errs.
371	    // [unit->REQ-MANIFEST-3]
372	    #[test]
373	    fn string_dot_path_read_write() {
374	        let mut t: toml::Table = toml::from_str(
375	            "greeting = \"hi\"\n[hook]\nadditionalContext = \"ctx\"\n",
376	        )
377	        .unwrap();
378
379	        // Read: top-level leaf, nested leaf, and a miss.
380	        assert_eq!(string_at(&t, "greeting").and_then(|v| v.as_str()), Some("hi"));
381	        assert_eq!(
382	            string_at(&t, "hook.additionalContext").and_then(|v| v.as_str()),
383	            Some("ctx")
384	        );
385	        assert!(string_at(&t, "hook.missing").is_none());
386	        assert!(string_at(&t, "nope").is_none());
387	        // A non-final segment that isn't a table is a miss, not a panic.
388	        assert!(string_at(&t, "greeting.x").is_none());
389
390	        // Write: overwrite a leaf, and create an intermediate table.
391	        set_string_at(&mut t, "greeting", Value::String("yo".into())).unwrap();
392	        set_string_at(&mut t, "deep.nest.key", Value::String("v".into())).unwrap();
393	        assert_eq!(string_at(&t, "greeting").and_then(|v| v.as_str()), Some("yo"));
394	        assert_eq!(string_at(&t, "deep.nest.key").and_then(|v| v.as_str()), Some("v"));
395	        // Writing through an existing non-table leaf errs (no clobber).
396	        assert!(set_string_at(&mut t, "greeting.x", Value::String("z".into())).is_err());
397	        // An empty segment errs.
398	        assert!(set_string_at(&mut t, "a..b", Value::String("z".into())).is_err());
399	    }
400
401	    /// A profile overlays `[strings]` like any section (leaf-replace): the
402	    /// profile's key wins, the parent's untouched string survives.
403	    // [unit->REQ-MANIFEST-3]
404	    #[test]

    /// is a loosen).
    // [unit->REQ-MANIFEST-2]
    #[test]
    fn tighten_only_refuses_loosened_over_cap() {
        let parent = Manifest::from_toml_str(PROFILED_SHELL).expect("parent parses");
        let err = resolve(&parent, "caploose").expect_err("reject -> approve must be refused");
        assert!(matches!(err, ManifestError::Validation(m) if m.contains("over_cap")));
    }

    /// `[strings]` dot-path read/write: nested traversal, missing key, and the
    /// write building intermediate tables; a non-table mid-path errs.
    // [unit->REQ-MANIFEST-3]
    #[test]
    fn string_dot_path_read_write() {
        let mut t: toml::Table = toml::from_str(
            "greeting = \"hi\"\n[hook]\nadditionalContext = \"ctx\"\n",
        )
        .unwrap();

        // Read: top-level leaf, nested leaf, and a miss.
        assert_eq!(string_at(&t, "greeting").and_then(|v| v.as_str()), Some("hi"));
        assert_eq!(
            string_at(&t, "hook.additionalContext").and_then(|v| v.as_str()),
            Some("ctx")
        );
        assert!(string_at(&t, "hook.missing").is_none());
        assert!(string_at(&t, "nope").is_none());
        // A non-final segment that isn't a table is a miss, not a panic.
        assert!(string_at(&t, "greeting.x").is_none());

        // Write: overwrite a leaf, and create an intermediate table.
        set_string_at(&mut t, "greeting", Value::String("yo".into())).unwrap();
        set_string_at(&mut t, "deep.nest.key", Value::String("v".into())).unwrap();
        assert_eq!(string_at(&t, "greeting").and_then(|v| v.as_str()), Some("yo"));
        assert_eq!(string_at(&t, "deep.nest.key").and_then(|v| v.as_str()), Some("v"));
        // Writing through an existing non-table leaf errs (no clobber).
        assert!(set_string_at(&mut t, "greeting.x", Value::String("z".into())).is_err());
        // An empty segment errs.
        assert!(set_string_at(&mut t, "a..b", Value::String("z".into())).is_err());
    }

    /// A profile overlays `[strings]` like any section (leaf-replace): the
    /// profile's key wins, the parent's untouched string survives.
    // [unit->REQ-MANIFEST-3]
    #[test]