You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Roll out and cut over the Telegram conversational MVP
Status: in progress — automated repair accepted; HUMAN rollout pending
Tags: enhancement, assistant, backend, infra, testing, P1
Parent: #122
Depends on: #126/#127 automated slices on main; Gate A effective apply before
any new app deployment; corrected #131 review/integration/effective Gate B apply
before #128 commit/push
Blocks: Closing #122
Architecture: _docs/CONVERSATIONAL_AGENT_PLUGIN_ARCHITECTURE.md
Current review state
The frozen candidate at base f08cfc750cebc601db8b71024168741f8ef4aadb is not accepted. Keep it
uncommitted and use it as the rework starting point.
The six-control rollout, proposal/execution boundaries, legacy cutover,
readiness shape, redaction, and most of the frozen infrastructure graph passed
specialist review. Rework is deliberately limited to truthful heartbeat
monitoring, bounded ingress-off maintenance I/O, deterministic proofs, and the
resulting #131 graph/IAM correction.
Do not commit, merge, push, deploy, retrieve secrets, call Telegram/providers,
or apply AWS while these gates are open.
Product scope
Ship one private-Telegram conversational rollout for the two implemented
proposal capabilities:
todo: propose, revise, explicitly approve, and create one actor-owned todo;
Typefully: confirm typed public source, propose/revise, explicitly approve,
and create one unscheduled/unpublished/unshared saved draft.
#126 continues to own todo/coordinator/outbox semantics. #127 continues to own
Typefully source, approval, account, request, result, and reconciliation
semantics. #128 owns only rollout integration, dark maintenance, readiness,
telemetry, deployment wiring, staged HUMAN verification, and switch rollback.
Exactly six deploy-owned controls
Expose exactly:
Telegram ingress: off | on
execution leasing: off | on
enabled plugins: none | todo | typefully | todo,typefully
Typefully external execution: off | on
voice: off | on
photo: off | on
One immutable strict snapshot accepts only canonical lowercase values and a
sorted unique plugin subset. All code, SAM, and workflow defaults are off.
Reject missing, whitespace-bearing, duplicate, unknown, retired, or
contradictory controls.
Derived behavior remains:
runtime availability and result delivery follow Telegram ingress;
todo visibility follows the todo plugin;
todo dispatch requires execution plus todo;
Typefully visibility follows the typefully plugin;
Typefully dispatch requires execution, typefully, and Typefully external;
voice/photo require ingress plus their individual switch.
Gates run before proposal approval, before lease, and after lease/before
dispatch. Disabling a capability preserves durable state, never interrupts an
already dispatched effect, and never makes an unknown effect retryable.
Typefully preview with external execution off permits revise/cancel/discard but
has no active approval or queue path.
Required rework 1 — truthful heartbeat monitoring
Keep the existing three fixed-key readiness heartbeat rows and the existing
three metric/alarm names. Treat the metric values as scheduled pulse-presence
signals; readiness continues to calculate stored heartbeat age separately.
Use this minimal design:
Add one rate(2 minutes) scheduled event to ConversationalExecutionWorkerFunction, enabled only by ConversationalExecutionIsEnabled, with a distinct fixed action such as conversational-execution-health-pulse.
The worker handler recognizes that action before stream/recovery work. It
writes only the fixed execution_worker heartbeat and emits exactly ExecutionWorkerHeartbeatAgeSeconds = 0; it does not query/lease/recover an
attempt, call a provider, retrieve a provider secret, or inspect queue
traffic.
The existing recovery schedule remains rate(2 minutes) and emits exactly RecoveryHeartbeatAgeSeconds = 0 only after its scheduled invocation reaches
the successful pulse point. Idle recovery is healthy.
The existing result-dispatcher schedule remains rate(1 minute) and emits
exactly ResultDispatcherHeartbeatAgeSeconds = 0 after a successful
bounded run. An empty outbox is healthy.
Only these three heartbeat alarms change to TreatMissingData: breaching. Retain Maximum, threshold > 300, 5-minute
periods, two evaluation periods, and two datapoints to alarm. Successful
fixed-zero pulses remain non-breaching; two missing periods can page a
stopped schedule/function.
All other 14 alarms retain TreatMissingData: notBreaching. Disabled
execution/ingress conditions still remove their heartbeat alarm and disable
the related schedules, so an intentionally disabled component cannot page.
Do not add metric math, a monitoring Lambda, a fourth heartbeat alarm, another
metric namespace, or queue-traffic heuristics.
The new SAM schedule event will produce an additional EventBridge rule and
Lambda permission in the transformed template. Freeze their exact logical IDs,
condition/state, target, input, and function ownership. Gate A's accepted
deploy role already covers stack-prefixed EventBridge rule lifecycle/tagging
and worker Lambda Add/RemovePermission; do not add those actions to #131's
observability policy. #131 must add structural coverage proving that reuse.
After webhook authentication but before JSON parsing:
measure the raw update in UTF-8 bytes using the same 256 KiB maximum as
the enabled adapter;
reject an oversized update with 413;
attempt no Telegram reply, conversation/model/media/plugin/domain/provider
work, and do not parse the oversized body.
Every ingress-off maintenance or private-chat-redirect sendMessage attempt
must:
use one fixed deadline no greater than 5 seconds through an AbortSignal;
make at most one attempt and never retry an unknown send outcome;
swallow timeout/network failure into the normal safe Telegram webhook
acknowledgement so Telegram is not invited to repeat a possibly delivered
reply;
log only an allowlisted fixed event/error code, never token, URL, chat/update
content, body, header, or stack trace.
Add deterministic tests with a non-returning fetch that observes abort within
the configured deadline and an oversized update that receives 413 with zero
reply/downstream calls. Retain positive proofs for authentication, private
maintenance, directed-group redirect, and at most one fixed reply.
Permanent Telegram and safety behavior
The legacy mutating Telegram fallback remains removed/unreachable. In all
states:
/todo and /social are static guidance and never write directly;
/podcast states that podcast creation is outside this MVP;
/start, /help, /status, session controls, private identity, directed
group redirect, unsupported-document failure, and reviewed media behavior
remain as accepted;
ingress off authenticates and performs only the bounded maintenance behavior
above;
rollback never restores a legacy handler or polling.
Readiness remains admin-only, exact-read/query-bounded, scan-free,
mutation-free, provider/secret-free, and redacted. Telemetry remains
fixed-cardinality and recursively safe.
Frozen application graph after rework
Refreeze from fresh source/build/transforms. The intended observability family
remains:
one ConversationalRolloutAlarmTopic, encrypted with alias/aws/sns, ARN
output only, no stack subscription/topic policy/contact;
exactly three new 30-day retained custom log groups wired with LoggingConfig;
exactly 17 named alarms and one topic action;
the three heartbeat alarms use TreatMissingData: breaching; the other 14
use notBreaching;
no dashboard, composite, anomaly, canary, metric/filter/subscription, or
additional observability resource.
The intended schedule family is:
existing execution recovery schedule;
new execution worker health-pulse schedule;
existing result dispatcher schedule;
their exact SAM-generated EventBridge rules and Lambda permissions.
The replacement #128 source and transforms, not the old #131 fixture, are
authoritative.
Acceptance criteria
One immutable snapshot exposes exactly six controls, rejects invalid
combinations, and keeps all defaults off.
Legacy direct task/social/podcast/fallback mutation is unreachable and
slash compatibility remains static.
Ingress-off authenticates, enforces a 256 KiB pre-parse byte limit, and
performs only one deadline-bounded maintenance/redirect attempt.
Maintenance timeout/abort returns safely without retry, downstream work,
or sensitive logging.
Worker health has an execution-conditioned two-minute no-op pulse
independent of stream/queue traffic.
Recovery and dispatcher use their existing schedules as idle-safe
successful pulse sources.
Successful pulses emit fixed zero; the three heartbeat alarms alone use
missing-as-breaching; disabled components do not alarm.
Deterministic tests prove healthy/idle, one-period gap, stopped/two-period
missing, disabled, handler-error, and no-work states for all three
components.
Readiness stays fixed-key/bounded/redacted and reports disabled
components as not_applicable.
Telemetry remains fixed-cardinality and drops unsafe values without
breaking business behavior.
Fresh transforms contain one topic, three retained groups, 17 corrected
alarms, the new worker pulse rule/permission, existing schedules, and no
prohibited family.
The Playwright global setup passes these six explicit canonical dark values
to its spawned test server, independent of the parent environment: CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED=false, CONVERSATIONAL_EXECUTION_ENABLED=false, CONVERSATIONAL_ENABLED_PLUGINS=none, CONVERSATIONAL_TYPEFULLY_EXTERNAL_EXECUTION_ENABLED=false, CONVERSATIONAL_TELEGRAM_VOICE_ENABLED=false, and CONVERSATIONAL_TELEGRAM_PHOTO_ENABLED=false.
Playwright readiness probes GET /api/health and resolves only on HTTP 200; connection errors, timeouts, and every non-200 response remain
unready until the bounded startup timeout fails clearly.
An automated regression unsets all six controls in the parent process,
starts the E2E server through the real bootstrap, and proves the child is
healthy with the six dark defaults rather than inherited ambient values.
CI=true npm --prefix backend run test:e2e completes the full suite with
exit code 0; retries must not mask a server-startup/configuration failure.
Production fail-closed validation is unchanged: missing, malformed, or
contradictory rollout controls still fail outside the test-only E2E
bootstrap, with no fallback defaults added to production handler/runtime
parsing.
[HUMAN] One webhook/no poller, confirmed out-of-band subscription, dark
maintenance, staged todo/media/Typefully behavior, alarm canaries, and
reverse rollback pass with sanitized evidence.
Required automated verification
Run and record exact exit codes/counts:
npm --prefix backend test
npm --prefix backend run typecheck
npm --prefix backend run build
npm --prefix backend run test:telegram-transaction
npm --prefix backend run test:execution-transaction
npm --prefix backend run test:todo-transaction
npm --prefix backend run test:typefully-transaction
uv run --with pytest python -m pytest -q tests/infra
make sam-validate
AWS_CONFIG_FILE=.tmp/aws-empty/config \
AWS_SHARED_CREDENTIALS_FILE=.tmp/aws-empty/credentials \
AWS_EC2_METADATA_DISABLED=true \
AWS_DEFAULT_REGION=eu-west-1 \
sam validate --lint --template-file infra/template.full.yaml
make sam-build
git diff --check
Also require:
focused handler tests for the worker pulse, recovery pulse, dispatcher pulse,
256 KiB boundary, oversized rejection, abort deadline, zero retries, and zero
downstream work;
fresh isolated dark/execution/ingress/voice/photo/Typefully-external
transforms and exact schedule/rule/permission inventory;
repeated deterministic graph generation and public-safe sentinel scans;
Assistant and Architect re-review, then independent Tester and PM acceptance.
The repair requires the focused unset-parent bootstrap/readiness regression and CI=true npm --prefix backend run test:e2e. Screenshots remain unnecessary
because no frontend surface changes. No real Telegram/provider/AWS action is an
agent test.
Groups/shared context beyond redirect, web conversation/approval, memory,
scheduling/publishing, podcast/SOP mutation, new plugins, generic uploads,
second proposal/execution paths, extra observability families, provider
fallback, secret UI/plaintext credentials, manual app deployment/Lambda patch,
polling, queue purge, destructive state changes, and edits to source repos from
the #128 worktree.
Roll out and cut over the Telegram conversational MVP
Status: in progress — automated repair accepted; HUMAN rollout pending
Tags:
enhancement,assistant,backend,infra,testing,P1Parent: #122
Depends on: #126/#127 automated slices on
main; Gate A effective apply beforeany new app deployment; corrected #131 review/integration/effective Gate B apply
before #128 commit/push
Blocks: Closing #122
Architecture:
_docs/CONVERSATIONAL_AGENT_PLUGIN_ARCHITECTURE.mdCurrent review state
The frozen candidate at base
f08cfc750cebc601db8b71024168741f8ef4aadbis not accepted. Keep ituncommitted and use it as the rework starting point.
Binding blockers:
Roll out and cut over the Telegram conversational MVP #128 (comment)
Roll out and cut over the Telegram conversational MVP #128 (comment)
The six-control rollout, proposal/execution boundaries, legacy cutover,
readiness shape, redaction, and most of the frozen infrastructure graph passed
specialist review. Rework is deliberately limited to truthful heartbeat
monitoring, bounded ingress-off maintenance I/O, deterministic proofs, and the
resulting #131 graph/IAM correction.
Do not commit, merge, push, deploy, retrieve secrets, call Telegram/providers,
or apply AWS while these gates are open.
Product scope
Ship one private-Telegram conversational rollout for the two implemented
proposal capabilities:
and create one unscheduled/unpublished/unshared saved draft.
#126 continues to own todo/coordinator/outbox semantics. #127 continues to own
Typefully source, approval, account, request, result, and reconciliation
semantics. #128 owns only rollout integration, dark maintenance, readiness,
telemetry, deployment wiring, staged HUMAN verification, and switch rollback.
Exactly six deploy-owned controls
Expose exactly:
One immutable strict snapshot accepts only canonical lowercase values and a
sorted unique plugin subset. All code, SAM, and workflow defaults are off.
Reject missing, whitespace-bearing, duplicate, unknown, retired, or
contradictory controls.
Derived behavior remains:
todoplugin;todo;typefullyplugin;typefully, and Typefully external;Gates run before proposal approval, before lease, and after lease/before
dispatch. Disabling a capability preserves durable state, never interrupts an
already dispatched effect, and never makes an unknown effect retryable.
Typefully preview with external execution off permits revise/cancel/discard but
has no active approval or queue path.
Required rework 1 — truthful heartbeat monitoring
Keep the existing three fixed-key readiness heartbeat rows and the existing
three metric/alarm names. Treat the metric values as scheduled pulse-presence
signals; readiness continues to calculate stored heartbeat age separately.
Use this minimal design:
rate(2 minutes)scheduled event toConversationalExecutionWorkerFunction, enabled only byConversationalExecutionIsEnabled, with a distinct fixed action such asconversational-execution-health-pulse.writes only the fixed
execution_workerheartbeat and emits exactlyExecutionWorkerHeartbeatAgeSeconds = 0; it does not query/lease/recover anattempt, call a provider, retrieve a provider secret, or inspect queue
traffic.
rate(2 minutes)and emits exactlyRecoveryHeartbeatAgeSeconds = 0only after its scheduled invocation reachesthe successful pulse point. Idle recovery is healthy.
rate(1 minute)and emitsexactly
ResultDispatcherHeartbeatAgeSeconds = 0after a successfulbounded run. An empty outbox is healthy.
TreatMissingData: breaching. RetainMaximum, threshold> 300, 5-minuteperiods, two evaluation periods, and two datapoints to alarm. Successful
fixed-zero pulses remain non-breaching; two missing periods can page a
stopped schedule/function.
TreatMissingData: notBreaching. Disabledexecution/ingress conditions still remove their heartbeat alarm and disable
the related schedules, so an intentionally disabled component cannot page.
Do not add metric math, a monitoring Lambda, a fourth heartbeat alarm, another
metric namespace, or queue-traffic heuristics.
The new SAM schedule event will produce an additional EventBridge rule and
Lambda permission in the transformed template. Freeze their exact logical IDs,
condition/state, target, input, and function ownership. Gate A's accepted
deploy role already covers stack-prefixed EventBridge rule lifecycle/tagging
and worker Lambda Add/RemovePermission; do not add those actions to #131's
observability policy. #131 must add structural coverage proving that reuse.
Required rework 2 — bounded ingress-off maintenance
After webhook authentication but before JSON parsing:
the enabled adapter;
413;work, and do not parse the oversized body.
Every ingress-off maintenance or private-chat-redirect
sendMessageattemptmust:
AbortSignal;acknowledgement so Telegram is not invited to repeat a possibly delivered
reply;
content, body, header, or stack trace.
Add deterministic tests with a non-returning fetch that observes abort within
the configured deadline and an oversized update that receives
413with zeroreply/downstream calls. Retain positive proofs for authentication, private
maintenance, directed-group redirect, and at most one fixed reply.
Permanent Telegram and safety behavior
The legacy mutating Telegram fallback remains removed/unreachable. In all
states:
/todoand/socialare static guidance and never write directly;/podcaststates that podcast creation is outside this MVP;/start,/help,/status, session controls, private identity, directedgroup redirect, unsupported-document failure, and reviewed media behavior
remain as accepted;
above;
Readiness remains admin-only, exact-read/query-bounded, scan-free,
mutation-free, provider/secret-free, and redacted. Telemetry remains
fixed-cardinality and recursively safe.
Frozen application graph after rework
Refreeze from fresh source/build/transforms. The intended observability family
remains:
ConversationalRolloutAlarmTopic, encrypted withalias/aws/sns, ARNoutput only, no stack subscription/topic policy/contact;
LoggingConfig;TreatMissingData: breaching; the other 14use
notBreaching;additional observability resource.
The intended schedule family is:
The replacement #128 source and transforms, not the old #131 fixture, are
authoritative.
Acceptance criteria
combinations, and keeps all defaults off.
accepted Ship approved conversational todo creation #126/Ship approved Typefully saved-draft creation #127 boundaries.
slash compatibility remains static.
performs only one deadline-bounded maintenance/redirect attempt.
or sensitive logging.
independent of stream/queue traffic.
successful pulse sources.
missing-as-breaching; disabled components do not alarm.
missing, disabled, handler-error, and no-work states for all three
components.
components as
not_applicable.breaking business behavior.
alarms, the new worker pulse rule/permission, existing schedules, and no
prohibited family.
proven tag-on-create plus existing lifecycle permission.
to its spawned test server, independent of the parent environment:
CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED=false,CONVERSATIONAL_EXECUTION_ENABLED=false,CONVERSATIONAL_ENABLED_PLUGINS=none,CONVERSATIONAL_TYPEFULLY_EXTERNAL_EXECUTION_ENABLED=false,CONVERSATIONAL_TELEGRAM_VOICE_ENABLED=false, andCONVERSATIONAL_TELEGRAM_PHOTO_ENABLED=false.GET /api/healthand resolves only on HTTP200; connection errors, timeouts, and every non-200 response remainunready until the bounded startup timeout fails clearly.
starts the E2E server through the real bootstrap, and proves the child is
healthy with the six dark defaults rather than inherited ambient values.
CI=true npm --prefix backend run test:e2ecompletes the full suite withexit code 0; retries must not mask a server-startup/configuration failure.
contradictory rollout controls still fail outside the test-only E2E
bootstrap, with no fallback defaults added to production handler/runtime
parsing.
commit/merge/push.
maintenance, staged todo/media/Typefully behavior, alarm canaries, and
reverse rollback pass with sanitized evidence.
Required automated verification
Run and record exact exit codes/counts:
npm --prefix backend test npm --prefix backend run typecheck npm --prefix backend run build npm --prefix backend run test:telegram-transaction npm --prefix backend run test:execution-transaction npm --prefix backend run test:todo-transaction npm --prefix backend run test:typefully-transaction uv run --with pytest python -m pytest -q tests/infra make sam-validate AWS_CONFIG_FILE=.tmp/aws-empty/config \ AWS_SHARED_CREDENTIALS_FILE=.tmp/aws-empty/credentials \ AWS_EC2_METADATA_DISABLED=true \ AWS_DEFAULT_REGION=eu-west-1 \ sam validate --lint --template-file infra/template.full.yaml make sam-build git diff --checkAlso require:
256 KiB boundary, oversized rejection, abort deadline, zero retries, and zero
downstream work;
breachingheartbeat alarms and 14notBreachingalarms;transforms and exact schedule/rule/permission inventory;
The repair requires the focused unset-parent bootstrap/readiness regression and
CI=true npm --prefix backend run test:e2e. Screenshots remain unnecessarybecause no frontend surface changes. No real Telegram/provider/AWS action is an
agent test.
Sequencing
and PM accept it.
aws-infra/main; a credentialed operatorapplies Gate A/Gate B and verifies effective permissions.
Refs #128, merge/pushmain, and letnormal CI perform the all-off dark deployment.
human, run staged HUMAN canaries/reverse rollback, and close Roll out and cut over the Telegram conversational MVP #128 onlyafter live gates pass. Close Epic: Deliver a Telegram-first conversational agent MVP #122 last.
Out of scope
Groups/shared context beyond redirect, web conversation/approval, memory,
scheduling/publishing, podcast/SOP mutation, new plugins, generic uploads,
second proposal/execution paths, extra observability families, provider
fallback, secret UI/plaintext credentials, manual app deployment/Lambda patch,
polling, queue purge, destructive state changes, and edits to source repos from
the #128 worktree.