Companion to 0133-workflow-chain-composition.md. Working document (not normative). Likelihood × Impact each H/M/L; Score = combined severity. Critical/High rows name a mitigation owner.
| ID | Risk | Likelihood | Impact | Score | Mitigation | Owner | Status |
|---|---|---|---|---|---|---|---|
| R1 | Unbounded co-expansion (DoS): a malicious pack composes chains in a cycle or pathological deep nest, driving a host into unbounded recursion at from-chain instantiation. | M | H | High | sub-chain-expansion-bounded invariant (protocol-tier): a self-composing chain rejects sub_chain_cycle; nesting past maxSubChainDepth (default 8) fails closed with the same code. Fully witnessed server-free by chain-subchain-cycle-rejected.test.ts (both guards + the benign-within-bound complement). The workflow-chain analog of RFC 0071 artifact-schema-compile-bounded. | Security Architect | Mitigated + witnessed (server-free, now) |
| R2 | Cross-tenant child aliasing: a co-registered child is registered under, or reachable from, the wrong tenant, or two tenants instantiating the same pack converge on one shared child workflow → cross-tenant data reach. | L | H | High | sub-chain-child-tenant-scoped invariant (reference-impl-tier): the child MUST be owned only by the parent's tenant; the deterministic id MUST be tenant-scoped (no cross-tenant convergence). Applies the existing CTI-1 isolation to the co-registration path. Graduates reference-impl → protocol when a host witnesses it non-vacuously (chain-subchain-fanout §B). | Reference host (openwop-app) | Open — mitigated by design; host-pending witness |
| R3 | Silent flatten: an unsupported host degrades a runtime sub-chain into an inline splice, changing run semantics + erasing the child as an editable unit without telling the author. | M | M | Med | sub_chain_unsupported (422) MUST-refuse rule: a host without capabilities.workflowChainPacks.subChains.supported refuses a subChains-bearing chain, never flattens. chain-subchain-unsupported-refused.test.ts (corpus-contract leg always-on; host-refusal leg gated). Capability gating makes the refusal discoverable. | Compatibility Architect | Open — mitigated by capability gate; host-refusal leg host-pending |
| R4 | Replay divergence: the minted child id or co-registration introduces run-time non-determinism, so a :fork replays against a different child. | L | H | Med | Child ids are minted at from-chain instantiate time (a POST), baked into the persisted parent BEFORE any run — the run event log gains no new non-determinism, and :fork replays byte-identically (per replay.md RunSnapshot.variables). Dispatch rides the existing core.subWorkflow/core.dispatch nodes (no new event shape). Documented in the spec §Compatibility. | Compatibility Architect | Mitigated by design (instantiate-time minting) |
| R5 | Undeclared variable read: a chain reads { type:"variable" } for a name nothing produces, so a downstream node silently receives undefined at run time. | M | M | Med | variable_undeclared closed-world validation: every variable read MUST reference a declared producedVariables[].name or a materialized parameter. validateVariableReads + chain-produced-var-roundtrip.test.ts (declared passes, undeclared rejects, materialized-param passes). | Spec Architect | Mitigated + witnessed (server-free, now) |
| R6 | producedVariables misused where an edge belongs, eroding the chain-native typed-dataflow model (every hand-off becomes an untyped bag write). | M | L | Low | Spec §2.1 "Prefer edges" MUST: a value on a typed output port MUST ride an explicit edge; producedVariables is ONLY for run-bag writes with no typed port. Prose + the "Alternatives considered" (edges-only) rationale. | Spec Architect | Open — bounded by the prefer-edges MUST |
| R7 | External sub-chain trust/version drift: an external subChainRef resolves to a different chain version on re-instantiation, breaking reproducibility or pulling in an unverified pack. | M | M | Med | External refs resolve + signature-verify like any pack dependency (node-packs.md §Signing); co-registration SHOULD pin the resolved version into the parent's ownership record (Unresolved-Q1, resolved: pin). Unresolvable/unverified → sub_chain_unresolved. | Security Architect | Open — mitigated (pin SHOULD + signature verify) |
| R8 | Reference-host implementation slips, leaving the runtime-child-dispatch legs shape/library-only indefinitely (the RFC 0013 host-leg-soft-skip pattern stalling). | M | M | Med | The library algorithm + 3 server-free scenarios + the bounded-recursion invariant witness the substance now (RFC 0013's own Accepted basis). Accepted §"Status note" names the exact remaining witness (runtime child dispatch on openwop-app) + tracks it in docs/KNOWN-LIMITS.md; the gated scenarios are ready to run the moment a host advertises the capability. | Reference host (openwop-app) | Open — tracked, path identified |
| R9 | Scope creep into arbitrary chain-graph orchestration (conditional sub-chain selection, dynamic fan-out counts) widens the RFC mid-flight. | L | M | Low | This RFC scopes to static subChains[] declaration + config.subChainRef over the existing core.subWorkflow/core.dispatch nodes; dynamic selection rides those nodes' existing runtime semantics, not new chain-layer surface. | Compatibility Architect | Open — scoped out by design |