Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/design_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ live.
|---|---|
| Every task | [`AGENTS.md`](../AGENTS.md) |
| Substantial, architecture-sensitive, or older-doc-dependent work | This index, then the applicable sections below |
| Build/deployment receipts and task work products | [Build and deployment evidence](engineering/deployment_evidence.md) |
| Task product selection and continuing a responsibility from Tasks | [Task responsibility continuation](engineering/task_responsibility_continuation.md) |
| Subscription-backed coding task pickup on the DGX | [Codex DGX worker pilot](engineering/codex_dgx_worker.md) |
| Coding-agent progress and completion messages in Von | [Interactive coding-agent messages](engineering/codex_dgx_worker.md#interactive-coding-agent-messages); operator-specific delivery configuration in personal `AGENTS.md` |
Expand Down
138 changes: 138 additions & 0 deletions docs/engineering/deployment_evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Build and deployment evidence

- **Kind:** Capability and producer guide
- **Lifecycle:** Active
- **Authority:** API contract and repository release input; repository presence is not live activation
- **Owner:** Von maintainers
- **Last reviewed:** 12 September 2026
- **Review trigger:** Receipt schema, task work products or deployment-controller changes

A source revision identifies code. `#V#software_build` identifies an immutable
artefact (use its content digest or immutable CI artefact ID). A
`#V#software_deployment` identifies one attempt to serve that build in `local`,
`staging` or `production`. `#V#deployment_observation` preserves an attributed
status receipt. These identities are distinct even when the deployment serves a
source checkout directly: give the installation its own immutable build ID and
record the exact source revision separately.

The release input is `deployment_evidence_service.VOCABULARY`. An authenticated
producer explicitly calls `POST /api/tasks/deployment-vocabulary` to create any
missing vocabulary in its normal creation scope. Existing concepts are reused;
this operation neither overwrites definitions nor publishes them globally. Use
canonical ontology publication authority separately when vocabulary or receipts
need broader visibility. A private vocabulary owned by a different actor must
be made available through that route before that actor can ingest receipts.
There is no startup migration, database write script or automatic publication.

## Producer procedure

Use the existing authenticated browser/API session and its active window scope.
The server derives actor and organisation; the payload cannot choose them.
Codex/DGX controllers or CI must use an already authorised session/API adapter;
this feature does not provision credentials, bypass login or grant deployment
permission. The coding worker must return evidence to its controller when its
execution instructions prohibit live writes.

After vocabulary activation, POST this object to `/api/tasks/deployments`:

```json
{
"build_id": "sha256:<actual artefact digest>",
"source_revision": "<full source Git SHA>",
"deployment_id": "dgx-production-<unique attempt ID>",
"environment": "production",
"target": "<served service/PWA URL or target identity>",
"receipt_id": "<unique CI/controller receipt ID>",
"status": "deployed",
"deployed_at": "2026-09-12T21:59:00Z",
"observed_at": "2026-09-12T22:00:00Z",
"evidence": "<receipt locator, observed revision, checks and outcome>",
"implements_tasks": ["#V#task_..."],
"verifies_tasks": []
}
```

Retain the returned concept IDs and canonical read-back. A repeated identical
receipt reuses the same build, deployment, observation and links. Conflicting
content under an existing immutable ID is rejected. After an interrupted or
failed relationship write, retry the same receipt: completed steps are reused.
Identity reuse is scoped to the trusted actor and organisation; two independent
producers do not silently take ownership of each other's records. Controller/CI
retries therefore need a stable producer identity and organisation.

Each status change has a **new receipt ID**, observation time and evidence. It
reuses the deployment ID, build ID, source revision, environment, target and
superseded-attempt reference. Supported statuses are `planned`, `built`,
`deployed`, `verified`, `failed`, `rolled_back` and `superseded`. Planned/built
receipts need not include a deployment time or target. When the target becomes
known it is part of a new deployment identity; do not rewrite a planned
attempt's immutable core. Deployment, verification, rollback and supersession
receipts require a deployment time. Include explicit evidence even for planned
or failed states, so consumers can distinguish a producer report from absence.

`verified` is the producer's evidence-backed observation, not a server-side
health probe or certification. Record the actual served revision and relevant
service/PWA checks, including limitations. A successful code push or process
start alone is insufficient. Ingestion never calls a model, deploys code,
completes a task, selects a product or asserts all linked tasks passed. Per-receipt
task links preserve which work that observation concerned.

History is append-only through this API. Current status is the latest observed
time, so late ingestion of an older receipt does not regress it. Contradictory
statuses/deployment times at the same latest time produce `ambiguous`; submit a
new explicit observation to resolve them. The normal concept store owns access,
creation timestamps and mutation provenance. This ingestion contract is not an
additional immutable-storage boundary against separately authorised generic
concept edits.

## Reads and task work products

- `GET /api/tasks/<task-id>/deployments`: all accessible deployments implementing
or verifying the task, including multiple builds and attempts.
- `GET /api/tasks/deployments/<deployment-concept-id>`: build identity, source,
environment, target, status, history and accessible task links.
- `GET /api/tasks/builds/<build-concept-id>/deployments`: attempts for that build,
with their task links (build-to-task traversal).

`#V#deploys_build`, `#V#implements_task`, `#V#verifies_task`,
`#V#observes_deployment` and `#V#supersedes_deployment` are canonical graph links.
Reverse reads query those same forward edges; no independently updated reverse
list is maintained. Exact structured receipt fields live in the concept's
`attributes.deployment_evidence`; they are not free-text task notes or copied
current-product content. These are actor-visible operational assertions, not
global domain truths.

Task details display builds, attempts, status and receipt history. A verified
attempt offers **Use as current work product**, using the existing
`#V#hascurrentworkproduct` selection route. Opening that product reads its current
structured deployment evidence. A later failure or rollback remains visible;
`ready` on the product response means readable, not successfully deployed.
Other concept products retain their existing text-content behaviour. Links
neither widen visibility nor transfer deployment authority.

## Rollback, redeployment and acceptance

A rollback or redeployment is a **new deployment ID**, even if it serves an
already known build. Supply `supersedes` with the prior deployment concept ID;
it must refer to a different attempt at the same environment and target. Record
the old attempt's rolled-back/superseded observation separately. This preserves
partial outcomes and does not invent a cross-record atomic traffic switch.
A replacement does not automatically prove the old deployment stopped serving.
Unverified deployments remain inspectable and are not offered as verified
product selections.

Candidate tests cover ingestion, reuse, conflicts, traversal, lifecycle,
partial retry, access-filtered reads and the product API. The browser fixture
`tests/browser/taskDeploymentEvidence.cjs` exercises the actual task panel and
CSS at desktop/mobile widths with isolated receipts. It does not prove live
ontology activation or a public deployment.

For representative live acceptance, bind an isolated authenticated candidate
profile to the exact candidate checkout/revision, with a disposable database
and no model generation. Ingest an operator-provided real deployment receipt,
read it through all three traversal APIs, select the verified attempt on an
accessible fixture task, and reopen it after a new unverified/rollback receipt.
Record the candidate SHA, receipt IDs, observed served revision and browser
read-back. Keep deployment credentials outside the checkout. Public Cloudflare
access redirects and browser profiles bound to other checkouts are not evidence
for this candidate. Public deployment and scoped vocabulary activation require explicit task authority; passing this isolated check does not itself activate either.
7 changes: 7 additions & 0 deletions docs/engineering/task_responsibility_continuation.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ choosing a neighbouring product or guessing a revision from timestamps. The
concept inspector remains available for accessible products needing revision
resolution. Product reads do not change task status or start work.


A selected software deployment is a structured work product: its current receipt
history and deployment status are read directly, without requiring a copied
`hasContent` assertion. Task details also show all linked deployment attempts and
allow selecting a verified attempt. See [build and deployment evidence](deployment_evidence.md)
for the distinct build, source revision, deployment and verification identities.

**Discuss** opens the existing task-focused conversation path with the accessible
product reference alongside the task. This also works for human review tasks;
it does not transfer their assignment to Von. For an eligible task assigned to
Expand Down
59 changes: 59 additions & 0 deletions src/backend/server/routes/task_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,62 @@ def get_task_taxonomy_route() -> ResponseReturnValue:
return jsonify({"error": "Internal server error"}), 500


@task_bp.route("/deployment-vocabulary", methods=["POST"])
@task_bp.route("/deployments", methods=["POST"])
@task_bp.route("/deployments/<deployment_id>", methods=["GET"])
@task_bp.route("/builds/<build_id>/deployments", methods=["GET"])
@task_bp.route("/<task_concept_id>/deployments", methods=["GET"])
def deployment_evidence_route(deployment_id=None, build_id=None, task_concept_id=None):
"""Actor-scoped receipt ingestion and represented evidence traversal."""
from ...services import deployment_evidence_service as evidence

actor = _get_current_user_concept_id()
if not actor:
return jsonify({"error": "Authentication required"}), 401
try:
if request.method == "POST":
if request.path.endswith("/deployment-vocabulary"):
evidence.bootstrap_vocabulary(
actor=actor, organisation=_get_current_org_concept_id()
)
return jsonify({"concept_ids": list(evidence.VOCABULARY)}), 200
return (
jsonify(
evidence.ingest_deployment(
request.get_json(silent=True),
actor=actor,
organisation=_get_current_org_concept_id(),
)
),
200,
)
if task_concept_id:
return (
jsonify(
{"deployments": evidence.list_task_deployments(task_concept_id)}
),
200,
)
if build_id:
return (
jsonify({"deployments": evidence.list_build_deployments(build_id)}),
200,
)
return jsonify(evidence.get_deployment(deployment_id)), 200
except TaskNotFoundError:
return jsonify({"error": "Task unavailable"}), 404
except evidence.DeploymentEvidenceError as exc:
return jsonify({"error": str(exc)}), 409 if request.method == "POST" else 404
except Exception:
logger.exception("Deployment evidence operation failed")
return (
jsonify(
{"error": "Evidence operation unavailable; retry the same receipt"}
),
503,
)


@task_bp.route("/<task_concept_id>/work-product", methods=["GET"])
def get_task_work_product_route(task_concept_id: str) -> ResponseReturnValue:
"""Read the selected product through the same actor-visible task boundary."""
Expand All @@ -416,6 +472,9 @@ def get_task_route(task_concept_id: str) -> ResponseReturnValue:
"""Get a task by concept_id."""
try:
result = dict(get_task(task_concept_id))
from ...services.deployment_evidence_service import list_task_deployments

result["deployments"] = list_task_deployments(task_concept_id)
actions = list_task_external_resource_actions(result)
result["external_resource_actions"] = [
{
Expand Down
Loading
Loading