Skip to content

Harden offline incidents and virtual eIM transitions - #1

Merged
HDGoldi merged 3 commits into
mainfrom
kiro/project-wide-improvements-wf9fbcd075
Oct 19, 2026
Merged

HDGoldi merged 3 commits into
mainfrom
kiro/project-wide-improvements-wf9fbcd075

Conversation

@HDGoldi

@HDGoldi HDGoldi commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

This change hardens two reliability boundaries:

  • AWS-independent offline incidents: --no-bedrock incidents now receive a private provider-neutral Strands Model. Offline replay, trace validation, benchmark helpers, and replay-backed UI flows no longer initialize AWS. Accidental inference fails closed with the fixed sanitized error inference is disabled for direct-tool-only incidents.
  • Atomic virtual-eIM target handling: FleetTwin.enable_profile now validates uncached targets before proposal checks or operational mutation. Non-string targets raise TypeError("target must be a string"); unknown and already-active strings produce uncached, audit-visible refusals. Approval-keyed duplicate commands remain cache-first and return the identical result object without new events, profile changes, or simulated time.

The demonstrated benefits are reliability, dependency isolation, and atomic state handling, not measured performance. No comparable raw before/after performance evidence was collected, so this PR makes no performance-improvement claim.

Reproduced failures and fixes

  1. Offline execution consulted AWS. Before the change,
    env AWS_PROFILE=simshift-profile-that-does-not-exist DECIDER_URL=http://127.0.0.1:1 .venv/bin/python -m simshift.agent TRANSIENT --approve --no-bedrock --replay docs/sample-runs/TRANSIENT.json
    exited 1 with botocore.exceptions.ProfileNotFound because Incident always constructed a Bedrock model. Offline construction now uses _DirectToolModel; the enabled branch still obtains and retains the exact cached real BedrockModel.
  2. An invalid profile could partially mutate the twin. Before the change, a proposal-bound FleetTwin("BR_PROFILE").enable_profile("profile_C", proposal_id) raised KeyError after changing active_profile to an uninstalled profile and emitted no audit event. Uncached malformed, unknown, and active targets are now rejected before mutation. Unknown/active string refusals retain the existing single-event, 30-minute accounting and remain uncached.

Compatibility boundaries

The change preserves Python >=3.12, Strands Agents 1.61.1 and its provider-neutral Model interface, Pydantic 2, the standard-library loopback web stack, public module/CLI commands, enabled Bedrock model ID/region/token settings, advisory plan review and explanations, orchestrator behavior, approval/proposal/capability/owner/reachability precedence for valid targets, rollback behavior, routing prompts and thresholds, policy rules, six scenario outcomes, trace schema and recordings, benchmark records, HTTP/JavaScript contracts, dependencies, packaging configuration, and frontend behavior.

Published traces and docs/benchmark/benchmark-2026-10-19.json have no branch or working-tree changes. The complete diff contains only the seven planned source, test, and documentation files; inspection found no credentials, generated artifacts, cache files, or unrelated churn.

Verification evidence

Focused and full tests

  • Agent/orchestrator/web focus, Python 3.12: 58 passed, 1 skipped (the optional real-Decider integration).
  • Twin/policy focus, Python 3.12: 39 passed.
  • Full Python 3.12 suite: 147 collected; 146 passed, 1 skipped.
  • Full Python 3.13 suite: 147 collected; 146 passed, 1 skipped.
  • The only skip on each interpreter was the optional real-Decider test because no server was running.

All 12 published trace validations

All 12/12 validations reported ok:

  • docs/sample-runs/BR_PROFILE.json
  • docs/sample-runs/DE_OPERATOR.json
  • docs/sample-runs/FW_LOOP.json
  • docs/sample-runs/JP_INGEST.json
  • docs/sample-runs/OFFLINE.json
  • docs/sample-runs/TRANSIENT.json
  • docs/sample-runs/orchestrated/BR_PROFILE.json
  • docs/sample-runs/orchestrated/DE_OPERATOR.json
  • docs/sample-runs/orchestrated/FW_LOOP.json
  • docs/sample-runs/orchestrated/JP_INGEST.json
  • docs/sample-runs/orchestrated/OFFLINE.json
  • docs/sample-runs/orchestrated/TRANSIENT.json

Six deterministic replay routes and outcomes

  • BR_PROFILE: gather_evidence -> query_apn_session_app_diagnostics; gather_evidence -> query_firmware_modem_diagnostics; act -> prepare_guarded_profile_transition; restored=True.
  • DE_OPERATOR: gather_evidence -> query_apn_session_app_diagnostics; act -> simulate_operator_reselection; restored=True.
  • FW_LOOP: gather_evidence -> query_firmware_modem_diagnostics; gather_evidence -> query_apn_session_app_diagnostics; fallback -> defer_or_escalate; restored=False, safely escalated.
  • JP_INGEST: act -> query_apn_session_app_diagnostics; gather_evidence -> query_firmware_modem_diagnostics; fallback -> defer_or_escalate; restored=False, safely escalated.
  • OFFLINE: gather_evidence -> query_apn_session_app_diagnostics; fallback -> defer_or_escalate; restored=False, safely escalated.
  • TRANSIENT: act -> simulate_retry_with_backoff; restored=True.

