feat: guard prod status since-arg; plan Glas contract adoption
scripts/prod_automation_status.sh took a positional since value, so the
Makefile's own `SINCE=sunday` form passed the literal string into timestamptz:
all five sections errored while the run still looked like it produced a report.
Now `SINCE=` is accepted and the value is parsed and normalised to UTC up
front, failing fast with usage and exit 2 instead of five parse errors.
ACTIVITY-WP-0032 plans adoption of the glas-harness contract 1.0 reported in
GLAS-WP-0004. WP-0026 is finished, so this gets its own plan. The motivation is
concrete: ops_run.approach_hint binds at claim time, which produced a failed
run on 2026-08-17 ("no approach matched labels/definition") after it had
already consumed a claim and a lease.
T01 is deliberately blocking: our claim path is pull-based and the Glas
contract is a call, so the invocation shape is an architectural decision, not a
port. Two questions are outstanding with glas-harness.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
459a272974
commit
17f2caee01
2 changed files with 210 additions and 1 deletions
|
|
@ -9,6 +9,22 @@ SINCE_ARG="${1:-sunday}"
|
||||||
SSH_HOST="${PROD_AUTOMATION_SSH_HOST:-railiance01}"
|
SSH_HOST="${PROD_AUTOMATION_SSH_HOST:-railiance01}"
|
||||||
NS="${PROD_AUTOMATION_NS:-activity-core}"
|
NS="${PROD_AUTOMATION_NS:-activity-core}"
|
||||||
|
|
||||||
|
# The Makefile target is `make prod-automation-status SINCE=sunday`, so operators
|
||||||
|
# reasonably type the same `SINCE=` form when calling the script directly. Accept
|
||||||
|
# it rather than passing the literal into timestamptz, where every section fails
|
||||||
|
# with a separate parse error while the run still looks like it produced a report.
|
||||||
|
if [[ "$SINCE_ARG" == SINCE=* ]]; then
|
||||||
|
SINCE_ARG="${SINCE_ARG#SINCE=}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat >&2 <<'USAGE'
|
||||||
|
Usage: ./scripts/prod_automation_status.sh [since]
|
||||||
|
since: "sunday" (default) | an ISO-8601 timestamp, e.g. 2026-08-17T00:00:00Z
|
||||||
|
Read-only. Requires SSH to the production host.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
if [[ "$SINCE_ARG" == "sunday" ]]; then
|
if [[ "$SINCE_ARG" == "sunday" ]]; then
|
||||||
# Floor of most recent Sunday 00:00 Europe/Berlin in UTC (portable enough for ops).
|
# Floor of most recent Sunday 00:00 Europe/Berlin in UTC (portable enough for ops).
|
||||||
SINCE_UTC="$(python3 - <<'PY'
|
SINCE_UTC="$(python3 - <<'PY'
|
||||||
|
|
@ -22,7 +38,27 @@ print(sunday.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S%z"))
|
||||||
PY
|
PY
|
||||||
)"
|
)"
|
||||||
else
|
else
|
||||||
SINCE_UTC="$SINCE_ARG"
|
# Validate before the value reaches five separate SQL/JSON consumers: an
|
||||||
|
# unparseable `since` must fail fast and loudly, not produce a report whose
|
||||||
|
# every section is an error the reader has to notice.
|
||||||
|
if ! SINCE_UTC="$(python3 - "$SINCE_ARG" <<'PYVALIDATE'
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
raw = sys.argv[1].strip()
|
||||||
|
try:
|
||||||
|
parsed = datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
||||||
|
except ValueError:
|
||||||
|
sys.exit(1)
|
||||||
|
if parsed.tzinfo is None:
|
||||||
|
parsed = parsed.replace(tzinfo=timezone.utc)
|
||||||
|
print(parsed.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S%z"))
|
||||||
|
PYVALIDATE
|
||||||
|
)"; then
|
||||||
|
echo "error: could not parse since value: ${SINCE_ARG}" >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "=== prod automation status (railiance01) since ${SINCE_UTC} ==="
|
echo "=== prod automation status (railiance01) since ${SINCE_UTC} ==="
|
||||||
|
|
|
||||||
173
workplans/ACTIVITY-WP-0032-glas-profile-execution-contract.md
Normal file
173
workplans/ACTIVITY-WP-0032-glas-profile-execution-contract.md
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
---
|
||||||
|
id: ACTIVITY-WP-0032
|
||||||
|
type: workplan
|
||||||
|
title: "Adopt the Glas profile-driven execution contract"
|
||||||
|
domain: infotech
|
||||||
|
repo: activity-core
|
||||||
|
status: proposed
|
||||||
|
owner: claude
|
||||||
|
topic_slug: activity-core
|
||||||
|
priority: medium
|
||||||
|
created: "2026-08-21"
|
||||||
|
updated: "2026-08-21"
|
||||||
|
related:
|
||||||
|
- ACTIVITY-WP-0026
|
||||||
|
- ACTIVITY-WP-0029
|
||||||
|
- ACTIVITY-WP-0031
|
||||||
|
- ACT-ADR-005
|
||||||
|
- GLAS-WP-0004
|
||||||
|
- REIN-A-0002
|
||||||
|
---
|
||||||
|
|
||||||
|
# Adopt the Glas profile-driven execution contract
|
||||||
|
|
||||||
|
## Origin
|
||||||
|
|
||||||
|
`glas-harness` reported (2026-08-20) that GLAS-WP-0004 contract 1.0 is
|
||||||
|
implemented and proven: the same bounded task completed through **two**
|
||||||
|
different reins (`rein-aharness`, `rein-openweights`) via an explicit
|
||||||
|
`harness_profile_ref`, with real commits and verified sandbox destruction.
|
||||||
|
Their guidance to us: activity-core stays scheduler and task source, and should
|
||||||
|
name an **approved profile**, never a concrete rein.
|
||||||
|
|
||||||
|
ACTIVITY-WP-0026 is `finished`, so this does not belong as an amendment to it.
|
||||||
|
|
||||||
|
## Why this is worth doing
|
||||||
|
|
||||||
|
Today an ops_run carries `approach_hint` (`orm.py:172`), a free-text string the
|
||||||
|
executor interprets at claim time. That binding is too late and too weak. Live
|
||||||
|
example from railiance01, 2026-08-17:
|
||||||
|
|
||||||
|
```
|
||||||
|
state=failed attempt=1 claim_owner=rein-aharness@railiance01
|
||||||
|
title=Run SBOM rescan for binect-js
|
||||||
|
failure=no approach matched labels/definition;
|
||||||
|
labels=['sbom','security','automated'] def='2969816c-…'
|
||||||
|
```
|
||||||
|
|
||||||
|
The run was claimed, then died because nothing could execute it. A
|
||||||
|
`harness_profile_ref` is resolved and **refused before sandbox creation**, so
|
||||||
|
an unroutable request fails at emission with a clear reason instead of
|
||||||
|
consuming a claim and a lease.
|
||||||
|
|
||||||
|
Second motivation: we are currently coupled to one backend in practice —
|
||||||
|
every claim in the last week is `rein-aharness@railiance01`. The dual-rein
|
||||||
|
proof means that coupling is now avoidable rather than inherent.
|
||||||
|
|
||||||
|
## Boundary (must not drift)
|
||||||
|
|
||||||
|
activity-core stays **when / what / where**. Adopting this contract must not
|
||||||
|
turn it into an executor — SCOPE drift risk #1 ("convenience execution"). We
|
||||||
|
emit an authorized, profile-named request and record normalized evidence. We do
|
||||||
|
not select reins, provision sandboxes, acquire credentials, or run the inner
|
||||||
|
agentic loop. Glas owns profile resolution and the outer loop; the rein owns its
|
||||||
|
own inner loop; `sand-boxer` owns isolation.
|
||||||
|
|
||||||
|
We also do not author the attribution refs the contract accepts
|
||||||
|
(`assignment_ref`, `role_ref`, `duty_ref`, `goal_refs`,
|
||||||
|
`resource_envelope_refs`). Those come from workforce/leadership vocabulary in
|
||||||
|
`info-tech-canon`; activity-core carries them through, and must not invent org
|
||||||
|
roles here (ACTIVITY-WP-0029 responsibility map).
|
||||||
|
|
||||||
|
## Open questions (asked of glas-harness 2026-08-21, not yet answered)
|
||||||
|
|
||||||
|
1. Is `approach_hint` expected to be **replaced** by `harness_profile_ref`, or
|
||||||
|
to coexist?
|
||||||
|
2. Is the profile registry authoritative in glas-harness, so we validate refs by
|
||||||
|
calling them rather than mirroring a catalogue that can drift?
|
||||||
|
|
||||||
|
T01 is blocked on these; the rest follows the answer.
|
||||||
|
|
||||||
|
## Decide the invocation shape
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: ACTIVITY-WP-0032-T01
|
||||||
|
status: wait
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Our claim path is **pull-based**: we insert `ops_run(open)` and
|
||||||
|
rein-aharness claims it. The Glas contract is shaped as a **call**. Those do not
|
||||||
|
compose automatically, and the choice is architectural, not mechanical. Record
|
||||||
|
a decision (ACT-ADR) between:
|
||||||
|
|
||||||
|
- **A — queue carries the profile.** `ops_run` grows `harness_profile_ref` plus
|
||||||
|
the attribution refs; a Glas-aware claimer resolves the profile. Keeps lease
|
||||||
|
semantics, durability, and the operator console unchanged. Keeps one more
|
||||||
|
indirection between authorization and execution.
|
||||||
|
- **B — activity-core calls the Glas gateway.** Direct invocation with the
|
||||||
|
approved profile. Matches the contract as designed and fails unroutable
|
||||||
|
requests before sandbox creation. Costs us the claim/lease/retry machinery
|
||||||
|
ACTIVITY-WP-0026 just built, and moves activity-core closer to the executor
|
||||||
|
boundary we deliberately hold.
|
||||||
|
|
||||||
|
Do not start T02+ before this is resolved. Prefer A unless glas-harness states
|
||||||
|
the profile must be resolved at emission time to keep its refusal guarantee.
|
||||||
|
|
||||||
|
## Carry the profile and attribution refs
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: ACTIVITY-WP-0032-T02
|
||||||
|
status: wait
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
Depends on T01. Add `harness_profile_ref` and the correlation/attribution refs
|
||||||
|
to the ops_run model, emission path (`activities.py`), queue projection
|
||||||
|
(`ops_run_queue.py`), and run artefacts (`run_artifacts.py`), with a migration.
|
||||||
|
Definitions declare the profile; rules pass it through. Decide `approach_hint`'s
|
||||||
|
fate per open question 1 — if it coexists, document which wins; if it is
|
||||||
|
replaced, strangle it rather than leaving two routing fields.
|
||||||
|
|
||||||
|
## Validate refs at emission
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: ACTIVITY-WP-0032-T03
|
||||||
|
status: wait
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
Depends on T01/T02. An unknown or incompatible profile must be refused when the
|
||||||
|
ActivityDefinition is synced or the run is emitted — not discovered at claim
|
||||||
|
time, which is the failure mode this workplan exists to remove. Per open
|
||||||
|
question 2, prefer calling the glas-harness registry over mirroring it. Include
|
||||||
|
the offline/unreachable behaviour: a registry we cannot reach must not silently
|
||||||
|
downgrade to "emit anything".
|
||||||
|
|
||||||
|
## Record normalized execution evidence
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: ACTIVITY-WP-0032-T04
|
||||||
|
status: wait
|
||||||
|
priority: low
|
||||||
|
```
|
||||||
|
|
||||||
|
Depends on T02. Glas emits normalized, non-secret execution evidence. Land it in
|
||||||
|
`ops_runs.result` and the evidence sink so the production status surface
|
||||||
|
(ACTIVITY-WP-0031-T03) shows what actually executed, through which profile and
|
||||||
|
rein. Keep the field allowlist discipline from ACTIVITY-WP-0031: no provider
|
||||||
|
blobs, no credential material in run artefacts.
|
||||||
|
|
||||||
|
## Prove on one definition
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: ACTIVITY-WP-0032-T05
|
||||||
|
status: wait
|
||||||
|
priority: low
|
||||||
|
```
|
||||||
|
|
||||||
|
Depends on T03/T04. Convert exactly one low-risk definition, run it on
|
||||||
|
railiance01, and capture evidence. Do not convert the fleet before one
|
||||||
|
definition is proven end to end. Note that FI and Binky are unsuitable as the
|
||||||
|
pilot while ACTIVITY-WP-0031-T01 is unresolved — their failures are currently
|
||||||
|
provider-credential failures and would mask the result.
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
- [ ] Invocation shape decided and recorded as an ADR, with the boundary stated
|
||||||
|
- [ ] ops_run carries an approved `harness_profile_ref`; no definition names a
|
||||||
|
concrete rein
|
||||||
|
- [ ] Unroutable profiles are refused at emission, not at claim
|
||||||
|
- [ ] `approach_hint` is either strangled or documented as subordinate
|
||||||
|
- [ ] Normalized Glas evidence is visible in production status
|
||||||
|
- [ ] One definition proven on railiance01 end to end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue