Skip to content

feat: persist trustworthy-transition continuity v0.1#770

Draft
safal207 wants to merge 15 commits into
feat/continuity-store-v0.1from
feat/trustworthy-transition-continuity-v0.1
Draft

feat: persist trustworthy-transition continuity v0.1#770
safal207 wants to merge 15 commits into
feat/continuity-store-v0.1from
feat/trustworthy-transition-continuity-v0.1

Conversation

@safal207

@safal207 safal207 commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

Implements LS #768 as a stacked follow-up to draft PR #766.

This PR adds a provider-neutral continuity adapter that persists exact trustworthy-transition record references and derives safe resume posture across pause, restart, retry, and context drift.

Base dependency

This PR is intentionally stacked on:

It must not be retargeted to main or merged before #766 lands.

The head branch has been fast-forwarded onto the current base head. It is behind_by: 0, mergeable, and its diff contains only transition-adapter files; the package workflow is inherited from #766.

Added

  • src/trustworthy-transition.ts
  • src/transition-demo.ts
  • tests/fixtures/trustworthy-transition-continuity-v0.1.json
  • tests/trustworthy-transition.test.ts
  • docs/TRUSTWORTHY_TRANSITION_CONTINUITY_V0_1.md
  • demo:transition package script
  • README interoperability and boundary documentation

Snapshot model

Each transition snapshot is stored as an immutable verification_receipt and binds:

  • transition and subject identity;
  • action identity and arguments binding digest;
  • authorization, observation, response-integrity, and causal-audit references;
  • deterministic evidence-set digest;
  • runtime context digest;
  • independent authority, execution, response-integrity, and causal-validity dimensions;
  • side-effect commitment state;
  • expiry and idempotent retry metadata;
  • optional explicit reauthorization reference.

Resume postures

  • CONTINUE_SIDE_EFFECT
  • RETRY_SIDE_EFFECT
  • REPORT_ONLY
  • REMEDIATE_RESPONSE
  • REVALIDATE
  • BLOCKED
  • ALREADY_CONSUMED

Safety rules

  • A committed or observed side effect cannot replay after restart.
  • Expired, denied, pending, consumed, unknown, or revalidation-required authority cannot silently resume.
  • Context or evidence drift requires a rebuilt snapshot.
  • Error retry requires an exact stored idempotency key and explicit retryable profile.
  • Failed response integrity routes to response remediation rather than execution replay.
  • Invalid causal lineage blocks continuation.
  • Historical snapshots remain reportable without regaining live permission.
  • Cross-subject, transition, action, and binding substitution fail closed.

Snapshot-chain monotonicity

The chain assessment rejects:

  • broken previous_ref;
  • observation removal;
  • committed-side-effect rollback;
  • executed-to-unobserved rollback;
  • terminal authority reopened under the same authorization;
  • capture-time rollback.

Reopening terminal authority requires a different authorization reference plus an explicit matching reauthorization_ref.

Validation

The inherited LS Continuity Store workflow is green on the final head and ran:

cd packages/ls-continuity-store
npm install --no-audit --no-fund
npm run build
npm test
npm run demo
npm run demo:transition

All steps passed, including TypeScript compilation, unit/restart tests, the base continuity demo, and the trustworthy-transition continuity demo.

The fixture suite persists every scenario to SQLite/WAL, closes the database, reopens it, loads the immutable object, and only then evaluates continuation.

The base PR #766 is also green for:

  • LS Continuity Store;
  • Security & CI Pipeline;
  • Phase 12.1 Regression Scan;
  • Reflection Dashboard HTTP E2E.

Review status

  • CodeRabbit: passed on the final head; no inline review threads.
  • LS Continuity Store package gate: passed on the final head.
  • Phase 12.1 Regression Scan / Semgrep: passed on the final head.
  • Mandatory Codex review remains unavailable because the connected code-review quota is exhausted.

Boundary

LS persists continuity and derives continuation posture only. It does not issue authorization, observe execution, verify response claims, or decide causal validity.

Merge gate

Keep this PR draft. Do not merge until:

  1. PR feat: add LS Continuity Store v0.1 #766 lands;
  2. this PR is retargeted to main and rerun on the landed base;
  3. all final CI checks are green;
  4. mandatory Codex review is available and passes.

Relates to #768 and safal207/Liminal#108.

Summary by CodeRabbit

  • New Features

    • Added a new continuity workflow and demo path for the LS Continuity Store.
    • Introduced transition snapshot evaluation, chain assessment, and resume decision support.
    • Added a new demo command to show transition behavior end to end.
  • Bug Fixes

    • Improved demo setup and shutdown so local data is created and cleaned up reliably.
    • Added checks that help detect invalid snapshot changes, rollback cases, and stale or mismatched continuity state.
  • Documentation

    • Expanded the README and reference docs with clearer continuity rules, demo steps, and expected behavior.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a "trustworthy-transition" continuity adapter to the ls-continuity-store package: new snapshot data model, evidence-digest computation, envelope creation/persistence, resume-decision evaluation, snapshot-chain consistency assessment, a fixture-driven test suite, a runnable transition demo, a new CI workflow, and expanded README/profile documentation.

Changes

Trustworthy-transition continuity adapter

Layer / File(s) Summary
Data model: dimensions, types, and interfaces
packages/ls-continuity-store/src/trustworthy-transition.ts
Defines dimension/operation types, snapshot payload shape, request/decision/chain-assessment interfaces, and internal validation/normalization helpers.
Snapshot creation and persistence
packages/ls-continuity-store/src/trustworthy-transition.ts
Implements evidence-set digest computation, snapshot envelope creation with validation, and persistence into ContinuityStore.
Resume evaluation logic
packages/ls-continuity-store/src/trustworthy-transition.ts
Implements stored-snapshot verification and evaluateTransitionResume covering report/remediate/resume/retry operations, authority/causal/integrity checks, and idempotency gating.
Snapshot chain assessment
packages/ls-continuity-store/src/trustworthy-transition.ts
Implements assessSnapshotChain detecting identity mismatches, rollback conditions, and authority re-open handling.
Fixture-driven test suite
packages/ls-continuity-store/tests/fixtures/trustworthy-transition-continuity-v0.1.json, packages/ls-continuity-store/tests/trustworthy-transition.test.ts
Adds a 10-case JSON fixture and tests covering restart-safe replay, chain rollback rejection, authorization-epoch renewal, and digest sensitivity.
Demo, CI workflow, and docs
packages/ls-continuity-store/src/transition-demo.ts, .../src/demo.ts, .../package.json, .github/workflows/ls-continuity-store.yml, .../README.md, .../docs/TRUSTWORTHY_TRANSITION_CONTINUITY_V0_1.md
Adds a standalone transition demo script, updates the base demo to use a managed data directory and close the DB, adds a demo:transition npm script and a CI workflow that runs build/test/demos, and expands README and profile documentation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant createTransitionSnapshotEnvelope
  participant computeEvidenceSetDigest
  participant ContinuityStore
  participant evaluateTransitionResume

  Caller->>computeEvidenceSetDigest: transition_id, subject_id, refs, observations
  computeEvidenceSetDigest-->>Caller: evidence_set_digest
  Caller->>createTransitionSnapshotEnvelope: snapshot input + createdAt
  createTransitionSnapshotEnvelope-->>Caller: continuity envelope
  Caller->>ContinuityStore: persist(envelope)
  ContinuityStore-->>Caller: stored snapshot
  Caller->>ContinuityStore: reload snapshot (after restart)
  ContinuityStore-->>Caller: stored snapshot payload
  Caller->>evaluateTransitionResume: stored snapshot + resume request
  evaluateTransitionResume-->>Caller: allow/posture/reason decision
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: persisting trustworthy-transition continuity v0.1.
Description check ✅ Passed The description is detailed and covers the change, rationale, checks, and merge constraints, with only minor template-heading differences.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/trustworthy-transition-continuity-v0.1

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

safal207 commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

TRI-AGENT LIVE TRAIL RUN 001

Repository: safal207/LS
PR: #770
Target HEAD: 0ed6613

Review this exact immutable HEAD only.

Until all review attempts finish:

  • do not push commits;
  • do not apply autofixes;
  • do not resolve findings;
  • do not retarget the PR;
  • keep the PR in Draft.

Review independently.
Do not treat conclusions from other agents as evidence.
Every finding must include concrete code evidence or a reproducible failure path.

Focus:

  • correctness and fail-closed behavior;
  • snapshot-chain monotonicity;
  • authority expiry;
  • replay and idempotency;
  • evidence and context drift;
  • missing negative or adversarial tests;
  • unsafe state transitions.

safal207 commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

/agentic_review

safal207 commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

safal207 commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@codex review for correctness, security regressions, unsafe continuation paths, and missing adversarial tests.

Review the current HEAD independently.
Do not trust existing bot conclusions unless verified directly against code.
Report only P0/P1 findings supported by concrete evidence or a reproducible failure path.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ed661304e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