CLI, HTTP, JavaScript, and distribution checks

  • Under hostile AWS_PROFILE=simshift-profile-that-does-not-exist, the documented TRANSIENT --approve --no-bedrock --replay command exited 0, identified no Bedrock, routed to simulate_retry_with_backoff, ended with restored=True, and contained no ProfileNotFound.
  • Replay-backed real loopback HTTP checks: 2 passed. They loaded the inspector page and exactly six scenarios, advanced BR_PROFILE to proposal-bound human approval, rejected another step with HTTP 409 while pending, accepted literal JSON true, and completed in restored SUCCEEDED state.
  • node --check simshift/static/app.js exited 0.
  • uv build produced exactly 1 sdist and 1 wheel. Wheel assertions found all 3/3 required assets: simshift/static/app.js, simshift/static/index.html, and simshift/static/style.css.
  • The existing setuptools warning that simshift.static is absent from explicit package configuration was observed and intentionally not changed.
  • Working-tree and origin/main...HEAD git diff --check commands exited 0; immutable fixture/benchmark diff checks also exited 0.
  • pyproject.toml, Makefile, and CI configure no lint command, formatter command, or static type-check command. These checks are recorded as absent rather than misrepresented as passing; Python tests/import execution, JavaScript parsing, package build, and whitespace checks are separate evidence.

No live Decider/Bedrock benchmark was run. The measured facts above support dependency isolation and state-boundary reliability; any future latency or recovery-quality claim requires comparable raw before/after measurements.

Deferred recommendations

  • Resolve simshift.static discovery and add artifact-content CI. Impact: medium packaging reliability. Evidence: the wheel currently contains all static assets, but setuptools emits an ambiguous package-discovery warning and CI does not build artifacts. Tradeoff: package-data/discovery policy must be chosen deliberately to avoid including unwanted packages or tests.
  • Test Python 3.12 in CI. Impact: medium lower-bound compatibility assurance. Evidence: metadata declares >=3.12, CI runs only 3.13, while local 3.12.13 and 3.13.13 both passed 146 tests with one skip. Tradeoff: a version matrix increases CI time and unpinned resolution may differ by interpreter.
  • Coordinate frontend requests and incident snapshots. Impact: medium UI robustness under rapid/concurrent requests. Evidence: start() checks but does not set busy, controls are not uniformly disabled, and snapshots do not take the incident lock. Tradeoff: naive locking can deadlock and stale-response handling needs dedicated browser/concurrency tests.
  • Validate router configuration at construction. Impact: medium reusable-pattern developer experience. Evidence: fallback/options/tools/read-only consistency is currently assumed. Tradeoff: new validation tightens the public API and may reject configurations that previously failed only at runtime.
  • Harden benchmark CLI validation. Impact: low-to-medium local developer experience. Evidence: unknown chooser names raise KeyError, zero samples fail late, and --stamp is joined into an output path. Tradeoff: this trusted local CLI work is unrelated to the fixed runtime boundaries.
  • Adopt narrowly selected quality automation separately. Impact: medium maintainability. Evidence: no lint, formatter, static type, JavaScript, or dependency-vulnerability check is configured. Tradeoff: adds dependencies/policy and can create cosmetic churn, especially around dynamic Strands tool wrappers.
  • Expand web security/logging only if deployment scope changes. Impact: high for remote production use but low for the declared loopback demo. Evidence: the app intentionally has no authentication, suppresses access logs, and may expose exception details, while binding to loopback and enforcing host/origin/content-type/body-size controls. Tradeoff: authentication, TLS, CSRF, logging, and rate limiting are a large architectural expansion.
  • Revisit model/policy outcome optimization only with comparable live evidence. Impact: potentially high but unproven. Evidence: the published benchmark notes firmware misses and synthetic/cross-hardware caveats; no comparable Decider/AWS environment was available. Tradeoff: tuning risks overfitting, changing recordings, and overstating performance; it requires held-out data, repeated runs, comparable hardware, and confidence intervals.

@coldtea-pr-lens

coldtea-pr-lens Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
  • Redraw · drawn at 49c2751, the branch has moved on

Architecture

Architecture diagram for HDGoldi/simshift-agent at 49c2751

Play the walkthrough

Inside the changed components — 2 views

Component view — Recovery Agent Runtime

Model selection and tool wiring inside the recovery agent container.

