{"task_id":"st_01a01808","status":"completed","residency_state":"evicted","parent_session_id":"01a00387-aaf8-7f2f-89e3-e24c1af24859","root_session_id":"01a00387-aaf8-7f2f-89e3-e24c1af24859","depth":1,"execution_mode":"in-process","model":"openai-codex/gpt-5.6-sol","notify_on_terminal":true,"created_at":"2026-08-19T03:19:33.873Z","updated_at":"2026-08-21T16:15:19.315Z","notification":{"run_epoch":0,"notified_epoch":0},"name":"design-runtime-authority","task_summary":"Design runtime current-authority enforcement","description":"Authority enforcement architecture","category":"architect","requested_model":{"provider":"openai-codex","model_id":"gpt-5.6-sol","display":"openai-codex/gpt-5.6-sol","source":"category","variant":"xhigh","reasoning_effort":"xhigh"},"fallback_models":[{"provider":"clinepass","model_id":"cline-pass/glm-5.2","display":"clinepass/cline-pass/glm-5.2","source":"category","variant":"xhigh","reasoning_effort":"medium"},{"provider":"openai-codex","model_id":"gpt-5.6-terra","display":"openai-codex/gpt-5.6-terra","source":"category","variant":"xhigh","reasoning_effort":"xhigh"}],"resolved_model":{"provider":"openai-codex","model_id":"gpt-5.6-sol","display":"GPT-5.6 Sol","source":"category","variant":"xhigh","reasoning_effort":"xhigh"},"spawn_spec":{"version":1,"cwd":"/home/cube/projects/richard/traning coach","prompt":"Design the smallest production runtime current-candidate authority enforcement for Task26, read-only. Current flaw: candidate bundles carry self-contained one-event qualification chains; verify_candidate_authority is only called by verification tools. A later revocation cannot stop activation, capability issuance, or transport. Survey /home/cube/projects/richard/hermes-agent authority, activation, nutrition coaching, gateway runtime, and tests. Propose an operator-trusted external authority source/interface outside immutable candidate bundles, with a pinned genesis/source identity and mutable hash-chained registry+ledger. Require checks at activation, immediately before capability issuance, and immediately before transport; bind authority heads/event count/source id into authorization/capability/receipts; fail on unavailable, stale, revoked, mismatched candidate, local/self-contained authority, or revocation between issuance and transport. Preserve offline Golden testability and no live changes. Deliver exact file/function seams, data schema, TOCTOU handling, minimal tests, and migration order. Do not edit files.\n\n<Category_Context>\nYou are a big-picture system design consultant, NOT an implementer.\n\n<Method>\n1. Survey the WHOLE architecture end to end before proposing anything: module boundaries, data flow, ownership, and the blast radius of the area in question.\n2. Produce at least TWO viable designs and state the trade-offs of each in concrete terms (coupling, testability, migration cost, failure modes).\n3. Recommend ONE of them with the reasoning that decided it, the boundaries it assumes, and the risks it carries.\n4. Demand the SIMPLEST architecture that handles the actual requirements. Reject enterprise patterns that do not pay for themselves.\n</Method>\n\n<Advisory_Mode>\nWhen the caller marks the task advisory-only, produce NO file edits: return the analysis, options, recommendation, and risks as structured text.\n</Advisory_Mode>\n</Category_Context>"},"host_pid":1761519,"final_response":"I read this as an advisory architecture investigation - I surveyed the Task26 qualification, activation, capability, nutrition, gateway, transport, receipt, and test paths and recommend a minimal external filesystem authority with lock-based TOCTOU control.\n\n## Recommendation\n\nUse an **operator-owned local filesystem authority**, separate from every candidate bundle and profile:\n\n```text\n/etc/hermes/task26-authority-pin.json       # immutable operator pin\n/var/lib/hermes/task26-authority/\n  genesis.json                              # immutable identity\n  authority.lock                            # shared/exclusive flock\n  registry.json                             # mutable hash-chained registry\n  qualification-ledger.json                 # mutable paired ledger\n```\n\nExpose it through one injectable `CandidateAuthoritySource` interface. Production uses a read-only `FileCandidateAuthoritySource`; Golden and unit tests use an in-memory implementation.\n\nThis is the smallest design satisfying all requirements. Do not add a database, event bus, distributed consensus, or remote signing service for this single-host pilot.\n\n## Current architecture and flaw\n\nThe current Task26 authority is qualification evidence, not runtime authority:\n\n- `gateway/platforms/task26_candidate_authority.py`\n  - `append_candidate_authority()` writes paired v1 registry/ledger files beneath any caller-supplied root.\n  - `verify_candidate_authority()` verifies chains and current/revoked status.\n  - There is no source identity, pinned genesis, freshness limit, process lock, or production-root distinction.\n- `scripts/source_golden_path.py`\n  - Calls `append_candidate_authority(profile, ...)` only at the end of the Golden lifecycle.\n  - The lifecycle has already activated a customer, issued a capability, and exercised transport before qualification exists.\n- `scripts/verify_source_golden_path.py`\n  - Calls `verify_candidate_authority(root, candidate)` only as an offline verifier.\n- `gateway/platforms/task26_final_state.py`\n  - Copies `candidate-authority/registry.json` and `qualification-ledger.json` into the sealed candidate.\n  - Rehydration compares the immutable copied heads, so it proves historical qualification but cannot observe later revocation.\n- No production activation, capability, or transport module imports `verify_candidate_authority()`.\n\nRuntime paths therefore trust only immutable or profile-local candidate pins:\n\n- `dualcoach_tasks21_25_controller.launch_controller()` validates manifest, profile, wheel, and service state, but not current candidate authority.\n- It currently starts the service before writing the launch authorization.\n- `dualcoach_tasks21_25_controller.issue_delivery_capability()` trusts the launch receipt and profile hash.\n- `dualcoach_activation_cutover.activate_customer_cutover()` trusts deployment and membership evidence but not current candidate authority.\n- `NutritionCoachingCoordinator.issue_delivery_capability()` accepts any syntactically valid `DeliveryLaunchAuthorization`.\n- `NutritionCoachingCoordinator.validate_delivery_transport()` verifies customer, route, approval, expiry, and one-use state, but only checks that candidate fields look like SHA-256 values.\n- `AdaptiveOperatorService._issue()` and `AdaptiveOperatorCapability` bind owner/config/registry/consent/activation state, but no Task26 candidate authority.\n- `AdaptiveNutritionCoordinator.activate_latest()` and `deliver_latest_once()` do not consult Task26 authority.\n- `TelegramCustomerTransport`, scheduled delivery, reminders, activation notices, legacy draft delivery, and onboarding publications all revalidate customer/profile authority, but not the current Task26 candidate.\n\nA copied one-event bundle therefore remains sufficient forever.\n\n## Viable designs\n\n### A. External filesystem authority with a shared transport lock — recommended\n\nThe runtime reads the operator-owned registry and ledger under `flock(LOCK_SH)`. Operator qualification, affirmation, and revocation append under `LOCK_EX`.\n\nFor the final transport check, the runtime retains the shared lock through the provider invocation. Therefore:\n\n- A revocation committed before the final check prevents transport.\n- A revocation writer arriving during transport waits.\n- The transport is linearized before that revocation, eliminating the check/send gap.\n\nTrade-offs:\n\n- Lowest implementation and migration cost.\n- Excellent offline testability.\n- No network dependency.\n- POSIX/local-filesystem coupling.\n- A slow provider call delays revocation commit, so transport calls must have bounded timeouts.\n- A crash between paired registry/ledger replacements fails closed and needs operator repair.\n\n### B. Authority broker that owns transport permits\n\nA small local daemon verifies candidate authority and either:\n\n1. proxies the provider send itself, or\n2. grants and atomically consumes a transport permit while serializing revocations.\n\nTrade-offs:\n\n- Stronger isolation if the gateway account must not read or write operator files.\n- Better multi-host evolution.\n- More deployment units, IPC authentication, availability failure modes, protocol testing, and Golden harness work.\n- A permit-only broker still has a TOCTOU gap unless it owns transport or retains a lease through provider I/O.\n\nThis does not pay for itself in the current single-host Task26 architecture.\n\nA signed snapshot feed without locking or broker-owned transport is not sufficient: signatures authenticate state but do not close the revocation/check/send race.\n\n## External authority schema\n\n### Operator pin\n\n`task26-authority-pin-v1` must be supplied independently, preferably through a systemd credential or root/operator-owned file:\n\n```json\n{\n  \"schema\": \"task26-authority-pin-v1\",\n  \"source_id\": \"<64 hex>\",\n  \"genesis_sha256\": \"<64 hex>\",\n  \"authority_root\": \"/var/lib/hermes/task26-authority\",\n  \"max_event_age_seconds\": 86400,\n  \"allowed_clock_skew_seconds\": 60\n}\n```\n\nThe pin loader must reject:\n\n- Relative or symlinked paths.\n- A root beneath the profile, package, wheelhouse, launch-receipt directory, or candidate bundle.\n- Bundle v1 schemas.\n- Wrong source ID or genesis hash.\n- Unsafe ownership/modes.\n- Missing lock, registry, ledger, or genesis files.\n\n### Genesis\n\n```json\n{\n  \"schema\": \"task26-runtime-authority-genesis-v1\",\n  \"source_id\": \"<64 hex>\",\n  \"created_at_utc\": \"...\",\n  \"operator_domain\": \"dualcoach-production\",\n  \"generation\": 1\n}\n```\n\nThe independently supplied pin contains the hash of these canonical bytes.\n\n### Registry event\n\n```json\n{\n  \"sequence\": 7,\n  \"source_id\": \"<64 hex>\",\n  \"genesis_sha256\": \"<64 hex>\",\n  \"previous_sha256\": \"<registry event head>\",\n  \"ledger_predecessor_sha256\": \"<ledger row head>\",\n  \"action\": \"qualify|affirm|revoke\",\n  \"candidate_digest\": \"<64 hex>\",\n  \"historical_pass_digest\": \"<64 hex>\",\n  \"committed_at_utc\": \"...\",\n  \"valid_until_utc\": \"...\",\n  \"reason\": \"...\",\n  \"event_sha256\": \"<hash of preceding fields>\"\n}\n```\n\nRules:\n\n- `qualify` selects one current candidate.\n- `affirm` may refresh only the currently selected, non-revoked candidate.\n- `revoke` requires an earlier qualification and permanently invalidates that digest.\n- A revoked digest cannot be requalified.\n- Qualification of a successor makes the predecessor non-current.\n- `valid_until_utc` cannot exceed the maximum period allowed by the pinned policy.\n\n### Registry and ledger documents\n\nBoth documents bind:\n\n- `source_id`\n- `genesis_sha256`\n- `event_count`\n- their own head\n- the paired document’s head\n- current candidate\n- permanent revoked digest set\n- canonical document hash\n\nThe paired ledger row mirrors event sequence, action, candidate, timestamps, and event hash. Registry and ledger counts must be equal and nonzero.\n\n### Runtime snapshot\n\nEvery accepted check returns:\n\n```json\n{\n  \"schema\": \"task26-runtime-authority-snapshot-v1\",\n  \"source_id\": \"<64 hex>\",\n  \"genesis_sha256\": \"<64 hex>\",\n  \"candidate_digest\": \"<64 hex>\",\n  \"registry_head_sha256\": \"<64 hex>\",\n  \"ledger_head_sha256\": \"<64 hex>\",\n  \"event_count\": 7,\n  \"committed_at_utc\": \"...\",\n  \"valid_until_utc\": \"...\",\n  \"snapshot_sha256\": \"<64 hex>\"\n}\n```\n\nThis exact object is embedded in launch authorizations, activation receipts, capabilities, reservations, and local provider receipts.\n\n## Runtime interface and TOCTOU semantics\n\nAdd `gateway/platforms/task26_runtime_authority.py` with approximately:\n\n```python\nclass CandidateAuthoritySource(Protocol):\n    @contextmanager\n    def authorize(\n        self,\n        candidate_digest: str,\n        *,\n        stage: AuthorityStage,\n        predecessor: AuthoritySnapshot | None = None,\n    ) -> Iterator[AuthoritySnapshot]: ...\n```\n\nStages:\n\n- `service_activation`\n- `customer_activation`\n- `adaptive_activation`\n- `capability_issue`\n- `transport`\n\n`authorize()` must:\n\n1. Acquire the source shared lock.\n2. Re-read and fully verify genesis, registry, ledger, document hashes, both chains, counts, and cross-heads.\n3. Match the independently pinned source ID and genesis.\n4. Reject unavailable or expired authority.\n5. Require the requested digest to be the current non-revoked candidate.\n6. If given a predecessor snapshot, prove its heads occur at its exact event count and that every later event is only an `affirm` of the same continuously current candidate.\n7. Yield the latest snapshot while retaining the lock.\n\nFor activation and capability issuance, retain the lock until the durable local mutation commits. For transport, retain it through the bounded provider call.\n\nThis permits harmless authority affirmations between stages while rejecting rollback, revocation, successor selection, truncation, or source substitution.\n\n## Exact implementation seams\n\n### Historical bundle authority\n\n- `gateway/platforms/task26_candidate_authority.py`\n  - Keep v1 as historical qualification evidence only.\n  - Rename documentation and result keys to make that explicit.\n  - Do not let production instantiate a runtime source from this root.\n- `scripts/source_golden_path.py::run`\n  - Use an injected deterministic Golden authority source while exercising the offline lifecycle.\n  - Append historical PASS evidence only after the Golden contract completes.\n- `scripts/verify_source_golden_path.py::verify`\n  - Continue checking historical bundle evidence.\n  - Add an assertion that the bundled v1 root is rejected by the production source loader.\n- `gateway/platforms/task26_final_state.py`\n  - Rename sealed `authority` metadata to `historical_candidate_authority`.\n  - Update `TRUST_BOUNDARY`: mutable production authority comes from the independently pinned external source, not sealed bytes.\n\n### Gateway startup and service activation\n\n- `gateway/run.py::GatewayRunner.start`\n  - When production nutrition coaching is enabled, require a runtime launch authorization and authority pin.\n  - Check `service_activation` immediately before connecting the production adapter.\n  - Fail startup for that production surface if authority is absent, stale, revoked, or mismatched.\n- `gateway/run.py::_create_adapter`\n  - Pass the source and runtime candidate authorization into `TelegramAdapter`.\n- `gateway/platforms/dualcoach_tasks21_25_controller.py::launch_controller`\n  - Check current authority for the manifest candidate.\n  - Write the authority-bound launch authorization before starting the service.\n  - Keep the shared authority lock through receipt persistence and `_start_service()`.\n- `_deployed_candidate_matches`\n  - Also require the service unit to expose independently configured authority-pin and launch-authorization paths.\n- `hermes_cli/gateway.py::generate_systemd_unit`\n  - For the dedicated production profile, provide these as systemd credentials or operator-owned absolute paths; do not derive them from the candidate/profile.\n\n### Customer activation\n\n- `gateway/platforms/dualcoach_activation_cutover.py::activate_customer_cutover`\n  - Derive candidate identity from the validated deployment receipt.\n  - Acquire `customer_activation` authority before `admin.activate_customer()`.\n  - Retain it through `_finalize_membership_activation_binding()` and `_activate_bootstrap()`.\n  - Add the snapshot to `ActivationCutoverResult`.\n- `_finalize_membership_activation_binding`\n  - Bind the snapshot into the activation-binding journal row.\n- Profile-wheel source corresponding to installed `checkin_cli/customer_admin.py`:\n  - `ActivationResult`\n  - `_activate_customer_locked`\n  - `_build_activation_audit_record`\n  - `_require_committed_activation_receipt`\n  - Include and validate the Task26 authority snapshot in the prepared/committed activation journal and audit receipt.\n  - Do not edit installed `site-packages`; rebuild the profile wheel.\n\n### Capability issuance\n\n- `dualcoach_tasks21_25_controller.issue_delivery_capability`\n  - Recheck `capability_issue` immediately before writing `draft-deliveries.json`.\n  - Bind the latest snapshot into `bindings`.\n- `nutrition_coaching.py::DeliveryLaunchAuthorization`\n  - Add the launch authority snapshot.\n- `NutritionCoachingCoordinator.__init__`\n  - Require the runtime candidate identity and `CandidateAuthoritySource` in production.\n- `NutritionCoachingCoordinator.issue_delivery_capability`\n  - Recheck under the source lock immediately before the delivery-ledger write.\n  - Bind the issue snapshot into `DraftDeliveryCapability.bindings`.\n- `AdaptiveOperatorService._issue`\n  - Recheck before `_append_row_unlocked`.\n  - Add authority fields to `_ADAPTIVE_SESSION_IMMUTABLE_FIELDS`, the session row, and `AdaptiveOperatorCapability`.\n- `AdaptiveNutritionCoordinator._validate_capability_session`\n  - Verify source/genesis, continuity, candidate, heads, and count against the live source.\n\n### Activation of adaptive plans\n\n- `AdaptiveNutritionCoordinator.activate_latest`\n  - Recheck `adaptive_activation` from the operator capability immediately before the prepared transition.\n  - Retain the source lock through overlay, epoch, receipt, and committed-transition writes.\n  - Add the snapshot to `prepared_payload`, `receipt_payload`, and `adaptive_plan_activated`.\n\n### Final transport boundary\n\n- `NutritionCoachingCoordinator.claim_delivery_transport`\n  - Return a claim containing the issued authority snapshot, not only an opaque digest.\n- `TelegramAdapter._handle_nutrition_draft_callback`\n  - After claim consumption and immediately before `_send_nutrition_topic`, enter a `transport` authorization context.\n  - On rejection, persist a provider-not-started terminal state rather than “unknown provider outcome.”\n- `TelegramCustomerTransport._send_customer`\n  - Resolve the exact consumed delivery row and issued snapshot, then retain transport authority through the strict sender.\n- `TelegramCustomerTransport.send_adaptive_customer`\n  - Read the authority snapshot from the immutable adaptive reservation.\n  - Recheck it under the external lock and retain the lock through `strict_sender`.\n- `AdaptiveNutritionCoordinator.deliver_latest_once`\n  - Add the issue snapshot to `delivery_attempt_started`.\n  - Add the transport snapshot to `delivery_attempt_consumed`, provider receipt, delivered, and sent-audited rows.\n- `TelegramAdapter._send_nutrition_topic`\n  - Make this the final non-bypassable gate for all nutrition Telegram I/O, including onboarding publications.\n  - It should consume an expected snapshot when the operation has a capability/reservation and return an authorized receipt wrapper carrying the transport snapshot.\n- `telegram_nutrition_onboarding_runtime_publication_transport.py::_send_publication`\n  - Bind the returned snapshot into `GatewayOnboardingPublicationOutbox` claim and receipt rows.\n- `telegram_activation_notice.py::ActivationNoticeReceipt` and `ActivationNoticeStore.mark_sending`\n  - Add issue and transport authority snapshots.\n- `TelegramAdapter._send_missing_checkin_reminder`\n- `TelegramAdapter._drain_activation_completion_notices`\n- `TelegramAdapter._send_nutrition_coaching_tick`\n  - Bind authority when provider authority is issued.\n  - Pass that snapshot into the final send.\n  - If it changed or was revoked, record a provider-not-started terminal outcome.\n- Profile-wheel `checkin_cli/customer_schedule.py`\n  - Extend `ScheduledDeliveryReceipt`, `_build_row`, `_validate_schedule_row_shape`, `reserve_*`, and `mark_customer_task_sending` with candidate-authority snapshots.\n\nEvery local receipt should distinguish:\n\n```json\n{\n  \"candidate_authority_issued\": { \"...\": \"...\" },\n  \"candidate_authority_transport\": { \"...\": \"...\" }\n}\n```\n\nThe Telegram provider’s opaque message ID remains unchanged; the local durable receipt binds that ID to these snapshots.\n\n## Minimal tests\n\n1. Valid external source returns the expected source ID, heads, count, and candidate.\n2. Missing files, stale `valid_until`, wrong source/genesis, candidate mismatch, revocation, chain tampering, count mismatch, and bundle-local v1 roots all fail.\n3. Customer activation failure leaves the registry disabled and bootstrap in `AWAITING_ACTIVATION`.\n4. Service launch under revoked authority never invokes `_start_service`.\n5. Revocation after launch but before draft/adaptive capability issuance writes no capability.\n6. Revocation after capability issuance but before legacy draft transport invokes no sender and records provider-not-started.\n7. The same race for adaptive transport yields `delivery_preflight_rejected` and no strict sender.\n8. Scheduled delivery and activation-notice reservations bind authority and stop before provider I/O after revocation.\n9. Lock-order integration: collect ordered events showing transport exits before an exclusive revocation commit; use barriers/events and bounded joins, never sleeps.\n10. Offline source and installed Golden paths use a deterministic injected authority and assert that the immutable copied authority is never accepted as production authority.\n\n## Migration order\n\n1. Treat all existing bundled v1 authority as historical evidence only.\n2. Implement and test the external v2 source and deterministic fake source.\n3. Extend profile-wheel activation and scheduled-receipt schemas to carry authority snapshots.\n4. Extend gateway launch, capability, adaptive, and transport schemas while still reading old receipts only for reconciliation.\n5. Provision a new external genesis and operator pin outside the profile and candidate.\n6. Import the existing PASS as an explicit operator migration qualification event; do not copy the candidate’s self-contained registry as authority.\n7. Build and qualify a successor candidate containing the runtime enforcement.\n8. Generate a fresh authority-bound launch authorization and restart the dedicated service.\n9. Reject all legacy pending capabilities/reservations lacking authority fields. Existing uncertain/sending attempts remain terminal and are never retried.\n10. Enable future customer/adaptive activation only through the new checks.\n\n## Boundaries and risks\n\n- This recommendation assumes Task26 governs the DualCoach/nutrition production surface, not unrelated generic Hermes messaging.\n- The filesystem must be local and all authority writers must honor the same lock.\n- The gateway process should have read/lock access but no write access to genesis, registry, or ledger. If it runs under the same writable operator account, the broker design is stronger.\n- A provider call already linearized before revocation cannot be undone. The shared lock guarantees the revocation commits after that call, not that already-started external I/O can be recalled.\n- Freshness requires periodic operator `affirm` events. If affirmation stops, production fails closed.\n- Paired-file crash inconsistency is intentionally fail-closed; automatic distributed recovery is unnecessary here.\n\nNo files were edited and no live, service, provider, or network actions were performed.","run_stats":{"runtime_ms":519028,"turns":24,"tool_calls":96,"output_tokens":25254,"total_tokens":5278267,"generation_ms":507790,"tokens_per_second":50,"cost_usd":6.86015,"cache_hit_rate_last":0.9955470397110776,"cache_hit_rate_run":0.9367637201735461}}