binding_digest: bindingDigest,
record_refs: recordRefs
}),
dimensions: { ...input.dimensions },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate transition dimensions before granting resumes

At this point the API persists whatever runtime values are in input.dimensions, while verification_receipt payloads are only validated as z.record(z.unknown()). A JS caller can persist authority: "STALE", execution: "NOT_OBSERVED", response_integrity: "VERIFIED", and causal_validity: "VALID"; evaluateTransitionResume has no default fail-closed branch for the unknown authority/causal/integrity values and falls through to CONTINUE_SIDE_EFFECT when evidence/context match. That turns malformed or adversarial snapshots into live permission, so the enum set needs runtime validation here or in verifyStoredSnapshot.

Useful? React with 👍 / 👎.


if (
TERMINAL_AUTHORITY.has(previous.payload.dimensions.authority) &&
current.payload.dimensions.authority === "VALID"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject multi-hop reopening of terminal authority

Because this guard only rejects a direct terminal-authority -> VALID transition, a same-authorization chain such as DENIED -> PENDING -> VALID passes pairwise assessSnapshotChain checks: the first hop is not VALID, and the second hop no longer has a terminal previous state. After that, evaluateTransitionResume can allow resume_side_effect under the original denied/expired/consumed authorization. Terminal authority needs to remain sticky until an explicit new authorization/ref is present, not only on direct transitions to VALID.

Useful? React with 👍 / 👎.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (2) 📜 Skill insights (0)

Context used

Grey Divider


Action required

1. Resume ignores snapshot chain 📎 Requirement gap ⛨ Security
Description
evaluateTransitionResume() makes allow/deny decisions without validating that the provided
snapshot is the latest, forward-only snapshot in the previous_ref chain. This allows a caller to
evaluate an older, more-permissive snapshot and potentially resume when a newer snapshot should have
blocked continuation.
Code

packages/ls-continuity-store/src/trustworthy-transition.ts[R344-532]

+export function evaluateTransitionResume(
+  snapshot: StoredContinuityObject<TransitionContinuitySnapshotPayload>,
+  request: TransitionResumeRequest
+): TransitionResumeDecision {
+  verifyStoredSnapshot(snapshot);
+  const payload = snapshot.payload;
+
+  if (request.transition_id !== payload.transition_id) {
+    return decision(snapshot, false, "BLOCKED", "TRANSITION_MISMATCH");
+  }
+  if (request.subject_id !== payload.subject_id) {
+    return decision(snapshot, false, "BLOCKED", "SUBJECT_MISMATCH");
+  }
+  if (
+    request.action_identity_digest !== payload.action_identity_digest ||
+    request.binding_digest !== payload.binding_digest
+  ) {
+    return decision(snapshot, false, "BLOCKED", "ACTION_BINDING_MISMATCH");
+  }
+
+  if (request.operation === "report_only") {
+    const checks: string[] = [];
+    if (request.current_evidence_set_digest !== payload.evidence_set_digest) {
+      checks.push("evidence_set_changed_since_snapshot");
+    }
+    if (request.current_context_digest !== payload.context_digest) {
+      checks.push("context_changed_since_snapshot");
+    }
+    return decision(
+      snapshot,
+      true,
+      "REPORT_ONLY",
+      "HISTORICAL_REPORT_ONLY",
+      checks
+    );
+  }
+
+  if (request.current_evidence_set_digest !== payload.evidence_set_digest) {
+    return decision(snapshot, false, "REVALIDATE", "EVIDENCE_DRIFT", [
+      "rebuild_transition_snapshot"
+    ]);
+  }
+  if (request.current_context_digest !== payload.context_digest) {
+    return decision(snapshot, false, "REVALIDATE", "CONTEXT_DRIFT", [
+      "revalidate_runtime_context"
+    ]);
+  }
+
+  if (payload.dimensions.causal_validity === "INVALID") {
+    return decision(snapshot, false, "BLOCKED", "CAUSAL_LINEAGE_INVALID", [
+      "repair_causal_lineage"
+    ]);
+  }
+  if (
+    payload.dimensions.causal_validity === "UNKNOWN" ||
+    payload.dimensions.causal_validity === "NOT_EVALUATED"
+  ) {
+    return decision(
+      snapshot,
+      false,
+      "REVALIDATE",
+      "CAUSAL_LINEAGE_NOT_EVALUATED",
+      ["evaluate_causal_lineage"]
+    );
+  }
+
+  if (request.operation === "remediate_response") {
+    if (
+      payload.dimensions.response_integrity === "FAILED" ||
+      payload.dimensions.response_integrity === "PARTIAL"
+    ) {
+      return decision(
+        snapshot,
+        true,
+        "REMEDIATE_RESPONSE",
+        "RESPONSE_REMEDIATION_ONLY"
+      );
+    }
+    return decision(snapshot, false, "BLOCKED", "UNKNOWN_STATE", [
+      "no_response_remediation_required"
+    ]);
+  }
+
+  if (
+    payload.authority_expires_at !== null &&
+    parseTime(request.now, "now") >= parseTime(payload.authority_expires_at, "authority_expires_at")
+  ) {
+    return decision(snapshot, false, "BLOCKED", "AUTHORITY_EXPIRED", [
+      "obtain_fresh_authorization"
+    ]);
+  }
+
+  switch (payload.dimensions.authority) {
+    case "DENIED":
+      return decision(snapshot, false, "BLOCKED", "AUTHORITY_DENIED");
+    case "PENDING":
+      return decision(snapshot, false, "BLOCKED", "AUTHORITY_PENDING", [
+        "resolve_pending_approval"
+      ]);
+    case "EXPIRED":
+    case "EXPIRED_AT_REPORT":
+      return decision(snapshot, false, "BLOCKED", "AUTHORITY_EXPIRED", [
+        "obtain_fresh_authorization"
+      ]);
+    case "CONSUMED":
+      return decision(snapshot, false, "ALREADY_CONSUMED", "AUTHORITY_CONSUMED");
+    case "REVALIDATION_REQUIRED":
+      return decision(
+        snapshot,
+        false,
+        "REVALIDATE",
+        "AUTHORITY_REVALIDATION_REQUIRED",
+        ["revalidate_authority"]
+      );
+    case "NOT_EVALUATED":
+    case "UNKNOWN":
+      return decision(
+        snapshot,
+        false,
+        "REVALIDATE",
+        "AUTHORITY_NOT_EVALUATED",
+        ["evaluate_authority"]
+      );
+    case "VALID":
+      break;
+  }
+
+  if (
+    payload.side_effect_committed ||
+    payload.dimensions.execution === "OBSERVED_EXECUTED"
+  ) {
+    return decision(
+      snapshot,
+      false,
+      "ALREADY_CONSUMED",
+      payload.side_effect_committed
+        ? "SIDE_EFFECT_ALREADY_COMMITTED"
+        : "EXECUTION_ALREADY_OBSERVED"
+    );
+  }
+
+  if (payload.dimensions.execution === "OBSERVED_BLOCKED") {
+    return decision(snapshot, false, "BLOCKED", "EXECUTION_BLOCKED", [
+      "obtain_new_authorization"
+    ]);
+  }
+
+  if (payload.dimensions.response_integrity === "FAILED") {
+    return decision(
+      snapshot,
+      false,
+      "REMEDIATE_RESPONSE",
+      "RESPONSE_INTEGRITY_FAILED",
+      ["remediate_response_before_continuation"]
+    );
+  }
+  if (payload.dimensions.response_integrity === "PARTIAL") {
+    return decision(
+      snapshot,
+      false,
+      "REVALIDATE",
+      "RESPONSE_INTEGRITY_PARTIAL",
+      ["resolve_unverifiable_claims"]
+    );
+  }
+
+  if (payload.dimensions.execution === "OBSERVED_ERRORED") {
+    if (
+      request.operation === "retry_side_effect" &&
+      payload.retry.retryable_after_error &&
+      payload.retry.idempotency_key !== null &&
+      request.idempotency_key === payload.retry.idempotency_key
+    ) {
+      return decision(snapshot, true, "RETRY_SIDE_EFFECT", "OK");
+    }
+    return decision(snapshot, false, "REVALIDATE", "RETRY_NOT_PROVEN_SAFE", [
+      "prove_idempotent_retry"
+    ]);
+  }
+
+  if (
+    request.operation === "resume_side_effect" &&
+    payload.dimensions.execution === "NOT_OBSERVED"
+  ) {
+    return decision(snapshot, true, "CONTINUE_SIDE_EFFECT", "OK");
+  }
+
+  return decision(snapshot, false, "BLOCKED", "UNKNOWN_STATE");
+}
Evidence
PR Compliance ID 4 requires enforcing forward-only continuity via previous_ref to prevent rollback
to permissive state. The added evaluateTransitionResume() implementation performs resume
authorization solely from the provided snapshot and request fields, while chain enforcement exists
only as a separate helper (assessSnapshotChain) and is not required/used during resume evaluation.

Chained snapshots enforce forward-only continuity without rollback to permissive state
packages/ls-continuity-store/src/trustworthy-transition.ts[344-532]
packages/ls-continuity-store/src/trustworthy-transition.ts[542-606]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`evaluateTransitionResume()` does not enforce `previous_ref` chain monotonicity, so callers can evaluate stale snapshots and bypass forward-only continuity rules.

## Issue Context
The module defines `assessSnapshotChain(previous, current)`, but resume decisions are made without requiring a chain check or ensuring the evaluated snapshot is the latest/most-restrictive snapshot.

## Fix Focus Areas
- packages/ls-continuity-store/src/trustworthy-transition.ts[344-532]
- packages/ls-continuity-store/src/trustworthy-transition.ts[542-606]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Missing tampering fixture case 📎 Requirement gap ⛨ Security
Description
The new trustworthy-transition fixture suite does not include a tampering scenario, despite the
compliance requirement to cover snapshot tampering acceptance/fail-closed behavior. This leaves the
adapter’s restart/resume security properties unverified under adversarial mutation.
Code

packages/ls-continuity-store/tests/fixtures/trustworthy-transition-continuity-v0.1.json[R1-350]

+{
+  "fixture_version": "0.1",
+  "profile": "org.ls.trustworthy-transition-continuity.v0.1",
+  "cases": [
+    {
+      "case_id": "active_unobserved_resume",
+      "snapshot": {
+        "transition_id": "transition-001",
+        "subject_id": "agent:deploy",
+        "action_identity_digest": "$REF:action-001",
+        "binding_digest": "$REF:binding-001",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-001",
+          "observation_refs": [],
+          "response_integrity_ref": null,
+          "causal_audit_ref": "$REF:causal-001"
+        },
+        "dimensions": {
+          "authority": "VALID",
+          "execution": "NOT_OBSERVED",
+          "response_integrity": "NOT_EVALUATED",
+          "causal_validity": "VALID"
+        },
+        "side_effect_committed": false,
+        "authority_expires_at": "2030-01-01T01:00:00.000Z",
+        "context_digest": "$REF:context-001"
+      },
+      "request": {
+        "operation": "resume_side_effect",
+        "now": "2030-01-01T00:10:00.000Z"
+      },
+      "expected": {
+        "allowed": true,
+        "posture": "CONTINUE_SIDE_EFFECT",
+        "reason": "OK"
+      }
+    },
+    {
+      "case_id": "executed_restart_cannot_replay",
+      "snapshot": {
+        "transition_id": "transition-002",
+        "subject_id": "agent:deploy",
+        "action_identity_digest": "$REF:action-002",
+        "binding_digest": "$REF:binding-002",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-002",
+          "observation_refs": ["$REF:observation-002"],
+          "response_integrity_ref": "$REF:integrity-002",
+          "causal_audit_ref": "$REF:causal-002"
+        },
+        "dimensions": {
+          "authority": "VALID",
+          "execution": "OBSERVED_EXECUTED",
+          "response_integrity": "VERIFIED",
+          "causal_validity": "VALID"
+        },
+        "side_effect_committed": true,
+        "authority_expires_at": "2030-01-01T01:00:00.000Z",
+        "context_digest": "$REF:context-002"
+      },
+      "request": {
+        "operation": "retry_side_effect",
+        "now": "2030-01-01T00:10:00.000Z"
+      },
+      "expected": {
+        "allowed": false,
+        "posture": "ALREADY_CONSUMED",
+        "reason": "SIDE_EFFECT_ALREADY_COMMITTED"
+      }
+    },
+    {
+      "case_id": "expired_snapshot_cannot_resume",
+      "snapshot": {
+        "transition_id": "transition-003",
+        "subject_id": "agent:deploy",
+        "action_identity_digest": "$REF:action-003",
+        "binding_digest": "$REF:binding-003",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-003",
+          "observation_refs": [],
+          "response_integrity_ref": null,
+          "causal_audit_ref": "$REF:causal-003"
+        },
+        "dimensions": {
+          "authority": "VALID",
+          "execution": "NOT_OBSERVED",
+          "response_integrity": "NOT_EVALUATED",
+          "causal_validity": "VALID"
+        },
+        "side_effect_committed": false,
+        "authority_expires_at": "2030-01-01T00:05:00.000Z",
+        "context_digest": "$REF:context-003"
+      },
+      "request": {
+        "operation": "resume_side_effect",
+        "now": "2030-01-01T00:10:00.000Z"
+      },
+      "expected": {
+        "allowed": false,
+        "posture": "BLOCKED",
+        "reason": "AUTHORITY_EXPIRED"
+      }
+    },
+    {
+      "case_id": "exact_idempotent_retry_after_error",
+      "snapshot": {
+        "transition_id": "transition-004",
+        "subject_id": "agent:deploy",
+        "action_identity_digest": "$REF:action-004",
+        "binding_digest": "$REF:binding-004",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-004",
+          "observation_refs": ["$REF:observation-004"],
+          "response_integrity_ref": null,
+          "causal_audit_ref": "$REF:causal-004"
+        },
+        "dimensions": {
+          "authority": "VALID",
+          "execution": "OBSERVED_ERRORED",
+          "response_integrity": "NOT_EVALUATED",
+          "causal_validity": "VALID"
+        },
+        "side_effect_committed": false,
+        "authority_expires_at": "2030-01-01T01:00:00.000Z",
+        "context_digest": "$REF:context-004",
+        "retry": {
+          "retryable_after_error": true,
+          "idempotency_key": "retry-key-004"
+        }
+      },
+      "request": {
+        "operation": "retry_side_effect",
+        "now": "2030-01-01T00:10:00.000Z",
+        "idempotency_key": "retry-key-004"
+      },
+      "expected": {
+        "allowed": true,
+        "posture": "RETRY_SIDE_EFFECT",
+        "reason": "OK"
+      }
+    },
+    {
+      "case_id": "wrong_retry_key_requires_revalidation",
+      "snapshot": {
+        "transition_id": "transition-005",
+        "subject_id": "agent:deploy",
+        "action_identity_digest": "$REF:action-005",
+        "binding_digest": "$REF:binding-005",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-005",
+          "observation_refs": ["$REF:observation-005"],
+          "response_integrity_ref": null,
+          "causal_audit_ref": "$REF:causal-005"
+        },
+        "dimensions": {
+          "authority": "VALID",
+          "execution": "OBSERVED_ERRORED",
+          "response_integrity": "NOT_EVALUATED",
+          "causal_validity": "VALID"
+        },
+        "side_effect_committed": false,
+        "authority_expires_at": "2030-01-01T01:00:00.000Z",
+        "context_digest": "$REF:context-005",
+        "retry": {
+          "retryable_after_error": true,
+          "idempotency_key": "retry-key-005"
+        }
+      },
+      "request": {
+        "operation": "retry_side_effect",
+        "now": "2030-01-01T00:10:00.000Z",
+        "idempotency_key": "wrong-key"
+      },
+      "expected": {
+        "allowed": false,
+        "posture": "REVALIDATE",
+        "reason": "RETRY_NOT_PROVEN_SAFE"
+      }
+    },
+    {
+      "case_id": "failed_response_requires_remediation",
+      "snapshot": {
+        "transition_id": "transition-006",
+        "subject_id": "agent:reporter",
+        "action_identity_digest": "$REF:action-006",
+        "binding_digest": "$REF:binding-006",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-006",
+          "observation_refs": ["$REF:observation-006"],
+          "response_integrity_ref": "$REF:integrity-006",
+          "causal_audit_ref": "$REF:causal-006"
+        },
+        "dimensions": {
+          "authority": "VALID",
+          "execution": "OBSERVED_ERRORED",
+          "response_integrity": "FAILED",
+          "causal_validity": "VALID"
+        },
+        "side_effect_committed": false,
+        "authority_expires_at": "2030-01-01T01:00:00.000Z",
+        "context_digest": "$REF:context-006"
+      },
+      "request": {
+        "operation": "remediate_response",
+        "now": "2030-01-01T00:10:00.000Z"
+      },
+      "expected": {
+        "allowed": true,
+        "posture": "REMEDIATE_RESPONSE",
+        "reason": "RESPONSE_REMEDIATION_ONLY"
+      }
+    },
+    {
+      "case_id": "causal_invalid_blocks_continuation",
+      "snapshot": {
+        "transition_id": "transition-007",
+        "subject_id": "agent:deploy",
+        "action_identity_digest": "$REF:action-007",
+        "binding_digest": "$REF:binding-007",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-007",
+          "observation_refs": [],
+          "response_integrity_ref": null,
+          "causal_audit_ref": "$REF:causal-007"
+        },
+        "dimensions": {
+          "authority": "VALID",
+          "execution": "NOT_OBSERVED",
+          "response_integrity": "NOT_EVALUATED",
+          "causal_validity": "INVALID"
+        },
+        "side_effect_committed": false,
+        "authority_expires_at": "2030-01-01T01:00:00.000Z",
+        "context_digest": "$REF:context-007"
+      },
+      "request": {
+        "operation": "resume_side_effect",
+        "now": "2030-01-01T00:10:00.000Z"
+      },
+      "expected": {
+        "allowed": false,
+        "posture": "BLOCKED",
+        "reason": "CAUSAL_LINEAGE_INVALID"
+      }
+    },
+    {
+      "case_id": "evidence_drift_requires_new_snapshot",
+      "snapshot": {
+        "transition_id": "transition-008",
+        "subject_id": "agent:deploy",
+        "action_identity_digest": "$REF:action-008",
+        "binding_digest": "$REF:binding-008",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-008",
+          "observation_refs": [],
+          "response_integrity_ref": null,
+          "causal_audit_ref": "$REF:causal-008"
+        },
+        "dimensions": {
+          "authority": "VALID",
+          "execution": "NOT_OBSERVED",
+          "response_integrity": "NOT_EVALUATED",
+          "causal_validity": "VALID"
+        },
+        "side_effect_committed": false,
+        "authority_expires_at": "2030-01-01T01:00:00.000Z",
+        "context_digest": "$REF:context-008"
+      },
+      "request": {
+        "operation": "resume_side_effect",
+        "now": "2030-01-01T00:10:00.000Z",
+        "current_evidence_set_digest": "$REF:different-evidence"
+      },
+      "expected": {
+        "allowed": false,
+        "posture": "REVALIDATE",
+        "reason": "EVIDENCE_DRIFT"
+      }
+    },
+    {
+      "case_id": "stale_snapshot_remains_reportable",
+      "snapshot": {
+        "transition_id": "transition-009",
+        "subject_id": "agent:deploy",
+        "action_identity_digest": "$REF:action-009",
+        "binding_digest": "$REF:binding-009",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-009",
+          "observation_refs": ["$REF:observation-009"],
+          "response_integrity_ref": "$REF:integrity-009",
+          "causal_audit_ref": "$REF:causal-009"
+        },
+        "dimensions": {
+          "authority": "EXPIRED_AT_REPORT",
+          "execution": "OBSERVED_EXECUTED",
+          "response_integrity": "VERIFIED",
+          "causal_validity": "VALID"
+        },
+        "side_effect_committed": true,
+        "authority_expires_at": "2030-01-01T00:05:00.000Z",
+        "context_digest": "$REF:context-009"
+      },
+      "request": {
+        "operation": "report_only",
+        "now": "2030-01-01T00:10:00.000Z",
+        "current_evidence_set_digest": "$REF:newer-evidence",
+        "current_context_digest": "$REF:newer-context"
+      },
+      "expected": {
+        "allowed": true,
+        "posture": "REPORT_ONLY",
+        "reason": "HISTORICAL_REPORT_ONLY"
+      }
+    },
+    {
+      "case_id": "cross_subject_substitution_fails",
+      "snapshot": {
+        "transition_id": "transition-010",
+        "subject_id": "agent:deploy",
+        "action_identity_digest": "$REF:action-010",
+        "binding_digest": "$REF:binding-010",
+        "record_refs": {
+          "authorization_ref": "$REF:auth-010",
+          "observation_refs": [],
+          "response_integrity_ref": null,
+          "causal_audit_ref": "$REF:causal-010"
+        },
+        "dimensions": {
+          "authority": "VALID",
+          "execution": "NOT_OBSERVED",
+          "response_integrity": "NOT_EVALUATED",
+          "causal_validity": "VALID"
+        },
+        "side_effect_committed": false,
+        "authority_expires_at": "2030-01-01T01:00:00.000Z",
+        "context_digest": "$REF:context-010"
+      },
+      "request": {
+        "operation": "resume_side_effect",
+        "now": "2030-01-01T00:10:00.000Z",
+        "subject_id": "agent:other"
+      },
+      "expected": {
+        "allowed": false,
+        "posture": "BLOCKED",
+        "reason": "SUBJECT_MISMATCH"
+      }
+    }
+  ]
+}
Evidence
PR Compliance ID 8 requires fixtures that cover tampering. The added fixture file enumerates 10
restart/replay cases, and the test asserts exactly 10 cases are present and replays only those cases
after restart; no tampering fixture is included or exercised by this suite.