Architecture view of Component view — Recovery Agent Runtime in HDGoldi/simshift-agent

Component view — Virtual eIM & Twin

State transitions, target validation, and eIM execution logic.

Architecture view of Component view — Virtual eIM & Twin in HDGoldi/simshift-agent

Data flow

Data flow diagram for HDGoldi/simshift-agent at 49c2751

Follow each request

The other flows — 1 sequence

Profile enable target validation

Sequence diagram of Profile enable target validation in HDGoldi/simshift-agent

Review

✨ Nothing flagged · reviewed 49c2751


Tip

Set github.comment.collapsed: true in .github/pr-lens.yml to fold the comment behind one View architecture and data flow row. Drawing still runs as before

🪧 More tips
  • Run npx skills add coldteadotai/pr-lens, then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Run npx @coldtea/pr-lens-cli analyze --base origin/main on a branch, then npx @coldtea/pr-lens-cli render .pr-lens/graph.json. Same lenses, your own model key, before the pull request exists
  • Click the link under each diagram to open it on a canvas you can zoom, pan and step through
  • The diagrams are links. Click one to open it on the canvas, then press W or click play to walk through the change
  • Open a diagram on the canvas, then press W or click play to walk through the change one step at a time
  • The CLI's render reads .github/pr-lens.yml and applies your renames, exclusions and lane pins at draw time
  • Set github.draw: on-demand in .github/pr-lens.yml and PR Lens stops drawing pull requests on its own. Comment @pr-lens draw on a pull request when you want that one drawn
  • Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and your model provider's key as its api-key to run PR Lens from your own CI. Any /chat/completions endpoint works
  • After a push, tick Redraw to draw the latest commit
  • Switch GitHub to dark mode and the diagrams follow. The moving dots are this pull request's data in motion

Thanks for using PR Lens! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out on X, Bluesky, Mastodon, Reddit or LinkedIn

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/test_agent.py">

<violation number="1" location="tests/test_agent.py:75">
P3: This test does not verify caching: the constant lambda returns the same object on every call, and the test constructs only one incident. Track factory calls across multiple enabled incidents and assert one cached result is reused, otherwise a regression to per-incident Bedrock construction will pass.</violation>
</file>

<file name="docs/architecture.md">

<violation number="1" location="docs/architecture.md:102">
P3: The documentation overstates the uncached behavior: `enable_profile` caches the `IPA unreachable` refusal, so repeating that approval emits no new audit event or simulated time. Distinguish cached delivery failures from the uncached validation, approval, capability, and owner refusals.</violation>
</file>

Tip: Get a Slack DM when cubic finishes reviewing your PRs. Connect Slack

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread tests/test_agent.py
passed_to_tools.append(llm)
return real_build_tools(twin, approver, llm)

monkeypatch.setattr(ag, "bedrock_model", lambda: sentinel)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This test does not verify caching: the constant lambda returns the same object on every call, and the test constructs only one incident. Track factory calls across multiple enabled incidents and assert one cached result is reused, otherwise a regression to per-incident Bedrock construction will pass.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/test_agent.py, line 75:

<comment>This test does not verify caching: the constant lambda returns the same object on every call, and the test constructs only one incident. Track factory calls across multiple enabled incidents and assert one cached result is reused, otherwise a regression to per-incident Bedrock construction will pass.</comment>

<file context>
@@ -22,6 +25,59 @@ def choose(self, state, instructions, options, name="next_tool"):
+        passed_to_tools.append(llm)
+        return real_build_tools(twin, approver, llm)
+
+    monkeypatch.setattr(ag, "bedrock_model", lambda: sentinel)
+    monkeypatch.setattr(ag, "build_tools", capture_tools)
+    inc = ag.Incident("TRANSIENT", decider=ScriptedDecider(["retry_connection"]), use_bedrock=True)
</file context>

Comment thread docs/architecture.md Outdated
Comment on lines +102 to +103
state change. New invalid-target and other command-not-sent refusals remain uncached audit events; under the
simulation's existing event accounting, each `eim_enable_profile` refusal still represents 30 simulated minutes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The documentation overstates the uncached behavior: enable_profile caches the IPA unreachable refusal, so repeating that approval emits no new audit event or simulated time. Distinguish cached delivery failures from the uncached validation, approval, capability, and owner refusals.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/architecture.md, line 102:

<comment>The documentation overstates the uncached behavior: `enable_profile` caches the `IPA unreachable` refusal, so repeating that approval emits no new audit event or simulated time. Distinguish cached delivery failures from the uncached validation, approval, capability, and owner refusals.</comment>

