fw: A8 template migration (drop on_fail: continue) and cp-docs-token/v1 layer (be110e25, 1d7ef09a)

Claude-Session: https://claude.ai/code/session_01N218bERyNeLhbLmDRDvbWF
This commit is contained in:
Paul O'Reilly
2026-10-17 20:52:35 +13:00
parent 44334a2143
commit 37a94dcd07
5 changed files with 120 additions and 3 deletions

View File

@@ -0,0 +1,48 @@
# Step documents (cp-docs-token)
This step can read and write planner documents through `python3 -m lib.cp_cli`
in step-docs mode (PLN-CLI-16). The mode is active because `CP_DOCS_TOKEN_FILE`
and `CP_DOCS_CA_FILE` are set and the listener URL is in `CP_DOCS_URL`.
## Credential
- The token is a file at `$CP_DOCS_TOKEN_FILE`. cp-cli reads it on every call.
- Never print, copy, log or pass the token on a command line.
- Do not use `~/.cpconfig`; step-docs mode does not read it.
## Allowed commands
Only these commands work; anything else exits 2 `cp_docs_mode_command_not_allowed`:
```
python3 -m lib.cp_cli planner items documents list <item-uuid>
python3 -m lib.cp_cli planner items documents get <item-uuid> <name> [--json]
python3 -m lib.cp_cli planner items documents put <item-uuid> <name> --file <path> (--create-only | --if-match <rev>)
python3 -m lib.cp_cli planner items documents related [--json]
```
Item arguments are full UUIDs. Do not pass a project id.
## Scope
- You may write only documents on this step's own item.
- You may read documents on this step's item, its parent and its siblings.
- `related` prints `item_uuid`, `parent_uuid`, `sibling_uuids` and `siblings_truncated`.
## Writes need a precondition
Exactly one of these is required on every `put` (neither or both exits 2):
- `--create-only` creates a new document (sends `If-None-Match: *`).
- `--if-match <rev>` updates an existing one (sends `If-Match`). Take `<rev>`
from `get --json` (the ETag revision); surrounding quotes are stripped.
## Errors
- 412 `precondition_failed`: the document changed or already exists. Re-run
`get --json`, merge your change into the current content, and `put` again
with the new revision.
- 428 (precondition missing): add `--create-only` or `--if-match`.
- 429 `step_docs_write_budget_exhausted` or `step_docs_rate_limited`: stop
writing; do not retry in a loop.
- 404 `not_found`, 400 `invalid_document_name`: fix the item uuid or name.

View File

@@ -0,0 +1,16 @@
kind: context
name: cp-docs-token
version: 1
description: "Step-docs mode env contract for lib.cp_cli (PLN-CLI-16): per-step wfcd_ token file and CA file delivered by the dispatcher; document access scoped to this step's item (spec/dispatcher.md D-40, spec/authentication.md AU-59)"
requires: []
provides: [cp-docs-token]
# Env-only layer: the dispatcher delivers the token and CA files into the
# container; this layer sets only the paths. No secrets_required, no init script.
env:
CP_DOCS_TOKEN_FILE: /run/agent/cp-docs/token
CP_DOCS_CA_FILE: /run/agent/cp-docs/ca.crt
context_files:
- source: ./CLAUDE.md
target: /opt/harness/context/cp-docs-token/CLAUDE.md

View File

@@ -24,7 +24,6 @@ nodes:
kind: script
cmd: scripts/workflow/state-tx
cp_write: true
on_fail: continue
runtime_env:
CP_URL: "https://agents.oreillyit.nz/api"
STATE_TX_NEW_STATE: test-skeleton-ready

View File

@@ -29,12 +29,10 @@ nodes:
kind: script
cmd: scripts/workflow/preflight-test-fail-mode
cp_write: false
on_fail: continue
- id: state-tx
kind: script
cmd: scripts/workflow/state-tx
cp_write: true
on_fail: continue
runtime_env:
CP_URL: "https://agents.oreillyit.nz/api"
STATE_TX_NEW_STATE: test-validated

View File

@@ -0,0 +1,56 @@
"""cp-docs-token/v1 context: env-only step-docs layer (PLN-CLI-16, D-40, AU-59).
The per-step wfcd_ token and CA file are delivered by the dispatcher. The layer
sets the env contract and ships one context doc; no ESO mount, no init script,
and no reference to the cp-service-token layer.
"""
from __future__ import annotations
from pathlib import Path
import yaml
_LAYER = (
Path(__file__).resolve().parents[2] / "harnesses" / "contexts" / "cp-docs-token" / "v1"
)
def _doc() -> dict:
return yaml.safe_load((_LAYER / "harness.yaml").read_text())
def test_cp_docs_token_layer_parses_with_env_keys():
doc = _doc()
assert doc["kind"] == "context"
assert doc["name"] == "cp-docs-token"
assert doc["version"] == 1
assert doc["requires"] == []
assert doc["provides"] == ["cp-docs-token"]
assert doc["env"] == {
"CP_DOCS_TOKEN_FILE": "/run/agent/cp-docs/token",
"CP_DOCS_CA_FILE": "/run/agent/cp-docs/ca.crt",
}
def test_cp_docs_token_layer_declares_no_secrets_and_no_init():
doc = _doc()
assert not doc.get("secrets_required")
assert not doc.get("scripts")
assert not (_LAYER / "init.sh").exists()
def test_cp_docs_token_layer_context_file_mapping():
doc = _doc()
assert doc["context_files"] == [
{
"source": "./CLAUDE.md",
"target": "/opt/harness/context/cp-docs-token/CLAUDE.md",
}
]
assert (_LAYER / "CLAUDE.md").is_file()
def test_cp_docs_token_layer_never_mentions_cp_service_token():
for path in _LAYER.rglob("*"):
if path.is_file():
assert "cp-service-token" not in path.read_text(), path