fix(soul): address review issues in feat/layer-composition
Neuron Soul CI / build (pull_request) Failing after 6m5s

- Add stub implementations of safety.el, stewardship.el, and imprint.el
  with their .elh headers so the branch compiles without the dependency
  branches (feat/layer-safety, feat/layer-stewardship, feat/layer-imprint).
  Each stub documents the layer contract it must satisfy when replaced.

- Fix GET /api/chat bypass: update the GET branch in handle_request to
  call layered_cycle() consistently with the POST branch, rather than
  calling handle_chat() directly and skipping the consciousness stack.

- Export layered_cycle() from soul.elh (and dist/soul.elh) so routes.el
  can resolve the symbol via the header import.

- Fix steward_action else branch: add explicit handling for "block"
  (returns safe refusal immediately, skips L3) and "redirect" (uses
  redirect_to field). Unknown actions now log a warning and fall back to
  the screened input rather than silently passing an empty string to
  imprint_respond().

- Document hard_bell path: clarify that omitting auto_persist/history
  update is intentional security isolation, and document the safety_validate
  second-param sentinel contract ("hard_bell" vs screen_action).
This commit is contained in:
2026-06-11 11:47:45 -05:00
parent f52d5bd9ae
commit bebf1f8c86
10 changed files with 137 additions and 3 deletions
+32 -2
View File
@@ -242,7 +242,17 @@ fn layered_cycle(raw_input: String) -> String {
let screen_result: String = safety_screen(raw_input, history)
let screen_action: String = json_get(screen_result, "action")
// Hard bell: bypass all upper layers, log and escalate
// Hard bell: bypass all upper layers, log and escalate.
// Intentionally does NOT update conversation_history or call auto_persist():
// hard bell events are security-sensitive and must not appear in engram conversation
// history where they could leak context to subsequent turns. They are persisted
// separately by safety_log_bell() into the Episodic tier with restricted labels.
//
// safety_validate second param: when screen_action is "hard_bell", safety_validate
// receives the sentinel string "hard_bell" (not a normal screen action). The safety
// layer contract requires it to return a fixed refusal regardless of the output arg.
// On the normal path, safety_validate receives the original screen_action ("pass")
// so it can apply action-specific post-output checks.
if str_eq(screen_action, "hard_bell") {
safety_log_bell("hard", json_get(screen_result, "reason"), str_slice(raw_input, 0, 80))
return safety_validate("", "hard_bell")
@@ -253,10 +263,30 @@ fn layered_cycle(raw_input: String) -> String {
let imprint_id: String = imprint_current()
let steward_result: String = steward_align(screened, imprint_id)
let steward_action: String = json_get(steward_result, "action")
// "block": stewardship has determined this input should not proceed.
// Return the steward's content directly (a safe refusal) and skip L3.
if str_eq(steward_action, "block") {
let block_msg: String = json_get(steward_result, "content")
let eff_block: String = if str_eq(block_msg, "") { "I'm not able to help with that." } else { block_msg }
return safety_validate(eff_block, screen_action)
}
// "redirect": stewardship steers toward aligned territory.
// redirect_to holds the reframed prompt; fall through to L3 with it.
// "pass": content holds the (possibly lightly reframed) input ready for L3.
// Unknown actions: treat as pass, log a warning, use content field.
let guided: String = if str_eq(steward_action, "pass") {
json_get(steward_result, "content")
} else {
json_get(steward_result, "redirect_to")
// redirect or unknown prefer redirect_to, fall back to content
let redir: String = json_get(steward_result, "redirect_to")
let alt: String = json_get(steward_result, "content")
let chosen: String = if str_eq(redir, "") { alt } else { redir }
if str_eq(chosen, "") {
println("[soul] warn: steward action '" + steward_action + "' returned no usable content — using original screened input")
}
if str_eq(chosen, "") { screened } else { chosen }
}
// L3: imprint responds