Provide fixtures covering restart/replay/stale snapshot/tampering/cross-subject substitution/digest-only evidence
packages/ls-continuity-store/tests/trustworthy-transition.test.ts[95-126]
packages/ls-continuity-store/tests/fixtures/trustworthy-transition-continuity-v0.1.json[1-350]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The fixture suite for the trustworthy-transition continuity adapter lacks a tampering case (e.g., one-byte mutation / payload mutation) to ensure mutated snapshots are rejected (fail closed).

## Issue Context
The test harness asserts the fixture contains exactly 10 cases and replays them after restart, but none of those cases represent a tampered snapshot. PR Compliance ID 8 explicitly requires fixtures for tampering scenarios.

## Fix Focus Areas
- packages/ls-continuity-store/tests/fixtures/trustworthy-transition-continuity-v0.1.json[1-350]
- packages/ls-continuity-store/tests/trustworthy-transition.test.ts[95-126]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Empty expires_at crashes evaluation 🐞 Bug ☼ Reliability
Description
createTransitionSnapshotEnvelope() only validates authority_expires_at when it is truthy, so an
empty string can be persisted without validation and later causes parseTime() to throw during
evaluateTransitionResume(). This turns malformed input into a runtime exception instead of returning
a deterministic BLOCKED/REVALIDATE decision.
Code