<file context>
@@ -96,8 +96,12 @@ What is modelled, what is simplified and what is left out, in telecom terms, is
+  stands in for SGP.32's IPA-triggered rollback). The virtual eIM requires a string target naming an installed,
+  inactive profile before it attempts delivery. Exact duplicate commands are served from the approval-keyed cache
+  first—even when the successful target is now active—so they return the original result without another event or
+  state change. New invalid-target and other command-not-sent refusals remain uncached audit events; under the
+  simulation's existing event accounting, each `eim_enable_profile` refusal still represents 30 simulated minutes.
+  No profile download, no cryptography, no certified SGP.32 flow.
</file context>
Suggested change
state change. New invalid-target and other command-not-sent refusals remain uncached audit events; under the
simulation's existing event accounting, each `eim_enable_profile` refusal still represents 30 simulated minutes.
New invalid-target, approval, capability and owner refusals remain uncached audit events; an IPA-unreachable result is cached under its approval, so repeating it emits no new event.
The first event for each `eim_enable_profile` refusal still represents 30 simulated minutes.

… names

The eIM cache is keyed by approval alone, so the docs no longer call cached
results exact duplicates. Drop the duplicated ordering prose and em dashes,
rename the model-factory test (it does not test caching) and drop an
assertion that could never fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 existing issue remains and 2 new issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docs/architecture.md">

<violation number="1" location="docs/architecture.md:125">
P3: Non-string targets raise `TypeError` rather than producing a refusal, so this sentence should say so. Add that case to the refusal list.</violation>
</file>

<file name=".claude/skills/verify/SKILL.md">

<violation number="1" location=".claude/skills/verify/SKILL.md:26">
P3: The gotcha overstates replay's AWS independence: `--replay` alone still constructs `BedrockModel` unless `--no-bedrock` is also supplied. Qualify this as `make replay` or state that direct replays require `--no-bedrock`, otherwise a verifier with an invalid `AWS_PROFILE` can hit `ProfileNotFound`.</violation>
</file>

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread docs/architecture.md
for an eUICC that cannot enable profiles or a fleet whose owner forbids switching, and one it cannot deliver because
the management path is down. It does not repeat the gate's evidence checks (reject cause, reselection, coverage). A test
replaces the gate with one that always passes and checks that the eIM still refuses an unapproved switch.
Independently of the gate, the virtual eIM refuses an enable command whose target is not an installed, inactive

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Non-string targets raise TypeError rather than producing a refusal, so this sentence should say so. Add that case to the refusal list.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/architecture.md, line 125:

<comment>Non-string targets raise `TypeError` rather than producing a refusal, so this sentence should say so. Add that case to the refusal list.</comment>

<file context>
@@ -126,13 +122,12 @@ Decider's options beforehand:
-whose owner forbids switching, and one it cannot deliver because the management path is down. It does not repeat the
-gate's evidence checks (reject cause, reselection, coverage). A test replaces the gate with one that always passes and
-checks that the eIM still refuses an unapproved switch.
+Independently of the gate, the virtual eIM refuses an enable command whose target is not an installed, inactive
+profile, whose approval does not match the proposal, for an eUICC that cannot enable profiles or a fleet whose owner
+forbids switching, and one it cannot deliver because the management path is down. A command that reuses an approval
</file context>

## Gotchas

- An invalid `AWS_PROFILE` name crashes even `--no-bedrock` runs: the inner Strands agent always builds a BedrockModel.
- `--no-bedrock` runs, replay and validate ignore `AWS_PROFILE`. `make test` still needs it unset or valid: the pattern

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The gotcha overstates replay's AWS independence: --replay alone still constructs BedrockModel unless --no-bedrock is also supplied. Qualify this as make replay or state that direct replays require --no-bedrock, otherwise a verifier with an invalid AWS_PROFILE can hit ProfileNotFound.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .claude/skills/verify/SKILL.md, line 26:

<comment>The gotcha overstates replay's AWS independence: `--replay` alone still constructs `BedrockModel` unless `--no-bedrock` is also supplied. Qualify this as `make replay` or state that direct replays require `--no-bedrock`, otherwise a verifier with an invalid `AWS_PROFILE` can hit `ProfileNotFound`.</comment>

<file context>
@@ -23,5 +23,6 @@ README "Live" section) and, for Bedrock, `AWS_PROFILE=<your-profile>`.
 ## Gotchas
 
-- Live Bedrock flows require a valid AWS profile and region configuration.
+- `--no-bedrock` runs, replay and validate ignore `AWS_PROFILE`. `make test` still needs it unset or valid: the pattern
+  tests and the Bedrock-mocked agent tests construct a `BedrockModel`.
 - Benchmark runs take ~20-40 min with both Bedrock choosers; run them in the background.
</file context>

@HDGoldi
HDGoldi merged commit 86dc6e2 into main Oct 19, 2026
3 checks passed
@HDGoldi
HDGoldi deleted the kiro/project-wide-improvements-wf9fbcd075 branch October 19, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants