ARCH-000005: propose first external operation #7

Merged
m merged 2 commits from codex/first-external-operation-proposal-v1 into main 2026-08-16 15:53:32 +00:00
3 changed files with 231 additions and 2 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000040-creator-action-history.json",
"task_id": "DEV-000040",
"task_file": "dev/tasks/ARCH-000005-first-external-operation-proposal.json",
"task_id": "ARCH-000005",
"task_revision": 1
}
@@ -0,0 +1,78 @@
{
"task_id": "ARCH-000005",
"task_revision": 1,
"architecture_revision": "ADR-0004",
"base_repo_revision": "91d03b730d1bdbbe8e67b831fb21046768c4df5e",
"risk_tier": "E",
"intent": "Produce a proposal-only decision for Domovoy's first real external operation, including typed semantics, authority, capability binding, idempotency, reconciliation, secret lifecycle, and safe implementation slices without installing credentials or changing runtime behavior.",
"requirement_ids": ["INV-AUTH-001", "INV-OWN-001", "CONSTITUTION-1.3.5", "CONSTITUTION-1.3.14", "CONSTITUTION-1.3.31"],
"allowed_change_scope": {
"modules": [],
"paths": [
"docs/FIRST_EXTERNAL_OPERATION_PROPOSAL.md",
"dev/ACTIVE_TASK.json",
"dev/tasks/ARCH-000005-first-external-operation-proposal.json"
]
},
"forbidden_change_scope": {
"paths": [
"architecture/**",
"ci/**",
".gitea/**",
"security/**",
"database/**",
"modules/**",
"contracts/**",
"adapters/**",
"cmd/**",
"deploy/**",
"tests/**"
]
},
"allowed_architecture_delta": {
"new_cross_module_dependencies": 0,
"new_canonical_owners": 0,
"new_privileges": 0,
"new_third_party_dependencies": 0
},
"contracts": {
"consume": ["Architecture Constitution", "Core Semantic Model V1", "ADR-0003", "existing CapabilityBinding ownership", "existing Commit/Executor boundary"],
"may_change": []
},
"semantic_owner": "proposal-only; no architecture decision is accepted by this task",
"canonical_state": ["none"],
"state_transitions": ["none"],
"side_effects_and_privileges": ["select ARCH-000005 in the protected active-task pointer so promotion CI can validate this proposal", "no endpoint call, secret creation, deployment, or runtime mutation"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["proposal must specify deterministic idempotency and read-only reconciliation", "implementation remains blocked until explicit acceptance and a concrete compatible destination"],
"assumptions": [
"The first external operation should extend the existing Creator and Continuity flow rather than introduce an unrelated automation domain",
"A destination chosen later can provide an idempotent create-by-ActionIntentID and read-only lookup protocol",
"No arbitrary URL or arbitrary provider payload should cross the Creator or model boundary",
"docker-test remains prohibited"
],
"acceptance_scenarios": [
"proposal compares notification, arbitrary webhook, file export, Git mutation, and direct provider alternatives",
"proposal recommends one typed operation without treating the provider mechanism as domain meaning",
"proposal keeps destination binding canonical under the existing Capability owner and secrets external",
"proposal requires explicit Creator confirmation, live Work justification, current authority, and current capability binding revision",
"proposal defines idempotent execution, UNKNOWN_OUTCOME, read-only reconciliation, and binding retirement behavior",
"proposal prevents SSRF, redirects, arbitrary URL/body, ambient credentials, and blind retry",
"proposal identifies the minimum external destination facts still requiring explicit acceptance",
"the active task pointer selects this proposal and no implementation, protected architecture, credential, or deployment state is changed"
],
"required_tests": [
"architecture preflight",
"exact diff scope",
"constitutional and ADR-0003 semantic review",
"go test",
"go vet",
"CI"
],
"terminal_gap_states": [
"BLOCKED_BY_SPEC_GAP",
"BLOCKED_BY_ARCHITECTURE_GAP",
"BLOCKED_BY_MISSING_CAPABILITY",
"BLOCKED_BY_CONFLICT"
]
}
+151
View File
@@ -0,0 +1,151 @@
# First external operation proposal
Status: **PROPOSED — NOT ACCEPTED**
Task: `ARCH-000005`, revision 1
Base: `91d03b730d1bdbbe8e67b831fb21046768c4df5e`
## Objective
Choose the smallest useful real-world operation that exercises Domovoy's existing safety path without creating a generic mutation escape hatch:
`Creator session → owner validation → live Work justification → Capability binding revision → Authority validation → durable Commit → isolated Executor → external receipt → durable attempt/outcome → read-only reconciliation`
This proposal changes no runtime, contract, schema, architecture authority, credential, endpoint, or deployment state. Its Tier E task packet selects itself in `dev/ACTIVE_TASK.json` only so fail-closed promotion CI validates the proposal's exact diff.
## Current gap
Foundation and the Creator console prove internal Continuity execution, durable attempts, `UNKNOWN_OUTCOME`, and reconciliation. No production Executor currently mutates a system outside Assistant-owned canonical state. Before adding one, V1 needs an explicit decision for:
- the semantic operation and subject;
- who may request it and with which live justification;
- how a destination is bound, revised, disabled, and recovered;
- which exact external protocol makes duplicate prevention and reconciliation provable;
- where credentials live and how they rotate;
- which ambiguous failures become `UNKNOWN_OUTCOME`.
## Alternatives considered
### Typed Prospective notification — recommended
Deliver one server-constructed notification about one Creator-owned `ProspectiveItem` to one preconfigured Creator destination. This extends existing Continuity product behavior while keeping delivery distinct from satisfaction or commitment fulfilment.
The semantic operation is `deliver-prospective-notification-v1`; HTTP is only its first adapter mechanism.
### Arbitrary webhook — rejected
Accepting a URL, headers, method, or free-form JSON from a browser/model would be a universal mutation capability, an SSRF surface, and a substitute for typed domain contracts. It cannot be the first operation.
### Direct email, Telegram, or chat-provider send — deferred
These are useful delivery mechanisms but their duplicate and reconciliation semantics differ. Selecting one before a recipient and provider contract exist would embed provider behavior into the domain decision. A provider may be used later only behind the accepted typed operation and Executor port.
### File or NAS export — deferred
An export is mechanically idempotent, but meaningful payload export requires Artifact metadata/payload lifecycle that V1 has not implemented. Creating unowned files first would bypass Artifact ownership questions.
### Git mutation — rejected for V1
Repository mutation belongs to the separately deferred Development Control Plane and would give the first product Executor disproportionate authority.
## Proposed semantic operation
Capability: `prospective-notification-delivery`
Operation: `deliver-prospective-notification-v1`
Subject and immutable commit evidence:
- `ProspectiveItemID` and expected Prospective revision;
- owner Principal, Scope and Goal observed from Continuity;
- live Work `TaskTarget` justification and revision;
- server-selected notification template code `prospective-delivered-v1`;
- current `CapabilityBindingID` and binding revision;
- `ActionIntentID`, AuthorizedAction ID and authorization time.
The browser supplies only the selected Prospective item, expected revision, Work target and an explicit confirmation. It never supplies Actor, Principal, grant, capability, operation, destination URL, authorization header, template, message body, Action IDs, or provider fields.
The owner must validate that the Prospective item belongs to the authenticated Principal and is currently `DELIVERED`. Notification success does **not** change the Prospective item to `SATISFIED`, fulfil a Commitment, or prove that a human read the message.
## Destination binding and secret lifecycle
The existing Capability owner already owns `CapabilityBinding`. An implementation should materialize a versioned binding rather than use ambient configuration as correctness state. Minimum binding meaning:
- stable Binding ID and revision;
- owner Principal and capability ID;
- fixed HTTPS origin and fixed delivery/receipt path template;
- active/retired lifecycle;
- declared idempotency and reconciliation protocol version;
- external secret reference, never literal secret material;
- verification time and health state.
Commit records the binding ID and revision used for authorization. A retired, missing, unhealthy, owner-mismatched, or revised binding fails closed before a new external attempt. Historical actions retain their original binding evidence. Rotation creates a new secret version/reference; it does not rewrite old envelopes.
Secret bytes are mounted only into the isolated Commit Runtime Executor. They must not enter PostgreSQL, Creator responses, browser storage, image layers, task packets, Git, logs, or cognitive context.
## Required external protocol
The first adapter may use HTTPS only if the selected destination implements a narrow receipt protocol:
1. `PUT <fixed-delivery-path>/<ActionIntentID>` creates the delivery exactly once.
2. The body is the server-rendered typed V1 notification; its canonical digest is recorded before execution.
3. Repeating the same intent and digest returns the original receipt without redelivery.
4. Reusing an intent with a different digest returns an integrity conflict without mutation.
5. `GET <fixed-receipt-path>/<ActionIntentID>` is read-only and returns whether the delivery exists plus the stored digest/receipt identity.
6. Success is accepted only when the receipt identity and digest match the committed request.
The endpoint origin and paths come only from the validated binding. Redirects are disabled. DNS resolution, certificate validation, response size, content type and timeouts are bounded. No request field can select another host, scheme, path, header or credential.
## Failure and reconciliation semantics
- Binding/authority/owner/revision failure: reject before Commit or external attempt as appropriate.
- Definite protocol rejection before mutation: `FAILED` with typed reason.
- Timeout, connection loss after send, malformed success, unexpected 5xx, or receipt mismatch: `UNKNOWN_OUTCOME`.
- `UNKNOWN_OUTCOME` blocks another delivery attempt for the same action.
- Explicit reconciliation issues only the receipt GET and creates a new attempt identity.
- Matching receipt/digest reconciles to `SUCCEEDED`; explicit absence remains unknown unless the provider contract proves absence is authoritative after its consistency window.
- Executor restart loads the immutable envelope and latest attempt; no process-local queue, mutex or timer is correctness state.
- Binding retirement blocks new authorization but preserves reconciliation access for historical actions until an explicit recovery/retention decision exists.
## Security constraints
- No arbitrary URL, HTTP method, headers, body, template or provider operation.
- No redirects and no fallback host.
- No ambient machine credential or credential shared with another capability.
- No direct call from UI, Core, Continuity, cognitive worker or model adapter.
- No logging of body, bearer secret or full provider response.
- No automatic retry of ambiguous mutation.
- No implication that delivery equals reading, satisfaction or fulfilment.
- No `docker-test` deployment or verification.
## Acceptance evidence required from an implementation
- adversarial browser/model fields cannot alter the operation or destination;
- foreign owner, wrong Prospective state, stale item/Work/binding revision, revoked grant and unhealthy binding are denied;
- duplicate delivery with the same intent produces one external receipt;
- lost acknowledgement records `UNKNOWN_OUTCOME` and read-only reconciliation finds the original receipt;
- mismatched digest is an integrity failure and never redelivers;
- restart recovery uses durable Commit state and provider receipt only;
- secret rotation and binding retirement fail closed without rewriting history;
- Commit Runtime is the only component with the delivery credential;
- production smoke uses the selected real destination and does not use `docker-test`.
## Implementation slices after acceptance
1. Accept the semantic/protocol decision in an ADR-level task; confirm that the existing registry dependencies remain sufficient.
2. Implement Capability-owned binding lifecycle and owner-local persistence, with only an external secret reference.
3. Extend the typed Commit envelope for the notification subject, binding revision and request digest.
4. Implement a deterministic fake receiver and adversarial/idempotency/recovery tests.
5. Implement the isolated HTTPS Executor/reconciler with strict destination policy and secret-file injection.
6. Add the explicit Creator confirmation flow and action-history rendering.
7. Configure one concrete compatible destination, generate its credential outside Git, deploy on `docker-prod`, and run exact-artifact production smoke.
## Decision still required
Implementation is `BLOCKED_BY_SPEC_GAP` until both are explicitly accepted:
1. the typed operation and safety protocol in this proposal;
2. one concrete destination base URL that implements the receipt protocol, including where the resulting notification is ultimately shown to the Creator.
No credential should be generated before that destination exists.