packages/ls-continuity-store/src/trustworthy-transition.ts[R228-233]

+  parseTime(createdAt, "created_at");
+  requireObjectRef(previousRef, "previous_ref");
+  requireObjectRef(input.context_digest, "context_digest");
+  requireObjectRef(input.reauthorization_ref ?? null, "reauthorization_ref");
+  if (input.authority_expires_at) parseTime(input.authority_expires_at, "authority_expires_at");
+
Evidence
The truthiness check allows an empty string to skip validation at creation, but the later non-null
check forces parsing and parseTime throws on invalid timestamps.

packages/ls-continuity-store/src/trustworthy-transition.ts[223-233]
packages/ls-continuity-store/src/trustworthy-transition.ts[182-186]
packages/ls-continuity-store/src/trustworthy-transition.ts[427-434]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`authority_expires_at` validation is inconsistent:
- Snapshot creation validates only when `input.authority_expires_at` is truthy, so `""` bypasses validation.
- Snapshot payload stores `authority_expires_at: input.authority_expires_at ?? null`, so `""` is persisted.
- Resume evaluation parses whenever `payload.authority_expires_at !== null`, so it attempts to parse `""` and throws.

## Issue Context
This creates a crash path for a plausible edge value (empty string from UI/config/JSON), breaking deterministic resume evaluation.

## Fix Focus Areas
- packages/ls-continuity-store/src/trustworthy-transition.ts[223-233]
- packages/ls-continuity-store/src/trustworthy-transition.ts[182-186]
- packages/ls-continuity-store/src/trustworthy-transition.ts[427-434]

## Suggested fix
1. Change the creation-time check to validate whenever the value is not null/undefined, not truthy:
  - `if (input.authority_expires_at != null) parseTime(input.authority_expires_at, ...)`
2. Consider normalizing empty strings to `null` (or rejecting them) before persisting:
  - e.g., `const expiresAt = input.authority_expires_at?.trim() || null;`
3. Optionally harden `verifyStoredSnapshot()` to validate `authority_expires_at` (if non-null) so malformed stored snapshots fail fast with a controlled error.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Unvalidated dimensions fail-open 🐞 Bug ⛨ Security
Description
evaluateTransitionResume() has no fail-closed handling for unexpected persisted dimension values
(notably authority), and verification_receipt payloads are not runtime-validated; a snapshot
containing an unknown authority/causal/integrity string can bypass safety branches and reach
CONTINUE_SIDE_EFFECT. This can incorrectly allow resuming a side effect from a malformed or
cross-producer snapshot instead of returning BLOCKED/REVALIDATE.
Code

packages/ls-continuity-store/src/trustworthy-transition.ts[R436-469]

