Repository navigation
Add transactional approval and durable execution recovery #124
Description
Add transactional approval and durable execution recovery
Status: source/reviews and default-off sandbox deployment are complete; remaining live worker/recovery/queue/rollback evidence is consolidated into #128 after #166/#182 restore the canonical Card/Task baseline
Tags: enhancement, assistant, backend, infra, testing, data, human, P1
Depends on: #123 persistence/repository source prerequisite is satisfied; #124 resources are deployed default-off; live acceptance depends on the #128 HUMAN matrix after #166 A/B/C → #182 preflight → D → cleanup and one successful ordinary all-off deployment
Blocks: no source implementation — #126/#127 are shipped; #124 remains a live-acceptance dependency for closing #128/#122
Next owner: #166 orchestrator/credentialed operator for storage sequencing, then the authorized #128 HUMAN rollout owner with On-Call; PM closes #124 when its exact cross-posted criteria pass
Resume condition: #166/#182 finish the reviewed live sandbox sequence, canonical writers reopen on the final Tasks schema, temporary phase controls are removed, and normal all-off OIDC deployment succeeds. Then #128's text/todo, execution readiness/recovery/queue observation, alarm canary, and reverse rollback supply #124's live evidence; do not run a separate #124 provider/Telegram canary.
Architecture: _docs/CONVERSATIONAL_AGENT_PLUGIN_ARCHITECTURE.md at 4e5402c
Current lifecycle evidence
DataOps commit 840dd471a522b9275a62174185076eb6d1072080 is integrated and retains its Tester/PM-accepted transactional approval, lease, recovery, reconciliation, redaction, and infrastructure contracts. The least-privilege deploy-role repair that originally unblocked the retained failure queue was credentialedly applied and verified effective.
Normal push run 31545689094 at 5ac8b39c39e358cdec41b192bad984b8a46a2daa created the #124 execution worker function/role, Stream event-source mapping, retained encrypted failure queue, generated recovery and health-pulse EventBridge rules/permissions, and related runtime configuration; CloudFormation reached UPDATE_COMPLETE, runtime seeding completed, and deployed smoke passed without the former sqs:CreateQueue denial. All conversational controls were false, so the schedules and execution path correctly remained inactive. Run 31711997388 later reconfirmed the stable normal all-off OIDC deploy, seed, and smoke path.
Default-off deployment proves resource ownership and deploy permission, not live leasing/recovery/effect behavior. No HUMAN execution, queue/alarm canary, reconciliation, provider call, or switch rollback has run. #128 is the sole live-evidence owner because its accepted todo journey exercises this execution substrate end to end and its observation/rollback stages cover the shared recovery/queue/readiness contract.
#166 is the active sequencing boundary. The #128 todo canary would create a Task in the transitional table immediately before its approved deletion, so no #124 live acceptance starts until #166/#182 complete Phase D and cleanup.
Product decisions
Keep this slice channel- and domain-independent. It proves the safety boundary with deterministic fake renderers, authorization checks, executors, and reconcilers only. It does not call Telegram, z.ai, task mutation APIs, or Typefully.
Use the conversational state table and repositories introduced by #123. Add one DynamoDB Stream-triggered execution worker and one scheduled recovery invocation of that same worker. Do not add SQS or a general workflow engine in this slice.
Use one immutable proposal version, one or more channel presentations, and one deterministic execution-attempt ID per proposal version. Approval creates no new semantic content: the worker can execute only the stored ProposalSpec.
Use conservative crash handling. Once a worker durably records that provider dispatch is starting, a lost response is potentially an external success. Recovery may retry only with the same provider idempotency key, or may perform a read-only correlation lookup. Otherwise the attempt becomes outcome_unknown and requires an explicit admin resolution. It is better to surface uncertainty than create a duplicate effect.
Scope
Deterministic proposal and presentation service
Add a core service that:
- validates a normalized
ProposalSpecfrom Define conversational state, proposal records, retention, and recovery #123 and canonicalizes it with stable key ordering and explicit normalization rules; - stores a SHA-256 canonical payload hash;
- renders a deterministic preview through an injected renderer and stores a SHA-256 rendered-view hash;
- rejects nondeterministic output, omitted semantic fields, unknown permissions, unavailable executor builds, or hash mismatch;
- creates a
ProposalPresentationwith a cryptographically random opaque token of at least 256 bits, stores only its SHA-256 hash, binds it to the exact actor identity, conversation, channel binding, proposal version, rendered view, and expiry, and defaults expiry to 30 minutes; - revokes older active presentations when a revision creates a new proposal version;
- never accepts proposal content, target data, permissions, or provider parameters from callback/action payloads.
Test time, token generation, renderer, identity/authorization lookup, canonical-target revision lookup, executor registry, and reconciler are injected dependencies.
Atomic approval claim
Approval receives only the opaque presentation token plus trusted request provenance. It hashes the token, loads the referenced server-side records, and performs all read checks before attempting a transaction.
Immediately before the transaction, recheck:
- the actor is authenticated and maps to the active identity bound to the presentation;
- the actor still has the proposal's core permission;
- the conversation and channel binding match the trusted request provenance;
- the presentation is
active, unexpired, and bound to the current rendered-view hash; - the proposal is
presented, not superseded/canceled/expired/conflicted/claimed, and its plugin build, schema, policy, payload, and rendered-view digests match; - the capability-scoped executor build referenced by the proposal is still available;
- the canonical target still has the proposal's stored base revision.
If the base revision changed, conditionally mark the unclaimed proposal conflicted, revoke its active presentations, create no execution attempt, and return a conflict that requires a new proposal. Authorization, actor, channel, expiry, hash, or build mismatch fails closed and creates no attempt.
One TransactWriteItems call must then:
- condition-check the still-active identity/permission, conversation/channel binding, and canonical target revision records;
- change exactly one matching presentation from
activetoconsumed; - change exactly one matching proposal from
presentedtoclaimed; - insert one
queuedExecutionAttemptif absent; and - append a minimal approval audit event if Define conversational state, proposal records, retention, and recovery #123 stores audit events in the same transactional table.
Every item uses condition expressions for the values checked above. The attempt ID and idempotency key are deterministic from the proposal-version identity and immutable payload hash, not from a callback delivery. A retried or simultaneous approval that loses the transaction reads and returns the already stored attempt/result when it belongs to the same proposal; it never queues a second attempt.
The approval path returns a normalized execution_pending interaction after a successful/already-successful claim. It never invokes an executor inline.
Capability-scoped execution and authorization recheck
Define a narrow executor contract keyed by capability and exact build digest. The worker supplies the stored proposal spec, attempt ID, stable idempotency key, and an abort/deadline signal. It does not supply arbitrary database/model/channel clients.
Before any effect, the worker must recheck active identity binding, current core permission, proposal/attempt relationship, exact executor build availability, immutable hashes, and the canonical target base revision. A revoked identity/permission, missing build, corrupt spec/hash, or changed target is a proven no-write failure: record failed_safe with a bounded redacted reason and do not call the executor. A target change after approval does not rewrite or silently refresh the proposal.
The fake executor records calls by idempotency key and can simulate:
- success with a safe effect receipt;
- a proved no-write error;
- timeout/lost response before or after applying the fake effect;
- provider-idempotent replay;
- correlation lookup reporting
applied,not_applied, orunknown; - operator-reconciliation-only behavior.
Result receipts contain only safe IDs, hashes, timestamps, and bounded redacted metadata. Secrets, action tokens, proposal bodies, and provider payloads are prohibited.
Leasing, dispatch boundary, and recovery
An ExecutionAttempt follows only these transitions:
queued -> executing -> succeeded
-> failed_safe
-> outcome_unknown
outcome_unknown -> succeeded (authoritative read-only reconciliation)
-> failed_safe (authoritative read-only reconciliation)
-> manually_resolved (admin records applied/no-effect; no executor call)
succeeded, failed_safe, and manually_resolved are terminal. A terminal attempt is never executed again.
The worker:
- claims an eligible
queuedattempt with one conditional update, recording a unique lease owner, lease expiry, incremented lease generation, bounded attempt history, and current executor build; - permits only the current unexpired lease generation to record dispatch or a result;
- records a durable
dispatch_started_atimmediately before the external/fake executor call; - records success or safe failure with a conditional update and emits a normalized status/result event;
- treats a stale worker's late result as non-authoritative and routes it through reconciliation rather than overwriting newer state.
The recovery invocation queries the #123 due-work index by nextActionAt; it must not scan the table. It handles:
queuedattempts whose Stream delivery was missed by making them eligible for normal leasing;- expired
executingleases with no dispatch marker by returning them toqueued; - expired dispatched attempts in
provider_idempotencymode by leasing and retrying with the same key; - expired dispatched attempts in
correlation_lookupmode by running read-only reconciliation first, then recordingsucceeded,failed_safe, oroutcome_unknownfrom the authoritative result; - expired dispatched attempts in
operator_reconciliation_onlymode by recordingoutcome_unknownwithout another executor call.
Infrastructure/transient failures before dispatch use bounded exponential backoff and a configured maximum pre-dispatch lease count. Exceeding it becomes failed_safe only when the system can prove no effect was attempted. outcome_unknown is never blindly requeued.
Status and reconciliation API
Add authenticated backend routes consistent with existing browser/portal authentication:
GET /api/conversational/execution-attempts/{id}returns a redacted status/result only to the owning authorized actor or an admin;POST /api/conversational/execution-attempts/{id}/reconcileis admin-only, accepts no provider result from the caller, and invokes only the attempt's registered read-only reconciler;POST /api/conversational/execution-attempts/{id}/resolveis admin-only and valid only foroutcome_unknown; it recordseffect_appliedorno_effect, a required bounded reason, actor, timestamp, and safe receipt, transitions tomanually_resolved, and never invokes the executor.
All mutation routes require the current attempt revision and use conditional writes. Duplicate reconciliation/resolution requests return the stored state. Non-admin, wrong-owner, stale-revision, malformed, secret-bearing, or non-unknown resolution requests fail closed.
Atomic invariants
- A proposal version has at most one execution attempt and one stable idempotency key.
- A presentation can be consumed at most once; consuming one presentation globally claims the proposal, so every sibling presentation becomes unusable.
- There is no reachable state where a presentation is consumed or a proposal is claimed without the corresponding queued attempt.
- There is no reachable state where an attempt exists for an unclaimed proposal.
- The executor receives exactly the stored spec whose payload and rendered view were approved.
- Only one unexpired lease generation may dispatch or finalize an attempt.
- No executor call occurs from the approval request.
- No automatic executor call occurs after an operator-only uncertain outcome.
- Authorization is checked both at approval and immediately before execution.
- A duplicate callback, Stream record, scheduled recovery event, Lambda retry, or stale worker result cannot create a second effect or overwrite terminal truth.
Acceptance criteria
- Deterministic canonicalization/rendering binds every semantically relevant
ProposalSpecfield to stored payload and rendered-view hashes; any semantic edit requires a new version/presentation. - Presentations use short opaque tokens, store only token hashes, bind actor/conversation/channel/version/view, expire after the configurable 30-minute default, and revoke on supersession.
- Approval rechecks identity, permission, channel, state, expiry, exact hashes/build/policy/schema, and base revision using trusted server-side state.
- One DynamoDB transaction consumes the presentation, claims the proposal, and inserts exactly one queued attempt; transaction conditions make partial approval states impossible.
- Duplicate/simultaneous approval returns the same stored attempt/result and never creates a second attempt.
- Approval returns
execution_pendingwithout calling an executor. - The worker uses capability-scoped fake executors, conditional leases, stable idempotency keys, exact stored specs, deadlines, redacted receipts, and a pre-effect authorization/base-revision recheck.
- Stream retries, concurrent workers, expired leases, and stale results preserve single ownership and terminal state.
- Recovery queries due work through an index and applies the declared delivery mode; it never table-scans or blindly retries
outcome_unknown. - Provider-idempotent recovery reuses the same key; correlation recovery performs lookup before any retry; operator-only uncertainty requires manual resolution.
- Authenticated status and admin-only reconciliation/resolution routes enforce owner/role/revision rules and never accept an executable/provider result from the caller.
- Approval, lease, dispatch, result, reconciliation, and manual resolution produce bounded redacted audit/status events without proposal content, tokens, secrets, or raw provider data.
- SAM declares the Stream event source, worker, scheduled recovery trigger, failure destination, environment configuration, and least-privilege IAM; production code creates none of these resources on cold start.
- Existing APIs and assistant-job approval behavior remain unchanged; this new approval contract is not retrofitted onto legacy jobs in this issue.
Deployment and live-closure criteria
- The accepted Add transactional approval and durable execution recovery #124 source and least-privilege deploy-role repair are integrated/effective; normal CI creates the worker, Stream mapping, recovery/health schedules, retained encrypted failure queue, IAM/configuration, reaches terminal stack success, seeds, and passes deployed smoke with execution off.
- Replace the transitional Tasks table with the final cardId schema #166 A/B/C, Gate canonical Card lifecycle on the empty production Cards table #182's no-replacement Cards preflight, D, and cleanup complete; canonical writers reopen, temporary phase controls are deleted, and a following normal all-off deployment succeeds before any live Add transactional approval and durable execution recovery #124/Roll out and cut over the Telegram conversational MVP #128 execution.
- [HUMAN via Roll out and cut over the Telegram conversational MVP #128] The text + todo stage proves proposal approval returns pending, exactly one queued attempt is claimed, duplicate approval converges, the Stream worker produces exactly one actor-owned Task/result, and no inline approval executor or duplicate effect is observed.
- [HUMAN via Roll out and cut over the Telegram conversational MVP #128] Bounded readiness/observation proves the execution worker, Stream mapping, recovery and health schedules, due-work query path, result delivery, runtime IAM/configuration, and redacted status/audit surfaces operate without scans, leaked proposal/token/provider content, or unauthorized access.
- [HUMAN via Roll out and cut over the Telegram conversational MVP #128] The retained encrypted failure queue and its alarm path are verified with sanitized state/count evidence and no payload publication or purge. Any synthetic failure canary requires the explicit Roll out and cut over the Telegram conversational MVP #128 HUMAN gate and must prove no real provider/domain effect.
- [HUMAN via Roll out and cut over the Telegram conversational MVP #128] Switch rollback disables new leasing/execution through normal OIDC while preserving queued/attempt/result truth, never retries an unknown effect, never overwrites terminal state, and requires no manual Lambda/IAM edit or destructive queue/data action.
- Roll out and cut over the Telegram conversational MVP #128 cross-posts exact sanitized links for the criteria above. PM verifies no separate Add transactional approval and durable execution recovery #124 provider/Telegram canary, no fabricated reconciliation result, and no missing live criterion; Add transactional approval and durable execution recovery #124 may then close even if unrelated Roll out and cut over the Telegram conversational MVP #128 media/Typefully stages remain open.
Test scenarios
Exact preview binding
Given semantically identical objects with different key order, and objects differing in destination, content, permission, policy/schema/build digest, source revision, base revision, or expiry
When the service canonicalizes and renders them
Then key order produces the same hash, every semantic change produces a different hash, the preview is deterministic, and execution receives only the stored approved spec.
Concurrent approval
Given one active presentation and 25 simultaneous approval requests plus duplicate delivery retries
When all requests run against DynamoDB Local
Then one transaction consumes/claims/queues, one attempt ID exists, all successful/idempotent responses refer to it, and the fake executor is not called by the request path.
Wrong or stale action
Given wrong actor/channel, disabled identity, revoked permission, expired/revoked/consumed token, superseded proposal, changed policy/schema/build/hash, and changed base revision
When approval is attempted
Then no attempt is queued; target drift marks the proposal conflicted/revokes controls; every other case fails closed without exposing record existence.
Worker race and stale lease
Given duplicate Stream records, two workers, an expired lease, and a late result from the old worker
When they race to lease and finalize the same attempt
Then only one lease generation may dispatch/finalize, the late result cannot overwrite state, and the fake effect ledger contains at most one effect.
Crash boundaries
Given deterministic crashes (a) after approval transaction, (b) after lease before dispatch marker, (c) after dispatch marker before fake call, (d) after fake effect before response, and (e) after response before result write
When Stream retries and scheduled recovery run
Then (a) is eventually leased, (b) safely returns to queued, and (c-e) follow the declared provider-idempotency/correlation/operator-only policy without blind duplicate execution.
Delivery modes
Given identical crash cases for all three delivery modes
When recovery runs
Then provider-idempotent mode reuses one key, correlation mode performs a read-only lookup first, and operator-only mode becomes outcome_unknown with zero automatic retries.
Authorization change after approval
Given a claimed queued attempt whose actor is disabled, permission is revoked, executor build disappears, or base revision changes before dispatch
When the worker rechecks authorization
Then the fake executor call count remains zero and the attempt becomes a redacted failed_safe.
Reconciliation and manual resolution
Given outcome_unknown attempts and admin/operator/wrong-owner callers
When status, reconcile, and resolve routes are called concurrently or repeatedly
Then owner/admin status access is redacted, only admin may reconcile/resolve, the reconciler is read-only, one conditional resolution wins, repeat calls return stored truth, and no route calls the executor.
Infrastructure and failure delivery
Given the SAM template
When template assertions, sam validate, and sam build run
Then the correct table Stream invokes only the execution worker, scheduled recovery invokes the same worker in recovery mode, failed Stream batches reach the declared failure destination, worker/recovery IAM is limited to required table/index/stream/log resources, and the API function cannot assume worker-only permissions.
Regression and redaction
Given existing backend APIs and errors containing secret-like values
When the full backend suite runs
Then existing behavior passes unchanged and responses, logs, audit events, attempt receipts, and failure-destination payloads expose no secrets, opaque tokens, or proposal bodies.
Required verification
Software Engineer and Tester must run:
npm --prefix backend test
npm --prefix backend run typecheck
npm --prefix backend run build
make sam-validate
make sam-buildAlso run focused DynamoDB Local tests for the transaction/race/lease/crash scenarios above, focused route authorization tests, and static SAM assertions for resources, event filters, failure handling, environment variables, and IAM. Use injected clocks and deterministic fake executors/reconcilers; automated tests make no external network calls or writes.
No portal UI changes are in scope, so Playwright and screenshots are not required unless implementation unexpectedly changes a web surface; Tester must state that rationale.
Infrastructure details
- Reuse the Define conversational state, proposal records, retention, and recovery #123 conversational state table, keys, due-work index, encryption, PITR, retention policies, and export/restore boundary.
- Enable DynamoDB Streams with the smallest view needed by the worker and filter the event source to execution-attempt records that become
queued. - Add a separate execution-worker Lambda rather than granting external execution responsibilities to the public backend function.
- Add bounded batch/retry settings, partial batch response, and an encrypted retained SQS failure queue for exhausted Stream delivery. This queue is diagnostic dead-letter storage only, not a work queue or orchestration layer.
- Invoke the same worker on a short EventBridge recovery schedule with an explicit recovery event; do not add a second recovery implementation.
- Grant the worker only required read/conditional-write/query/transaction permissions for conversational execution records and its due-work index. Grant Stream read permissions only through the event source mapping. Fake executors require no provider secrets or unrelated tables.
- Grant the backend only the conversational transaction/status operations it needs. The public API must not receive worker-only provider permissions.
- Configuration covers presentation TTL default, lease duration, pre-dispatch retry/backoff bounds, due-work bucket/index names, and fake executor enablement. Use safe defaults and validate bounds at startup.
- Deployed resources are SAM/CloudFormation-owned; local table/event setup remains test/local-only.
Dependencies and closure ownership
#123's persistent records, conditional repositories, TTL/retention, token-hash lookup, unique attempt identity, and due-work index are satisfied source prerequisites. They are historical implementation context, not a current blocker. #121 and #125-#127 register/use the accepted channel, model, todo, and Typefully implementations; no source change is required in #124.
#128 owns all remaining live acceptance and public-safe evidence. #124 does not require every #128 capability to complete: its closure subset is the end-to-end transactional todo execution, worker/recovery/readiness/queue observation, and execution switch rollback listed above. Voice/photo and Typefully-only outcomes remain their own child/rollout gates. If a real outcome_unknown occurs, only the owning domain rollout may use its accepted reconciliation process; #124 closure does not authorize fabricating provider truth or invoking admin resolution merely to exercise a route.
#166/#182 are sequencing dependencies only because the shared todo canary writes the Tasks table. They do not change #124's conversational-state schema or justify migration/compatibility machinery here.
Out of scope
- Telegram callbacks/keyboards, voice notes, images, channel rendering, or group behavior.
- z.ai, Groq, Typefully, task, GitHub, or any other real provider/domain mutation.
- Todo or social-post schemas and plugin registration.
- Model calls,
skill_load,skill_invoke, plugin selection, or general agent behavior. - Web proposal UI, cross-channel approval, notification delivery, or cancellation of an executing external action.
- Multiple effects, batches, partial success, compensating transactions, arbitrary workflow graphs, SQS orchestration, Step Functions, or a general flow DSL.
- Retrofitting/replacing legacy
AssistantJobapproval routes. - Live provider reconciliation runbooks or real-provider smoke tests; those belong to the domain plugin and rollout issues.
- Modifying
../dtc-operations,../datatasks, or../podcast-assistant.
Activity
alexeygrigorev commentedon Jul 29, 2026
GROOMED
This issue is ready for implementation after #123 closes.
Key product decisions:
outcome_unknown.No real Telegram, z.ai, task, Typefully, or other external mutation is in this slice.
alexeygrigorev commentedon Jul 30, 2026
Software Engineer handoff
Implemented the groomed #124 transactional approval and durable execution-recovery slice with injected fake executors only.
Delivered
ProposalSpeccanonicalization and preview rendering with SHA-256 payload/view binding.TransactWriteItemsapproval claim with current user, active identity, permission, conversation, channel, optional target revision, presentation, proposal, deterministic attempt, audit, and cleanup-link conditions/writes.outcome_unknown.Changed files
backend/src/conversation/{execution,executionDefaults,executionRepository,executionWorker,types}.tsbackend/src/execution-worker-handler.tsbackend/src/routes/conversationalExecution.tsbackend/src/router.tsbackend/tests/conversational-execution.test.tsinfra/template.full.yamltests/infra/test_template.pyMakefilebackend/README.mdVerification
sam validate: passed.sam build: passed for bothConversationalExecutionWorkerFunctionandBackendFunction.git diff --check: passed.No external network calls, production writes, commit, push, or issue close were performed. No portal UI changed, so Playwright/screenshots are not applicable per the groomed issue.
READY FOR TEST
alexeygrigorev commentedon Jul 30, 2026
CHANGES REQUESTED
The broad regression workflow passes, but independent safety probes found multiple blockers in the exact approval/execution contract.
Blocking findings
Rendered-view binding omits semantic proposal fields (AC 1).
The fake renderer/view hash includes effect/content/target/destination/permission/base revision, but not policy digest, schema digest, plugin build digest, source references, or expiry. An independent probe changed each of those fields: the canonical payload hash changed every time, while the rendered-view hash stayed identical every time. The groomed exact-preview scenario requires every listed semantic edit to invalidate the approved view. Bind the view to the complete canonical proposal (for example by including the canonical payload hash in the deterministic rendered envelope), and assert both hashes for every semantic field.
The worker does not recheck the exact approved identity/channel binding (AC 7).
ExecutionAttemptstores noidentityBindingIdor channel-conversation binding.preDispatchCheckaccepts any active identity belonging to the actor and never reloads the approved channel binding. In an isolated no-write probe I approved throughidentity-exact, revoked that identity, left a second identity for the same user active, and ran the worker. The fake executor was called once and the attempt becamesucceeded. The exact presentation-bound identity/channel must be stored on the attempt and rechecked immediately before dispatch.Unsafe executor receipts are persisted and exposed without validation/redaction (AC 7 and 12).
finalizeAttemptwrites the executor receipt directly without applyingvalidateConversationalRecord/safe-receipt validation, andpublicAttemptreturns the complete receipt. An injected fake executor returned a bearer-shaped receipt ID andmetadata.apiKey = SYNTHETIC_SECRET_MARKER; both values were persisted, and an independentpublicAttemptprobe reportedleaksBearer:trueandleaksSecret:true. Validate a narrowly allowlisted receipt before any write, reject/redact secret-shaped keys and values, and expose only explicitly safe fields.An expired lease generation can still finalize terminal truth (AC 8).
markDispatchStartedchecksleaseExpiresAt > now, butfinalizeAttemptchecks only status/owner/generation. An independent local probe leased an attempt through12:01, then finalized it at12:02without any renewal; the update succeeded assucceeded. This violates the invariant that only the current unexpired lease may record a result. Add lease-expiry/current-revision conditions to finalization and cover the expired-owner-before-reclaim race.The generated worker policy source lacks required Users-table read permission (AC 13).
preDispatchCheckcallsgetUser, which readsDataOpsUsersTable, butConversationalExecutionWorkerFunctionIAM lists only the conversational table and its two indexes. In deployment this reachesGetItemwithout authorization before every fake dispatch. Grant onlydynamodb:GetItemon the exact Users-table ARN and add a static/generated-template assertion that proves the dependency while keeping all other tables unavailable.Approval controls and transaction conditions are not fully closed over the approved state (AC 2–4).
activein an independent probe. It is indirectly unusable because the proposal is claimed, but the required global control revocation/state is not recorded.Make supersession/control invalidation durable and ensure the approval transaction conditions cover the complete immutable execution envelope required by the issue.
The required transactional/crash-boundary test evidence is incomplete.
The 25-way approval test runs with
NODE_ENV=test, andatomicApprovalthen uses its sequential Dynalite compatibility branch rather thanTransactWriteItems. A separate fake-client assertion inspects the production command shape, but no 25-way test executes the real transaction path against DynamoDB Local as groomed. The suite also does not deterministically exercise every named crash boundary, especially after dispatch marker/before call and after response/before result write. Add a real transaction-capable local test path and explicit crash hooks/assertions.Passing evidence
NODE_ENV=test SKIP_AUTH=true node --import tsx --test backend/tests/conversational-execution.test.ts— exit 0, 10/10, 1 suite.npm --prefix backend test— exit 0, 1,287/1,287, 179 suites.npm --prefix backend run typecheck— exit 0.npm --prefix backend run build— exit 0.uv run --with pytest python -m pytest tests/infra -q— exit 0, 25/25.uv run --with pytest python -m pytest tests/planning_docs -q— exit 0, 7/7.make sam-validate— exit 0; template valid.git diff --check— exit 0.make sam-buildwas invoked and remained active in the custom CopySource/build step when this blocking verdict was posted; the source-template IAM defect above is independently conclusive and must be fixed before retest.Acceptance criteria, in order
execution_pendingand does not invoke an executor.outcome_unknown.GetItemIAM is absent.Playwright and screenshots are not applicable because no portal page, browser route, or operator UI changed. All adversarial probes used local Dynalite and injected fake executors only. No live provider call, production write, deploy, source-repository edit, commit, or push was performed.
alexeygrigorev commentedon Jul 30, 2026
Software Engineer re-handoff — tester findings resolved
Resolved all seven blocking findings from the prior Tester review.
Fixes
Complete rendered-view binding
Exact approval provenance at dispatch
Safe receipts only
outcome_unknownresults without storing the receipt.Unexpired current lease required
leaseExpiresAt > now.Worker Users-table permission
DATAOPS_USERS_TABLEand exact-ARNdynamodb:GetItempermission only.Atomic control closure
Production transaction and crash evidence
TransactWriteCommandattempts, exactly one winner.Final verification
sam validate: passed.sam build: passed for bothConversationalExecutionWorkerFunctionandBackendFunction.GetItempolicy.git diff --check: passed.No prescribed final check failed. No external provider call, production write, deploy, source-repository edit, commit, push, or issue close was performed. No portal UI changed, so Playwright/screenshots remain not applicable.
READY FOR TEST
alexeygrigorev commentedon Jul 30, 2026
CHANGES REQUESTED
The consolidated fixes close every previously demonstrated runtime, redaction, lease, control, and IAM defect. One required verification gap remains: the 25-way “production transaction” test does not execute a DynamoDB transaction or its condition expressions against transaction-capable storage.
Sole blocker: production transaction concurrency is still simulated
admits one winner through the production transaction path under 25 simultaneous approvalspasses a fakeDynamoDBDocumentClienttoatomicApproval. That fake:TransactWriteCommand;transactionClaimed) to fulfill the first call;TransactionCanceledExceptionfor the other 24 calls;ConditionCheck,Update, orPut;The separate 25-way
approvePresentationtest does mutate Dynalite, butNODE_ENV=testselects the sequential compatibility branch instead ofTransactWriteItems. Therefore the groomed requirement to run the 25 simultaneous approval race against DynamoDB Local and prove one complete transaction/attempt with no partial state remains unverified. The new test proves command construction plus its own boolean, not DynamoDB transaction behavior.Smallest acceptable correction: run the 25 concurrent full
approvePresentationcalls against a transaction-capable DynamoDB Local instance with the productionTransactWriteItemsbranch enabled, then independently read storage and assert:A fake client with a manually selected winner is not sufficient for this safety boundary.
Original counterexamples — now passing
failed_safe; executor calls: 0.failed_safe; executor calls: 0.outcome_unknown; nothing unsafe was persisted or returned bypublicAttempt.revoked.TransactWriteCommand.DATAOPS_USERS_TABLE, exact Users-tabledynamodb:GetItem, conversational table/index permissions, filtered Stream event, shared recovery schedule, and retained SSE failure queue.Commands and evidence
NODE_ENV=test SKIP_AUTH=true node --import tsx --test backend/tests/conversational-execution.test.ts: 17/17, 1 suite.failed_safe/0 calls; channel mismatchfailed_safe/0 calls; unsafe receiptoutcome_unknown/no stored receipt/no API leak; siblingrevoked.npm --prefix backend test: 1,294/1,294, 179 suites.npm --prefix backend run typecheck.npm --prefix backend run build.uv run --with pytest python -m pytest tests/infra -q: 25/25.uv run --with pytest python -m pytest tests/planning_docs -q: 7/7.make sam-validate.make sam-build; both worker and backend artifacts built, generated template inspected.git diff --check.Acceptance criteria
1–3: PASS.
4–5: NOT VERIFIED / FAIL GATE — production transaction shape is correct by inspection, but atomic conditions, partial-state impossibility, and concurrent idempotency are not executed against transaction-capable storage as required.
6–14: PASS based on focused probes, full regression, route isolation, crash/recovery tests, redaction checks, and generated SAM inspection.
Playwright and screenshots remain inapplicable because no portal page, browser route, or operator UI changed. No live provider call, production write, deploy, source-repository edit, commit, or push was performed.
alexeygrigorev commentedon Jul 30, 2026
Software Engineer re-handoff — real transaction concurrency proof added
Resolved the sole remaining Tester blocker without changing production behavior.
Genuine DynamoDB transaction test
Added:
npm --prefix backend run test:execution-transactionbackend/scripts/test-conversational-execution-transaction.shbackend/tests/conversational-execution.test.tsThe command starts a fresh
amazon/dynamodb-local:latestin-memory container onan ephemeral localhost port, waits for readiness, runs the test through
DYNAMODB_ENDPOINT, and removes the container on exit. It uses the repository'ssupported DynamoDB Local image and adds no production dependency.
The test runs 25 concurrent full
approvePresentationcalls while selectingthe production
TransactWriteItemsbranch. There is no fake client, manuallychosen winner, or sequential Dynalite compatibility path.
Independent strongly consistent reads prove:
idempotency reference;
claimed;consumed;revoked;approval_claimedaudit event exists;to that attempt;
together, with no partial transaction state.
Because the successful production transaction executes against DynamoDB Local,
its user, exact identity, permission, conversation, exact channel, target,
presentation, full immutable proposal-envelope, and absent-attempt/link/audit
conditions are evaluated by DynamoDB rather than simulated by test code.
The prior process-local boolean harness was removed.
Verification
approvals, 0 skipped.
container-only case.
container-only case; 1,294 total tests, 179 suites.
git diff --check: passed.No production runtime behavior, external provider, production data, deployment,
commit, push, or issue state was changed.
READY FOR TEST
7 remaining items
alexeygrigorev commentedon Jul 30, 2026
TEST PASS — AWS OIDC deploy-role fix
Reviewed isolated aws-infra commit 691666a9b31ae58eba57b009c4bbd0b422d32231 on branch issue-124-conversational-execution-deploy-role against DataOps commit 840dd47, the failed deployment run 30505675113, the #124 SAM resource graph, and the AWS service-authorization boundaries. No edits, commit, push, stack apply, or AWS mutation were performed.
Coverage verified:
Commands and results:
The policy change is least-privilege within CloudFormation generated-name/UUID constraints and covers the complete #124 resource lifecycle identified by On-Call. It is ready for the credentialed operator integration/apply gate. A live policy evaluation and stack deployment remain intentionally outside this review and must be verified after the shared infrastructure stack is applied.
alexeygrigorev commentedon Jul 30, 2026
PM ACCEPTED — AWS deployment-role hotfix
Commit
691666a9b31ae58eba57b009c4bbd0b422d32231is accepted for integration intoDataTalksClub/aws-infraand credentialed application of thedataops-github-actionsstack.Product/operator decision
This is the smallest complete fix for the deployment failure reported by On-Call:
sandbox/dataops/template.github-actions.yamland is additive: 45 insertions, no deletions.iam:PassRole, and log-group access extend the existing lifecycle statements using only the generated worker resource-name patterns.Resource: "*", but is constrained by the supportedlambda:FunctionArncondition to this worker. Read/update/delete remain region/account/UUID scoped and worker-conditioned. Mapping tag actions use the narrow region/account resource boundary available for generated mapping UUIDs.This balances simplicity with least privilege: one deploy-role policy update covers the complete #124 CloudFormation resource graph instead of iterating through predictable one-permission deployment failures.
Evidence
sqs:CreateQueueand identified the remaining worker resource lifecycle gaps.uv run --with pytest python -m pytest sandbox/dataops/tests -q: 7/7 passed.sam validate --lint --template-file sandbox/dataops/template.github-actions.yaml: valid.git diff --check 804b745..691666a: passed; isolated worktree remains clean.Required operator sequence
691666ainDataTalksClub/aws-infra, preserving unrelated changes in the existing dirty checkout.dataops-github-actionsCloudFormation stack withCAPABILITY_NAMED_IAMand confirmsUPDATE_COMPLETE.840dd471a522b9275a62174185076eb6d1072080.The policy source is accepted; #124 deployment acceptance remains incomplete until the credentialed apply, DataOps redeploy, and On-Call production verification succeed.
alexeygrigorev commentedon Jul 30, 2026
AWS deploy-role source fix merged: https://github.com/DataTalksClub/aws-infra/pull/18. Automated review, 7/7 infra tests, SAM lint, and PM acceptance passed. Remaining gate: a credentialed AWS operator must apply the
dataops-github-actionsstack withCAPABILITY_NAMED_IAM; then rerun the failed DataOps deployment and On-Call verification.alexeygrigorev commentedon Jul 30, 2026
On-Call follow-up from the #121 push:
ConversationalExecutionFailureQueue.AccessDeniedforsqs:CreateQueuebydataops-github-actions-deploy.UPDATE_ROLLBACK_COMPLETE; seed and smoke were skipped.aws-infra PR #18 is merged, but this repeated denial confirms the credentialed
dataops-github-actionsstack update is still not applied in AWS. Consequently the #124 worker, Stream mapping, recovery schedule, encrypted failure queue/redrive, worker IAM/environment, and logs remain unavailable for production acceptance.No edits, manual deploy, or AWS mutation were performed by On-Call. Required next step remains: credentialed operator applies PR #18's infrastructure stack, rerun the DataOps deployment, then relaunch On-Call verification.
alexeygrigorev commentedon Jul 30, 2026
On-Call update — main
3365c2aThe latest DataOps deploy workflow failed in the backend test gate before OIDC authentication or any AWS operation. It therefore provides no new evidence about the live role permission state and does not supersede the accepted #124 infrastructure source change or pending operator apply.
Canonical current-run diagnosis: #115 (comment)
alexeygrigorev commentedon Jul 30, 2026
On-Call update — main
98fdc1fThe fresh deployment reached AWS after every repository-owned gate passed, then reproduced the known live #124 permission boundary:
sqs:CreateQueueforConversationalExecutionFailureQueue;UPDATE_ROLLBACK_COMPLETE;This is not an application/test failure: the check job passed backend tests with 0 failures and 0 cancellations, typecheck, build, 346 Playwright tests, SAM validation, and SAM build. The denial confirms that accepted #124 policy source is still not effective on the live deploy role. The current combined operator source is
DataTalksClub/aws-infraorigin/main2a1b95f(#124 + #129 + #130).Canonical report: #115 (comment)
Required next step remains the credentialed named-IAM stack apply, effective-policy verification, then a fresh current-main deployment and On-Call production verification. No manual deploy or AWS mutation was performed.
alexeygrigorev commentedon Jul 30, 2026
On-Call update — main
4f10a5bFresh current-main evidence again confirms the accepted #124 policy source is not effective on the live deploy role:
sqs:CreateQueueforConversationalExecutionFailureQueueand rolled back toUPDATE_ROLLBACK_COMPLETE;Canonical On-Call report: #106 (comment)
This is the known credentialed named-IAM apply boundary, not a code regression. The accepted combined
aws-infrasource remainsorigin/main2a1b95f; a credentialed operator must apply and verify it before fresh normal CI/CD. No manual deploy or AWS mutation was performed.alexeygrigorev commentedon Jul 30, 2026
On-Call update — main
c47c525Workflow run 30532699861 passed backend tests/typecheck/build, Playwright, SAM validation, and two SAM builds, then reproduced the live #124 boundary:
sqs:CreateQueuewas denied forConversationalExecutionFailureQueueunder the GitHub Actions deploy role.CloudFormation reached terminal
UPDATE_ROLLBACK_COMPLETE; no execution worker deployment or runtime smoke test completed. This is direct evidence that accepted combined deploy-role source atDataTalksClub/aws-infra2a1b95fis still unapplied to the live role. A credentialed operator must apply the named-IAM stack; do not manually deploy the DataOps app.Canonical #126 On-Call report: #126 (comment)
alexeygrigorev commentedon Jul 31, 2026
PM STATUS RECONCILIATION — code complete; production acceptance still blocked
The stale
pending/Depends on: #123header no longer described the shippedstate.
840dd471a522b9275a62174185076eb6d1072080is onmain. Its implementation passed the final Tester review and PM acceptance. The implementation acceptance checkboxes are therefore complete.sqs:CreateQueue. The least-privilege source fix passed Tester and PM, was later integrated into the combined deploy-role updates, and the combined named-IAM policy was applied and verified effective.PM decision: keep #124 open and add
human. Code and repository acceptance arecomplete, but production deployment/runtime On-Call acceptance is not. After
#136 and its inherited #140/#141 provenance chain are resolved, a fresh normal
CI/CD deployment must verify the #124 execution worker, Stream mapping, recovery
schedule, retained encrypted failure queue/redrive behavior, effective runtime
IAM/configuration, status/reconciliation routes, and redacted logs. Only then
may #124 close.
No code, AWS, IAM, workflow, or environment change was made during this
reconciliation.
alexeygrigorev commentedon Jul 31, 2026
PM tracker reconciliation — runtime deployment handoff clarified
The accepted code on
mainis unchanged. Replaced the stale #141 dependency with #140 lifecycle acceptance plus #143 ownership remediation, added the exact On-Call owner and terminal-success deployed-smoke resume signal, and removed the prematurehumanlabel while production verification remains. No implementation or criterion changed.alexeygrigorev commentedon Aug 13, 2026
PM lifecycle reconciliation — default-off deployed; live closure delegated to #128
#124 remains open, but the old #136/#143 deployment block is complete.
Completed evidence
840dd471a522b9275a62174185076eb6d1072080remains integrated with its full transactional approval, leasing, recovery, uncertainty, authorization, and redaction contract.UPDATE_COMPLETE, seeded, and passed deployed smoke without the former SQS denial. All conversational controls were off, so execution/schedules correctly remained inactive.This proves default-off resource deployment, not live leasing/recovery/effect behavior. No execution, queue/alarm canary, reconciliation, provider effect, or rollback has run.
Consolidated live owner and #166 sequence
#128 is the sole remaining live-evidence owner; #124 must not create a second Telegram/provider canary. Its text + todo stage exercises transactional approval, one queued attempt, Stream-worker dispatch, one Task/result, duplicate convergence, and result delivery. Its readiness/queue/alarm observation and switch rollback cover the shared recovery substrate.
That evidence waits for #166 A/B/C, #182's no-replacement Cards preflight, Phase D, and cleanup. Starting now would create the todo canary Task in the transitional table immediately before approved deletion. Resume after canonical writers reopen, temporary phase controls are gone, ordinary push deployment is restored, and one all-off run succeeds.
Exact #124 closure subset
PM may close #124 after #128 cross-posts sanitized PASS evidence for:
A fabricated
outcome_unknown, provider truth, or admin resolution is not required and is not authorized merely for closure. #124 can close when this subset passes even if unrelated #128 voice/photo/Typefully stages are still open. Thehumanlabel now truthfully reflects the lifecycle.No repository edit, test, AWS/IAM call, workflow dispatch, provider/Telegram action, credential use, flag change, queue action, data operation, import/export, or restore was performed.