You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
H1–H3: 3 hardening follow-ups from Wave 1 reviewer findings
H1, H2, H3 each address a HIGH severity reviewer finding from Wave 1 (#1131 final summary). They harden defense-in-depth gaps the reviewers explicitly recommended closing before external consumers (plugin, MCP) attach to workflow_ir.
Locks the boundary contract between #956 Workflow IR and #946 projection vocabulary documented in docs/agentos/workflow-ir-v1.md:
The default boundary fixture must stay local and deterministic: it may pair a validated WorkflowSpec with synthetic EventStore rows to prove source-event linkage, but it must not add dispatch, cache, persistence, or projection-record embedding to the IR.
This is the v1-explicit-supported scope for N3.
PR C-docs: tool-call hook contract
Pure docs PR. Defines the before_tool_call / after_tool_call payload shape, permission scopes, failure policy, and audit event names so a future PR F (dispatch implementation) has an unambiguous target. Per the #939 scope-decision comment, PR C-docs is gated on N1 (#1134) which is now merged.
Excluded from Wave 2 (deferred — boundary decisions still pending)
S4 StepSnapshot bounded projection view — docs/agentos/projection-v1-scope.md "Explicitly deferred" table still lists "StepSnapshot, session health, runtime handles, resume tokens — Later projection views". Needs a boundary-update PR (or canonical [Feature] Define Run/Step/Artifact projections as the canonical harness vocabulary #946 issue decision) before implementation.
In src/ouroboros/persistence/event_store.py:append(), reject any BaseEvent whose aggregate_type == WORKFLOW_LIFECYCLE_AGGREGATE_TYPE (raise PersistenceError instructing the caller to use append_workflow_lifecycle_event()).
In src/ouroboros/persistence/event_store.py:replay_workflow_lifecycle(), wrap each WorkflowLifecycleEvent.from_base_event() call in try/except so a single malformed row does not crash the entire replay (recommended fix from security review MEDIUM [Epic 2] Intelligent Task Routing (PAL) #3).
Out-of-scope
❌ Schema changes
❌ Any change to other aggregate types
❌ Behavioral change to append_workflow_lifecycle_event() itself
Files
src/ouroboros/persistence/event_store.py
tests/unit/persistence/test_event_store.py (or matching test file)
Acceptance
Negative test: raw BaseEvent with aggregate_type="workflow_ir" passed to append() raises PersistenceError.
Positive test: append_workflow_lifecycle_event() continues to work end-to-end.
Replay-resilience test: malformed row co-located with valid rows does not blow up replay_workflow_lifecycle().
In src/ouroboros/plugin/firewall.py:_run_lifecycle_hooks(), at function entry: if hook_kind in TERMINAL_OBSERVABILITY_HOOK_KINDS AND any iterated hook has failure_policy == "fail_closed":
Skip the hook (do not run subprocess).
Emit a plugin.hook.blocked audit event documenting the contract violation.
Do not let the skip influence the function return (terminal observability is fail-open by contract).
Out-of-scope
❌ Change to non-terminal hook policy (e.g., before_invocation still respects fail_closed)
❌ Manifest layer changes (the JSON schema + loader already prevent fail_closed for on_error/on_cancel; this is purely a runtime defense-in-depth)
❌ New permission scope or event family
Files
src/ouroboros/plugin/firewall.py
tests/unit/plugin/test_lifecycle_observability.py (or new sibling file)
Acceptance
Test: forge a HookSpec(failure_policy="fail_closed") for on_error directly in Python (bypassing manifest validator), pass through invoke_plugin() failure path, assert original cause is preserved AND plugin.hook.blocked audit event is emitted.
Existing 13 lifecycle observability tests still pass.
New doc docs/agentos/workflow-ir-projection-mapping.md defining:
How WorkflowNode.node_id maps to projection StepRecord.step_id / VerdictRecord.ac_id.
How WorkflowEdge.edge_id corresponds to projection event-pair linkage.
How WorkflowLifecycleEvent source IDs project into RunRecord / StageRecord / StepRecord.
New test tests/integration/test_ir_projection_consistency.py: build a small validated WorkflowSpec, emit synthetic EventStore rows that follow the documented mapping, verify ProjectionBuilder produces records whose IDs match the IR's node_id / edge_id.
Future plugin_schema_version: "0.4" migration plan (introduction deferred to a future PR F dispatch slice; this docs PR does NOT change manifest.py or schemas).
Out-of-scope
❌ ANY code change (no manifest.py, no schemas/, no firewall.py)
Doc names exactly which Wave 3+ PR will introduce v0.4 schema (PR F).
Common acceptance gates (apply to every Wave 2 PR)
Read first: each worker MUST grep / read upstream/main for the affected surface BEFORE pushing, to confirm the slot is genuinely net-new (Wave 1 lesson: S1 worker correctly refused to duplicate already-merged feat(harness): project artifact and verdict records #1061 work).
Schema invariant: no schema_version bump unless explicitly in the PR title.
Boundary doc citation: PR body names the exact section (projection-v1-scope.md, workflow-ir-v1.md, or #939 scope-decision comment) it implements.
Anti-action check: none of the explicit ❌ lists in the lock comments violated.
Test coverage: at least one negative test per acceptance criterion.
Goal
Implement the Wave 2 PR queue after the Wave 1 substrate (#1132 – #1137) merged on 2026-05-20. Wave 2 covers 5 independent PRs scoped tightly inside the locked v1 boundary documents:
docs/agentos/projection-v1-scope.md([Feature] Define Run/Step/Artifact projections as the canonical harness vocabulary #946 v1 boundary)docs/agentos/workflow-ir-v1.md(Agent OS: introduce typed Workflow IR for fat-harness execution planning #956 v1 boundary)All 5 PRs are mutually independent (touch disjoint surfaces) and may merge in any order.
Scope of Wave 2
fix(persistence): guard workflow_ir aggregate type against raw appendfix(orchestrator): bound workflow lifecycle reason_code/refs and nesting depthfix(plugin): block fail_closed terminal-observability hooks at dispatchdocs+test(agentos): IR ↔ projection mapping contractdocs(plugin): define before_tool_call/after_tool_call hook contractH1–H3: 3 hardening follow-ups from Wave 1 reviewer findings
H1, H2, H3 each address a HIGH severity reviewer finding from Wave 1 (#1131 final summary). They harden defense-in-depth gaps the reviewers explicitly recommended closing before external consumers (plugin, MCP) attach to
workflow_ir.N3: IR ↔ #946 projection mapping
Locks the boundary contract between #956 Workflow IR and #946 projection vocabulary documented in
docs/agentos/workflow-ir-v1.md:This is the v1-explicit-supported scope for N3.
PR C-docs: tool-call hook contract
Pure docs PR. Defines the
before_tool_call/after_tool_callpayload shape, permission scopes, failure policy, and audit event names so a future PR F (dispatch implementation) has an unambiguous target. Per the #939 scope-decision comment, PR C-docs is gated on N1 (#1134) which is now merged.Excluded from Wave 2 (deferred — boundary decisions still pending)
docs/agentos/projection-v1-scope.md"Explicitly deferred" table still lists "StepSnapshot, session health, runtime handles, resume tokens — Later projection views". Needs a boundary-update PR (or canonical [Feature] Define Run/Step/Artifact projections as the canonical harness vocabulary #946 issue decision) before implementation.on_eventobservation hook — subscription filter mechanism is undefined (per-hook event allowlist needs manifest schema decision; all-events fan-out is DoS-risky). Needs design decision on Agent OS plugins: standardize lifecycle hooks, permissions, and audit events #939 first.PR 1 — Hardening H1 ·
workflow_irappend guardRefs: #956 · #1134 security review HIGH #1
In-scope
src/ouroboros/persistence/event_store.py:append(), reject anyBaseEventwhoseaggregate_type == WORKFLOW_LIFECYCLE_AGGREGATE_TYPE(raisePersistenceErrorinstructing the caller to useappend_workflow_lifecycle_event()).src/ouroboros/persistence/event_store.py:replay_workflow_lifecycle(), wrap eachWorkflowLifecycleEvent.from_base_event()call in try/except so a single malformed row does not crash the entire replay (recommended fix from security review MEDIUM [Epic 2] Intelligent Task Routing (PAL) #3).Out-of-scope
append_workflow_lifecycle_event()itselfFiles
src/ouroboros/persistence/event_store.pytests/unit/persistence/test_event_store.py(or matching test file)Acceptance
BaseEventwithaggregate_type="workflow_ir"passed toappend()raisesPersistenceError.append_workflow_lifecycle_event()continues to work end-to-end.replay_workflow_lifecycle().PR 2 — Hardening H2 · Bounded
reason_code/refsand nesting depthRefs: #956 · #1134 security review HIGH #2 + MEDIUM #4
In-scope
WorkflowLifecycleEvent.reason_code:len(reason_code) ≤ 1024._coerce_refsvalidator:len(refs) ≤ 64entries, eachlen(ref) ≤ 512chars.WorkflowLifecycleEvent.workflow_id:len(workflow_id) ≤ 256(security review LOW [Epic 5] Three-Stage Evaluation Pipeline #6)._MAX_NESTING_DEPTH = 32and pass_depththrough_normalize_json_value()recursive calls; raiseValueErroron exceedance (security review MEDIUM [Epic 3] Double Diamond Execution #4).Out-of-scope
Files
src/ouroboros/orchestrator/workflow_lifecycle.pytests/unit/orchestrator/test_workflow_lifecycle_events.pyAcceptance
ValidationError).datarejected cleanly (noRecursionError).PR 3 — Hardening H3 · Terminal-hook fail-open runtime guard
Refs: #939 · #1137 code review HIGH
In-scope
src/ouroboros/plugin/firewall.py:_run_lifecycle_hooks(), at function entry: ifhook_kind in TERMINAL_OBSERVABILITY_HOOK_KINDSAND any iterated hook hasfailure_policy == "fail_closed":plugin.hook.blockedaudit event documenting the contract violation.Out-of-scope
before_invocationstill respectsfail_closed)fail_closedforon_error/on_cancel; this is purely a runtime defense-in-depth)Files
src/ouroboros/plugin/firewall.pytests/unit/plugin/test_lifecycle_observability.py(or new sibling file)Acceptance
HookSpec(failure_policy="fail_closed")foron_errordirectly in Python (bypassing manifest validator), pass throughinvoke_plugin()failure path, assert original cause is preserved ANDplugin.hook.blockedaudit event is emitted.PR 4 — N3 · IR ↔ #946 projection mapping contract
Refs: #956 / #946 ·
docs/agentos/workflow-ir-v1.mdIn-scope
docs/agentos/workflow-ir-projection-mapping.mddefining:WorkflowNode.node_idmaps to projectionStepRecord.step_id/VerdictRecord.ac_id.WorkflowEdge.edge_idcorresponds to projection event-pair linkage.WorkflowLifecycleEventsource IDs project intoRunRecord/StageRecord/StepRecord.tests/integration/test_ir_projection_consistency.py: build a small validatedWorkflowSpec, emit synthetic EventStore rows that follow the documented mapping, verifyProjectionBuilderproduces records whose IDs match the IR'snode_id/edge_id.Out-of-scope (per
workflow-ir-v1.mdboundary)parallel_executordispatch from IR#946records inside Workflow IRFiles
docs/agentos/workflow-ir-projection-mapping.md(new)tests/integration/test_ir_projection_consistency.py(new)Acceptance
step_id/ac_id/edge_idmatch the IR's planned identifiers.workflow-ir-v1.mdandprojection-v1-scope.mdboundaries explicitly.PR 5 — PR C-docs · Tool-call hook contract docs (pure docs)
Refs: #939 · #939 scope-decision comment
In-scope (pure docs)
docs/rfc/plugin-tool-call-hook-contract.mddefining:before_tool_call/after_tool_callpayload shape (tool name, redacted args, bounded output ref).plugin:tool:intercept(mutating, fail-closed-eligible) vsplugin:tool:observe(observation, fail-open).plugin.tool.intercept.requested/plugin.tool.intercept.completed/plugin.tool.intercept.blocked.plugin_schema_version: "0.4"migration plan (introduction deferred to a future PR F dispatch slice; this docs PR does NOT change manifest.py or schemas).Out-of-scope
Files
docs/rfc/plugin-tool-call-hook-contract.md(new, only)Acceptance
docs/rfc/userlevel-plugins.mdand feat(plugin): add on_error/on_cancel observability hooks (PR E) #1137 (PR E) as the observability-hook precedent.Common acceptance gates (apply to every Wave 2 PR)
projection-v1-scope.md,workflow-ir-v1.md, or#939 scope-decision comment) it implements.--no-verify: pre-commit hooks must run.