+  switch (payload.dimensions.authority) {
+    case "DENIED":
+      return decision(snapshot, false, "BLOCKED", "AUTHORITY_DENIED");
+    case "PENDING":
+      return decision(snapshot, false, "BLOCKED", "AUTHORITY_PENDING", [
+        "resolve_pending_approval"
+      ]);
+    case "EXPIRED":
+    case "EXPIRED_AT_REPORT":
+      return decision(snapshot, false, "BLOCKED", "AUTHORITY_EXPIRED", [
+        "obtain_fresh_authorization"
+      ]);
+    case "CONSUMED":
+      return decision(snapshot, false, "ALREADY_CONSUMED", "AUTHORITY_CONSUMED");
+    case "REVALIDATION_REQUIRED":
+      return decision(
+        snapshot,
+        false,
+        "REVALIDATE",
+        "AUTHORITY_REVALIDATION_REQUIRED",
+        ["revalidate_authority"]
+      );
+    case "NOT_EVALUATED":
+    case "UNKNOWN":
+      return decision(
+        snapshot,
+        false,
+        "REVALIDATE",
+        "AUTHORITY_NOT_EVALUATED",
+        ["evaluate_authority"]
+      );
+    case "VALID":
+      break;
+  }
Evidence
The store only validates envelopes, not verification_receipt payload structure, and the resume
logic’s authority switch has no default—so unknown runtime values can bypass blocking/revalidation
branches and proceed.

packages/ls-continuity-store/src/schema.ts[84-92]
packages/ls-continuity-store/src/store.ts[34-43]
packages/ls-continuity-store/src/trustworthy-transition.ts[309-325]
packages/ls-continuity-store/src/trustworthy-transition.ts[392-408]
packages/ls-continuity-store/src/trustworthy-transition.ts[436-469]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`verification_receipt` payloads are accepted as `z.record(z.unknown())`, and `verifyStoredSnapshot()` only checks profile/subject/evidence_set_digest. In `evaluateTransitionResume()`, the `authority` switch has no `default`, so any unexpected runtime string (e.g., from a cross-producer snapshot or untyped JSON) will fall through as if authority were acceptable and may allow `CONTINUE_SIDE_EFFECT`.

## Issue Context
TypeScript unions protect well-typed call sites, but persisted data can still be malformed (other producers writing to the same store, older snapshots, manual fixtures, or accidental `as any`). The adapter should fail closed on any unknown dimension values.

## Fix Focus Areas
- packages/ls-continuity-store/src/schema.ts[84-92]
- packages/ls-continuity-store/src/store.ts[34-43]
- packages/ls-continuity-store/src/trustworthy-transition.ts[309-325]
- packages/ls-continuity-store/src/trustworthy-transition.ts[392-469]

## Suggested fix
1. Add a runtime validator for `TransitionContinuitySnapshotPayload` (zod schema) that enforces:
  - `dimensions.authority/execution/response_integrity/causal_validity` are in the allowed enums
  - `context_digest` matches `sha256:<hex>`
  - optional times are either `null` or valid datetime strings
2. Call this validator inside `verifyStoredSnapshot()` (and optionally in `createTransitionSnapshotEnvelope()` as a final guard).
3. Add `default` branches (or explicit else fallbacks) in `evaluateTransitionResume()` for any dimension comparisons/switches so that unknown values return a deterministic fail-closed decision (e.g., `BLOCKED`/`REVALIDATE` with reason `UNKNOWN_STATE`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +344 to +532
export function evaluateTransitionResume(
snapshot: StoredContinuityObject<TransitionContinuitySnapshotPayload>,
request: TransitionResumeRequest
): TransitionResumeDecision {
verifyStoredSnapshot(snapshot);
const payload = snapshot.payload;

if (request.transition_id !== payload.transition_id) {
return decision(snapshot, false, "BLOCKED", "TRANSITION_MISMATCH");
}
if (request.subject_id !== payload.subject_id) {
return decision(snapshot, false, "BLOCKED", "SUBJECT_MISMATCH");
}
if (
request.action_identity_digest !== payload.action_identity_digest ||
request.binding_digest !== payload.binding_digest
) {
return decision(snapshot, false, "BLOCKED", "ACTION_BINDING_MISMATCH");
}

if (request.operation === "report_only") {
const checks: string[] = [];
if (request.current_evidence_set_digest !== payload.evidence_set_digest) {
checks.push("evidence_set_changed_since_snapshot");
}
if (request.current_context_digest !== payload.context_digest) {
checks.push("context_changed_since_snapshot");
}
return decision(
snapshot,
true,
"REPORT_ONLY",
"HISTORICAL_REPORT_ONLY",
checks
);
}

if (request.current_evidence_set_digest !== payload.evidence_set_digest) {
return decision(snapshot, false, "REVALIDATE", "EVIDENCE_DRIFT", [
"rebuild_transition_snapshot"
]);
}
if (request.current_context_digest !== payload.context_digest) {
return decision(snapshot, false, "REVALIDATE", "CONTEXT_DRIFT", [
"revalidate_runtime_context"
]);
}

if (payload.dimensions.causal_validity === "INVALID") {
return decision(snapshot, false, "BLOCKED", "CAUSAL_LINEAGE_INVALID", [
"repair_causal_lineage"
]);
}
if (
payload.dimensions.causal_validity === "UNKNOWN" ||
payload.dimensions.causal_validity === "NOT_EVALUATED"
) {
return decision(
snapshot,
false,
"REVALIDATE",
"CAUSAL_LINEAGE_NOT_EVALUATED",
["evaluate_causal_lineage"]
);
}

if (request.operation === "remediate_response") {
if (
payload.dimensions.response_integrity === "FAILED" ||
payload.dimensions.response_integrity === "PARTIAL"
) {
return decision(
snapshot,
true,
"REMEDIATE_RESPONSE",
"RESPONSE_REMEDIATION_ONLY"
);
}
return decision(snapshot, false, "BLOCKED", "UNKNOWN_STATE", [
"no_response_remediation_required"
]);
}

if (
payload.authority_expires_at !== null &&
parseTime(request.now, "now") >= parseTime(payload.authority_expires_at, "authority_expires_at")
) {
return decision(snapshot, false, "BLOCKED", "AUTHORITY_EXPIRED", [
"obtain_fresh_authorization"
]);
}

switch (payload.dimensions.authority) {
case "DENIED":
return decision(snapshot, false, "BLOCKED", "AUTHORITY_DENIED");
case "PENDING":
return decision(snapshot, false, "BLOCKED", "AUTHORITY_PENDING", [
"resolve_pending_approval"
]);
case "EXPIRED":
case "EXPIRED_AT_REPORT":
return decision(snapshot, false, "BLOCKED", "AUTHORITY_EXPIRED", [
"obtain_fresh_authorization"
]);
case "CONSUMED":
return decision(snapshot, false, "ALREADY_CONSUMED", "AUTHORITY_CONSUMED");
case "REVALIDATION_REQUIRED":
return decision(
snapshot,
false,
"REVALIDATE",
"AUTHORITY_REVALIDATION_REQUIRED",
["revalidate_authority"]
);
case "NOT_EVALUATED":
case "UNKNOWN":
return decision(
snapshot,
false,
"REVALIDATE",
"AUTHORITY_NOT_EVALUATED",
["evaluate_authority"]
);
case "VALID":
break;
}

if (
payload.side_effect_committed ||
payload.dimensions.execution === "OBSERVED_EXECUTED"
) {
return decision(
snapshot,
false,
"ALREADY_CONSUMED",
payload.side_effect_committed
? "SIDE_EFFECT_ALREADY_COMMITTED"
: "EXECUTION_ALREADY_OBSERVED"
);
}

if (payload.dimensions.execution === "OBSERVED_BLOCKED") {
return decision(snapshot, false, "BLOCKED", "EXECUTION_BLOCKED", [
"obtain_new_authorization"
]);
}

if (payload.dimensions.response_integrity === "FAILED") {
return decision(
snapshot,
false,
"REMEDIATE_RESPONSE",
"RESPONSE_INTEGRITY_FAILED",
["remediate_response_before_continuation"]
);
}
if (payload.dimensions.response_integrity === "PARTIAL") {
return decision(
snapshot,
false,
"REVALIDATE",
"RESPONSE_INTEGRITY_PARTIAL",
["resolve_unverifiable_claims"]
);
}

if (payload.dimensions.execution === "OBSERVED_ERRORED") {
if (
request.operation === "retry_side_effect" &&
payload.retry.retryable_after_error &&
payload.retry.idempotency_key !== null &&
request.idempotency_key === payload.retry.idempotency_key
) {
return decision(snapshot, true, "RETRY_SIDE_EFFECT", "OK");
}
return decision(snapshot, false, "REVALIDATE", "RETRY_NOT_PROVEN_SAFE", [
"prove_idempotent_retry"
]);
}

if (
request.operation === "resume_side_effect" &&
payload.dimensions.execution === "NOT_OBSERVED"
) {
return decision(snapshot, true, "CONTINUE_SIDE_EFFECT", "OK");
}

return decision(snapshot, false, "BLOCKED", "UNKNOWN_STATE");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Resume ignores snapshot chain 📎 Requirement gap ⛨ Security

evaluateTransitionResume() makes allow/deny decisions without validating that the provided
snapshot is the latest, forward-only snapshot in the previous_ref chain. This allows a caller to
evaluate an older, more-permissive snapshot and potentially resume when a newer snapshot should have
blocked continuation.
Agent Prompt
## Issue description
`evaluateTransitionResume()` does not enforce `previous_ref` chain monotonicity, so callers can evaluate stale snapshots and bypass forward-only continuity rules.

## Issue Context
The module defines `assessSnapshotChain(previous, current)`, but resume decisions are made without requiring a chain check or ensuring the evaluated snapshot is the latest/most-restrictive snapshot.

## Fix Focus Areas
- packages/ls-continuity-store/src/trustworthy-transition.ts[344-532]
- packages/ls-continuity-store/src/trustworthy-transition.ts[542-606]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +1 to +350
{
"fixture_version": "0.1",
"profile": "org.ls.trustworthy-transition-continuity.v0.1",
"cases": [
{
"case_id": "active_unobserved_resume",
"snapshot": {
"transition_id": "transition-001",
"subject_id": "agent:deploy",
"action_identity_digest": "$REF:action-001",
"binding_digest": "$REF:binding-001",
"record_refs": {
"authorization_ref": "$REF:auth-001",
"observation_refs": [],
"response_integrity_ref": null,
"causal_audit_ref": "$REF:causal-001"
},
"dimensions": {
"authority": "VALID",
"execution": "NOT_OBSERVED",
"response_integrity": "NOT_EVALUATED",
"causal_validity": "VALID"
},
"side_effect_committed": false,
"authority_expires_at": "2030-01-01T01:00:00.000Z",
"context_digest": "$REF:context-001"
},
"request": {
"operation": "resume_side_effect",
"now": "2030-01-01T00:10:00.000Z"
},
"expected": {
"allowed": true,
"posture": "CONTINUE_SIDE_EFFECT",
"reason": "OK"
}
},
{
"case_id": "executed_restart_cannot_replay",
"snapshot": {
"transition_id": "transition-002",
"subject_id": "agent:deploy",
"action_identity_digest": "$REF:action-002",
"binding_digest": "$REF:binding-002",
"record_refs": {
"authorization_ref": "$REF:auth-002",
"observation_refs": ["$REF:observation-002"],
"response_integrity_ref": "$REF:integrity-002",
"causal_audit_ref": "$REF:causal-002"
},
"dimensions": {
"authority": "VALID",
"execution": "OBSERVED_EXECUTED",
"response_integrity": "VERIFIED",
"causal_validity": "VALID"
},
"side_effect_committed": true,
"authority_expires_at": "2030-01-01T01:00:00.000Z",
"context_digest": "$REF:context-002"
},
"request": {
"operation": "retry_side_effect",
"now": "2030-01-01T00:10:00.000Z"
},
"expected": {
"allowed": false,
"posture": "ALREADY_CONSUMED",
"reason": "SIDE_EFFECT_ALREADY_COMMITTED"
}
},
{
"case_id": "expired_snapshot_cannot_resume",
"snapshot": {
"transition_id": "transition-003",
"subject_id": "agent:deploy",
"action_identity_digest": "$REF:action-003",
"binding_digest": "$REF:binding-003",
"record_refs": {
"authorization_ref": "$REF:auth-003",
"observation_refs": [],
"response_integrity_ref": null,
"causal_audit_ref": "$REF:causal-003"
},
"dimensions": {
"authority": "VALID",
"execution": "NOT_OBSERVED",
"response_integrity": "NOT_EVALUATED",
"causal_validity": "VALID"
},
"side_effect_committed": false,
"authority_expires_at": "2030-01-01T00:05:00.000Z",
"context_digest": "$REF:context-003"
},
"request": {
"operation": "resume_side_effect",
"now": "2030-01-01T00:10:00.000Z"
},
"expected": {
"allowed": false,
"posture": "BLOCKED",
"reason": "AUTHORITY_EXPIRED"
}
},
{
"case_id": "exact_idempotent_retry_after_error",
"snapshot": {
"transition_id": "transition-004",
"subject_id": "agent:deploy",
"action_identity_digest": "$REF:action-004",
"binding_digest": "$REF:binding-004",
"record_refs": {
"authorization_ref": "$REF:auth-004",
"observation_refs": ["$REF:observation-004"],
"response_integrity_ref": null,
"causal_audit_ref": "$REF:causal-004"
},
"dimensions": {
"authority": "VALID",
"execution": "OBSERVED_ERRORED",
"response_integrity": "NOT_EVALUATED",
"causal_validity": "VALID"
},
"side_effect_committed": false,
"authority_expires_at": "2030-01-01T01:00:00.000Z",
"context_digest": "$REF:context-004",
"retry": {
"retryable_after_error": true,
"idempotency_key": "retry-key-004"
}
},
"request": {
"operation": "retry_side_effect",
"now": "2030-01-01T00:10:00.000Z",
"idempotency_key": "retry-key-004"
},
"expected": {
"allowed": true,
"posture": "RETRY_SIDE_EFFECT",
"reason": "OK"
}
},
{
"case_id": "wrong_retry_key_requires_revalidation",
"snapshot": {
"transition_id": "transition-005",
"subject_id": "agent:deploy",
"action_identity_digest": "$REF:action-005",
"binding_digest": "$REF:binding-005",
"record_refs": {
"authorization_ref": "$REF:auth-005",
"observation_refs": ["$REF:observation-005"],
"response_integrity_ref": null,
"causal_audit_ref": "$REF:causal-005"
},
"dimensions": {
"authority": "VALID",
"execution": "OBSERVED_ERRORED",
"response_integrity": "NOT_EVALUATED",
"causal_validity": "VALID"
},
"side_effect_committed": false,
"authority_expires_at": "2030-01-01T01:00:00.000Z",
"context_digest": "$REF:context-005",
"retry": {
"retryable_after_error": true,
"idempotency_key": "retry-key-005"
}
},
"request": {
"operation": "retry_side_effect",
"now": "2030-01-01T00:10:00.000Z",
"idempotency_key": "wrong-key"
},
"expected": {
"allowed": false,
"posture": "REVALIDATE",
"reason": "RETRY_NOT_PROVEN_SAFE"
}
},
{
"case_id": "failed_response_requires_remediation",
"snapshot": {
"transition_id": "transition-006",
"subject_id": "agent:reporter",
"action_identity_digest": "$REF:action-006",
"binding_digest": "$REF:binding-006",
"record_refs": {
"authorization_ref": "$REF:auth-006",
"observation_refs": ["$REF:observation-006"],
"response_integrity_ref": "$REF:integrity-006",
"causal_audit_ref": "$REF:causal-006"
},
"dimensions": {
"authority": "VALID",
"execution": "OBSERVED_ERRORED",
"response_integrity": "FAILED",
"causal_validity": "VALID"
},
"side_effect_committed": false,
"authority_expires_at": "2030-01-01T01:00:00.000Z",
"context_digest": "$REF:context-006"
},
"request": {
"operation": "remediate_response",
"now": "2030-01-01T00:10:00.000Z"
},
"expected": {
"allowed": true,
"posture": "REMEDIATE_RESPONSE",
"reason": "RESPONSE_REMEDIATION_ONLY"
}
},
{
"case_id": "causal_invalid_blocks_continuation",
"snapshot": {
"transition_id": "transition-007",
"subject_id": "agent:deploy",
"action_identity_digest": "$REF:action-007",
"binding_digest": "$REF:binding-007",
"record_refs": {
"authorization_ref": "$REF:auth-007",
"observation_refs": [],
"response_integrity_ref": null,
"causal_audit_ref": "$REF:causal-007"
},
"dimensions": {
"authority": "VALID",
"execution": "NOT_OBSERVED",
"response_integrity": "NOT_EVALUATED",
"causal_validity": "INVALID"
},
"side_effect_committed": false,
"authority_expires_at": "2030-01-01T01:00:00.000Z",
"context_digest": "$REF:context-007"
},
"request": {
"operation": "resume_side_effect",
"now": "2030-01-01T00:10:00.000Z"
},
"expected": {
"allowed": false,
"posture": "BLOCKED",
"reason": "CAUSAL_LINEAGE_INVALID"
}
},
{
"case_id": "evidence_drift_requires_new_snapshot",
"snapshot": {
"transition_id": "transition-008",
"subject_id": "agent:deploy",
"action_identity_digest": "$REF:action-008",
"binding_digest": "$REF:binding-008",
"record_refs": {
"authorization_ref": "$REF:auth-008",
"observation_refs": [],
"response_integrity_ref": null,
"causal_audit_ref": "$REF:causal-008"
},
"dimensions": {
"authority": "VALID",
"execution": "NOT_OBSERVED",
"response_integrity": "NOT_EVALUATED",
"causal_validity": "VALID"
},
"side_effect_committed": false,
"authority_expires_at": "2030-01-01T01:00:00.000Z",
"context_digest": "$REF:context-008"
},
"request": {
"operation": "resume_side_effect",
"now": "2030-01-01T00:10:00.000Z",
"current_evidence_set_digest": "$REF:different-evidence"
},
"expected": {
"allowed": false,
"posture": "REVALIDATE",
"reason": "EVIDENCE_DRIFT"
}
},
{
"case_id": "stale_snapshot_remains_reportable",
"snapshot": {
"transition_id": "transition-009",
"subject_id": "agent:deploy",
"action_identity_digest": "$REF:action-009",
"binding_digest": "$REF:binding-009",
"record_refs": {
"authorization_ref": "$REF:auth-009",
"observation_refs": ["$REF:observation-009"],
"response_integrity_ref": "$REF:integrity-009",
"causal_audit_ref": "$REF:causal-009"
},
"dimensions": {
"authority": "EXPIRED_AT_REPORT",
"execution": "OBSERVED_EXECUTED",
"response_integrity": "VERIFIED",
"causal_validity": "VALID"
},
"side_effect_committed": true,
"authority_expires_at": "2030-01-01T00:05:00.000Z",
"context_digest": "$REF:context-009"
},
"request": {
"operation": "report_only",
"now": "2030-01-01T00:10:00.000Z",
"current_evidence_set_digest": "$REF:newer-evidence",
"current_context_digest": "$REF:newer-context"
},
"expected": {
"allowed": true,
"posture": "REPORT_ONLY",
"reason": "HISTORICAL_REPORT_ONLY"
}
},
{
"case_id": "cross_subject_substitution_fails",
"snapshot": {
"transition_id": "transition-010",
"subject_id": "agent:deploy",
"action_identity_digest": "$REF:action-010",
"binding_digest": "$REF:binding-010",
"record_refs": {
"authorization_ref": "$REF:auth-010",
"observation_refs": [],
"response_integrity_ref": null,
"causal_audit_ref": "$REF:causal-010"
},
"dimensions": {
"authority": "VALID",
"execution": "NOT_OBSERVED",
"response_integrity": "NOT_EVALUATED",
"causal_validity": "VALID"
},
"side_effect_committed": false,
"authority_expires_at": "2030-01-01T01:00:00.000Z",
"context_digest": "$REF:context-010"
},
"request": {
"operation": "resume_side_effect",
"now": "2030-01-01T00:10:00.000Z",
"subject_id": "agent:other"
},
"expected": {
"allowed": false,
"posture": "BLOCKED",
"reason": "SUBJECT_MISMATCH"
}
}
]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Missing tampering fixture case 📎 Requirement gap ⛨ Security

The new trustworthy-transition fixture suite does not include a tampering scenario, despite the
compliance requirement to cover snapshot tampering acceptance/fail-closed behavior. This leaves the
adapter’s restart/resume security properties unverified under adversarial mutation.
Agent Prompt
## Issue description
The fixture suite for the trustworthy-transition continuity adapter lacks a tampering case (e.g., one-byte mutation / payload mutation) to ensure mutated snapshots are rejected (fail closed).

## Issue Context
The test harness asserts the fixture contains exactly 10 cases and replays them after restart, but none of those cases represent a tampered snapshot. PR Compliance ID 8 explicitly requires fixtures for tampering scenarios.

## Fix Focus Areas
- packages/ls-continuity-store/tests/fixtures/trustworthy-transition-continuity-v0.1.json[1-350]
- packages/ls-continuity-store/tests/trustworthy-transition.test.ts[95-126]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +436 to +469
switch (payload.dimensions.authority) {
case "DENIED":
return decision(snapshot, false, "BLOCKED", "AUTHORITY_DENIED");
case "PENDING":
return decision(snapshot, false, "BLOCKED", "AUTHORITY_PENDING", [
"resolve_pending_approval"
]);
case "EXPIRED":
case "EXPIRED_AT_REPORT":
return decision(snapshot, false, "BLOCKED", "AUTHORITY_EXPIRED", [
"obtain_fresh_authorization"
]);
case "CONSUMED":
return decision(snapshot, false, "ALREADY_CONSUMED", "AUTHORITY_CONSUMED");
case "REVALIDATION_REQUIRED":
return decision(
snapshot,
false,
"REVALIDATE",
"AUTHORITY_REVALIDATION_REQUIRED",
["revalidate_authority"]
);
case "NOT_EVALUATED":
case "UNKNOWN":
return decision(
snapshot,
false,
"REVALIDATE",
"AUTHORITY_NOT_EVALUATED",
["evaluate_authority"]
);
case "VALID":
break;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Unvalidated dimensions fail-open 🐞 Bug ⛨ Security

evaluateTransitionResume() has no fail-closed handling for unexpected persisted dimension values
(notably authority), and verification_receipt payloads are not runtime-validated; a snapshot
containing an unknown authority/causal/integrity string can bypass safety branches and reach
CONTINUE_SIDE_EFFECT. This can incorrectly allow resuming a side effect from a malformed or
cross-producer snapshot instead of returning BLOCKED/REVALIDATE.
Agent Prompt
## Issue description
`verification_receipt` payloads are accepted as `z.record(z.unknown())`, and `verifyStoredSnapshot()` only checks profile/subject/evidence_set_digest. In `evaluateTransitionResume()`, the `authority` switch has no `default`, so any unexpected runtime string (e.g., from a cross-producer snapshot or untyped JSON) will fall through as if authority were acceptable and may allow `CONTINUE_SIDE_EFFECT`.

## Issue Context
TypeScript unions protect well-typed call sites, but persisted data can still be malformed (other producers writing to the same store, older snapshots, manual fixtures, or accidental `as any`). The adapter should fail closed on any unknown dimension values.

## Fix Focus Areas
- packages/ls-continuity-store/src/schema.ts[84-92]
- packages/ls-continuity-store/src/store.ts[34-43]
- packages/ls-continuity-store/src/trustworthy-transition.ts[309-325]
- packages/ls-continuity-store/src/trustworthy-transition.ts[392-469]

## Suggested fix
1. Add a runtime validator for `TransitionContinuitySnapshotPayload` (zod schema) that enforces:
   - `dimensions.authority/execution/response_integrity/causal_validity` are in the allowed enums
   - `context_digest` matches `sha256:<hex>`
   - optional times are either `null` or valid datetime strings
2. Call this validator inside `verifyStoredSnapshot()` (and optionally in `createTransitionSnapshotEnvelope()` as a final guard).
3. Add `default` branches (or explicit else fallbacks) in `evaluateTransitionResume()` for any dimension comparisons/switches so that unknown values return a deterministic fail-closed decision (e.g., `BLOCKED`/`REVALIDATE` with reason `UNKNOWN_STATE`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +228 to +233
parseTime(createdAt, "created_at");
requireObjectRef(previousRef, "previous_ref");
requireObjectRef(input.context_digest, "context_digest");
requireObjectRef(input.reauthorization_ref ?? null, "reauthorization_ref");
if (input.authority_expires_at) parseTime(input.authority_expires_at, "authority_expires_at");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Empty expires_at crashes evaluation 🐞 Bug ☼ Reliability

createTransitionSnapshotEnvelope() only validates authority_expires_at when it is truthy, so an
empty string can be persisted without validation and later causes parseTime() to throw during
evaluateTransitionResume(). This turns malformed input into a runtime exception instead of returning
a deterministic BLOCKED/REVALIDATE decision.
Agent Prompt
## Issue description
`authority_expires_at` validation is inconsistent:
- Snapshot creation validates only when `input.authority_expires_at` is truthy, so `""` bypasses validation.
- Snapshot payload stores `authority_expires_at: input.authority_expires_at ?? null`, so `""` is persisted.
- Resume evaluation parses whenever `payload.authority_expires_at !== null`, so it attempts to parse `""` and throws.

## Issue Context
This creates a crash path for a plausible edge value (empty string from UI/config/JSON), breaking deterministic resume evaluation.

## Fix Focus Areas
- packages/ls-continuity-store/src/trustworthy-transition.ts[223-233]
- packages/ls-continuity-store/src/trustworthy-transition.ts[182-186]
- packages/ls-continuity-store/src/trustworthy-transition.ts[427-434]

## Suggested fix
1. Change the creation-time check to validate whenever the value is not null/undefined, not truthy:
   - `if (input.authority_expires_at != null) parseTime(input.authority_expires_at, ...)`
2. Consider normalizing empty strings to `null` (or rejecting them) before persisting:
   - e.g., `const expiresAt = input.authority_expires_at?.trim() || null;`
3. Optionally harden `verifyStoredSnapshot()` to validate `authority_expires_at` (if non-null) so malformed stored snapshots fail fast with a controlled error.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/ls-continuity-store/tests/fixtures/trustworthy-transition-continuity-v0.1.json (1)

4-349: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Fixture covers the main happy/blocked paths but misses several fail-closed branches.

All 10 cases check out against evaluateTransitionResume's branching order (identity checks, report_only, evidence/context drift, causal validity, remediation, expiry, side-effect/execution consumption, subject substitution). However, several documented resume postures and safety branches aren't exercised anywhere in this fixture-driven suite:

  • authority states DENIED, PENDING, CONSUMED (via the authority switch, not the side-effect-committed short-circuit), REVALIDATION_REQUIRED, NOT_EVALUATED/UNKNOWN.
  • causal_validity states UNKNOWN/NOT_EVALUATED (only VALID/INVALID are covered).
  • response_integrity: PARTIAL, and FAILED reached via a non-remediate_response operation (the direct RESPONSE_INTEGRITY_FAILED branch).
  • execution: OBSERVED_BLOCKED.

Given the PR's explicit emphasis on fail-closed behavior and adversarial test coverage, adding a few more cases for these branches would materially increase confidence in the safety-critical resume logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ls-continuity-store/tests/fixtures/trustworthy-transition-continuity-v0.1.json`
around lines 4 - 349, The fixture for evaluateTransitionResume is missing
several fail-closed branches, so add cases that exercise the uncovered
authority, causal, response-integrity, and execution states. Extend
trustworthy-transition-continuity-v0.1.json with scenarios for authority values
like DENIED, PENDING, CONSUMED, REVALIDATION_REQUIRED, and
UNKNOWN/NOT_EVALUATED, plus causal_validity UNKNOWN/NOT_EVALUATED,
response_integrity PARTIAL and FAILED on a non-remediate_response path, and
execution OBSERVED_BLOCKED. Keep the new cases aligned with the existing
case_id/request/expected pattern so the branching order in
evaluateTransitionResume is validated end-to-end.
packages/ls-continuity-store/src/transition-demo.ts (1)

17-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Temp directory from mkdtempSync is never removed.

root is created via fs.mkdtempSync but nothing calls fs.rmSync(root, { recursive: true, force: true }), even on the happy path. Repeated local runs of npm run demo:transition will accumulate orphaned directories under the OS tmp folder.

♻️ Proposed cleanup
 const root = fs.mkdtempSync(path.join(os.tmpdir(), "ls-transition-demo-"));
 const databasePath = path.join(root, "continuity.db");
 const objectsPath = path.join(root, "objects");
 
-const db = openDatabase(databasePath);
-const store = new ContinuityStore(db, objectsPath);
-const snapshot = persistTransitionSnapshot(
-  store,
-  { ... },
-  "2030-01-01T00:00:00.000Z"
-);
-db.close();
-
-const reopenedDb = openDatabase(databasePath);
-const reopenedStore = new ContinuityStore(reopenedDb, objectsPath);
-const recovered = reopenedStore.load<typeof snapshot.payload>(snapshot.object_id);
-
-...
-
-reopenedDb.close();
+try {
+  const db = openDatabase(databasePath);
+  const store = new ContinuityStore(db, objectsPath);
+  const snapshot = persistTransitionSnapshot(
+    store,
+    { ... },
+    "2030-01-01T00:00:00.000Z"
+  );
+  db.close();
+
+  const reopenedDb = openDatabase(databasePath);
+  const reopenedStore = new ContinuityStore(reopenedDb, objectsPath);
+  const recovered = reopenedStore.load<typeof snapshot.payload>(snapshot.object_id);
+
+  ...
+
+  reopenedDb.close();
+} finally {
+  fs.rmSync(root, { recursive: true, force: true });
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ls-continuity-store/src/transition-demo.ts` around lines 17 - 89,
The demo in transition-demo.ts creates a temp directory with fs.mkdtempSync via
root, but never removes it after use. Add cleanup around the existing
openDatabase/ContinuityStore/persistTransitionSnapshot flow so the temporary
root is removed with fs.rmSync(..., { recursive: true, force: true }) even on
success or failure, and make sure both reopenedDb and the temp directory are
cleaned up reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ls-continuity-store.yml:
- Line 25: The checkout step in the ls-continuity-store workflow should disable
persisted git credentials because this job is read-only. Update the
actions/checkout@v4 usage in the workflow to set persist-credentials to false so
no token is written to disk.

In `@packages/ls-continuity-store/src/trustworthy-transition.ts`:
- Line 232: The creation path in trustworthy-transition.ts is letting
empty-string authority_expires_at slip through because the validation guard only
checks truthiness and the payload later preserves "" via nullish coalescing.
Update the validation around the input.authority_expires_at check so any
non-null value, including "", is passed to parseTime and rejected immediately if
invalid; also make sure the payload construction normalizes empty strings to
null before it reaches evaluateTransitionResume.

---

Nitpick comments:
In `@packages/ls-continuity-store/src/transition-demo.ts`:
- Around line 17-89: The demo in transition-demo.ts creates a temp directory
with fs.mkdtempSync via root, but never removes it after use. Add cleanup around
the existing openDatabase/ContinuityStore/persistTransitionSnapshot flow so the
temporary root is removed with fs.rmSync(..., { recursive: true, force: true })
even on success or failure, and make sure both reopenedDb and the temp directory
are cleaned up reliably.

In
`@packages/ls-continuity-store/tests/fixtures/trustworthy-transition-continuity-v0.1.json`:
- Around line 4-349: The fixture for evaluateTransitionResume is missing several
fail-closed branches, so add cases that exercise the uncovered authority,
causal, response-integrity, and execution states. Extend
trustworthy-transition-continuity-v0.1.json with scenarios for authority values
like DENIED, PENDING, CONSUMED, REVALIDATION_REQUIRED, and
UNKNOWN/NOT_EVALUATED, plus causal_validity UNKNOWN/NOT_EVALUATED,
response_integrity PARTIAL and FAILED on a non-remediate_response path, and
execution OBSERVED_BLOCKED. Keep the new cases aligned with the existing
case_id/request/expected pattern so the branching order in
evaluateTransitionResume is validated end-to-end.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ccf533d-86af-4ee8-ba20-f805d7b98f47

📥 Commits

Reviewing files that changed from the base of the PR and between 746b177 and 0ed6613.

📒 Files selected for processing (9)
  • .github/workflows/ls-continuity-store.yml
  • packages/ls-continuity-store/README.md
  • packages/ls-continuity-store/docs/TRUSTWORTHY_TRANSITION_CONTINUITY_V0_1.md
  • packages/ls-continuity-store/package.json
  • packages/ls-continuity-store/src/demo.ts
  • packages/ls-continuity-store/src/transition-demo.ts
  • packages/ls-continuity-store/src/trustworthy-transition.ts
  • packages/ls-continuity-store/tests/fixtures/trustworthy-transition-continuity-v0.1.json
  • packages/ls-continuity-store/tests/trustworthy-transition.test.ts

run:
working-directory: packages/ls-continuity-store
steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Add persist-credentials: false to checkout.

This job never mutates the repo, so persisting the git credential on disk is unnecessary attack surface — flagged by zizmor's artipacked audit.

🔒️ Proposed fix
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 25-25: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ls-continuity-store.yml at line 25, The checkout step in
the ls-continuity-store workflow should disable persisted git credentials
because this job is read-only. Update the actions/checkout@v4 usage in the
workflow to set persist-credentials to false so no token is written to disk.

Source: Linters/SAST tools

requireObjectRef(previousRef, "previous_ref");
requireObjectRef(input.context_digest, "context_digest");
requireObjectRef(input.reauthorization_ref ?? null, "reauthorization_ref");
if (input.authority_expires_at) parseTime(input.authority_expires_at, "authority_expires_at");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Empty-string authority_expires_at is accepted at creation but breaks every later evaluation.

The truthy guard skips validation for "" (falsy), and line 273's input.authority_expires_at ?? null preserves "" (not nullish) in the payload. Later, evaluateTransitionResume (line 428) sees payload.authority_expires_at !== null as true and calls parseTime(""), which is NaN and throws AUTHORITY_EXPIRES_AT_INVALID. The snapshot persists successfully but can never be resumed. Validate non-null values so bad input is rejected up front.

🐛 Proposed fix
-  if (input.authority_expires_at) parseTime(input.authority_expires_at, "authority_expires_at");
+  if (input.authority_expires_at != null) parseTime(input.authority_expires_at, "authority_expires_at");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (input.authority_expires_at) parseTime(input.authority_expires_at, "authority_expires_at");
if (input.authority_expires_at != null) parseTime(input.authority_expires_at, "authority_expires_at");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ls-continuity-store/src/trustworthy-transition.ts` at line 232, The
creation path in trustworthy-transition.ts is letting empty-string
authority_expires_at slip through because the validation guard only checks
truthiness and the payload later preserves "" via nullish coalescing. Update the
validation around the input.authority_expires_at check so any non-null value,
including "", is passed to parseTime and rejected immediately if invalid; also
make sure the payload construction normalizes empty strings to null before it
reaches evaluateTransitionResume.

Copy link
Copy Markdown
Owner Author

Disposition: PARKED stacked follow-up

This PR remains intentionally stacked on #766 and must not move independently. Preserve it as the trustworthy-transition continuity experiment; resume only after an explicit product decision revives the Continuity Store line. Until then it is not an active v0.1 delivery blocker.

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.

1 participant