ADR-007: Enforce the do/ Script Contract (Convention Made True)¶
Status¶
Accepted — in progress (Wave 6)
Context¶
Generated projects ship a do/ directory of runtime scripts (build, push,
deploy, benchmark, tune, clean, …). A well-designed convention already
governs them, documented in docs/do-script-contract.md:
- a standard header — shebang, copyright/SPDX, one-line description;
- an
@mlcc-scriptblock with four fields (type,guard,lifecycle,targets); source lib/script-contract.shas the first source, which reads the# guard:annotation and auto-enforces it (exit code3on violation);_require_guardfor inline flag escalation and_guard_metfor non-enforcing queries.
The convention is sound. The problem Wave 6 addresses is that reality has drifted from it and nothing catches the drift:
- Header regressions.
do/deployanddo/cleanare missing the copyright/SPDX header.do/benchmarkis worse: its--flag=valuenormalization loop sits above the header, so the file has no shebang and no copyright line — only a stray SPDX comment on line 13. - Hand-rolled target guards with inconsistent exit codes.
do/ci,do/add-ic,do/optimize, anddo/benchmarkeach open-code a "not supported on<target>"if/caseblock that exits1. The doc reserves exit3for contract/target violations (so CI and the advisory agent can distinguish "couldn't start" from "failed").do/statususes a bespoke exit4for an AWS-credentials failure. - Stale registry. The doc's registry table lists 23 scripts;
do/draftships with a full contract block but is absent from the table, and the doc'sdo/draftexample liststargets: realtime-inference, hyperpod-ekswhile the shipped file declarestargets: hyperpod-eksonly (realtime speculative decoding is handled viado/optimize --apply). - No enforcement test.
test/unit/do-script-contracts.test.jsexercisesscript-contract.sh's guard functions thoroughly via synthetic scripts, but never iterates the real scripts to validate their headers — which is exactly why the deploy/clean/benchmark regressions went unnoticed.
Separately, the script bodies carry consolidatable duplication: an AWS
credential-preflight block (aws sts get-caller-identity → export
AWS_ACCOUNT_ID) is copy-pasted in ~10 places (push, submit, status, stage, and
every deploy.d/* / clean.d/* target script), and a JSON parse-back idiom
(… | grep -E '^\{' | tail -1 piped into a python3 -c json.load one-liner) is
repeated across dozens of call sites in the large helper-driven scripts.
Decision¶
Make the documented convention true and enforced, and pull the highest-value
duplication into do/lib/. Do not redesign the contract — it is good; close the
gap between it and the code.
1. A test that enforces the contract on every script¶
Add a conformance test that enumerates every contract-bearing top-level do/
script and asserts:
- a
#!/bin/bashshebang on line 1, followed by the copyright + SPDX header; - an
@mlcc-scriptblock with all four fields, each a valid enum value per the doc tables; source .../lib/script-contract.shis present.
Two documented exceptions are encoded explicitly: do/config is a sourced data
file (carries the contract block for the agent, but is not an executable step,
so it does not source the enforcer), and do/manifest is a thin Node shim
(sources the enforcer but delegates to lib/manifest-cli.js rather than sourcing
config/profile.sh). The test fails against the pre-fix tree — proving it
catches the drift.
2. Restore the drifted headers¶
do/deploy, do/clean, do/benchmark get the standard shebang +
copyright/SPDX header. do/benchmark's arg-normalization loop moves below the
header and set -e/-u/-o pipefail preamble. Behavior is unchanged.
3. A _restrict_targets helper — one target-guard, exit 3¶
Add _restrict_targets <comma,list> to script-contract.sh. It compares the
current DEPLOYMENT_TARGET against the allowed list and, on mismatch, emits the
standard contract-violation format and exits 3. The hand-rolled blocks in
ci, add-ic, optimize, and benchmark migrate onto it, preserving their
specific guidance text via the helper's remedy argument. This unifies the exit
code (3) and format while keeping the user-facing message intent.
do/status's AWS-credentials check is not a contract violation — it is a
general runtime error — so it standardizes to exit 1 (the documented
"general error"), not 3. Exit 4 is retired.
4. Extract do/lib/aws-preflight.sh¶
Move the repeated credential-preflight block into do/lib/aws-preflight.sh,
exposing a helper that validates credentials (with the standard message) and
exports AWS_ACCOUNT_ID. The ~10 copies source it instead. The new lib file
carries a module-header docblock naming its role.
5. Refresh the docs to match reality¶
Update docs/do-script-contract.md: add do/draft to the registry, reconcile
its targets with the shipped file, standardize the header example on
#!/bin/bash (matching every shipped script), document the config /
manifest exceptions, document _restrict_targets and aws-preflight.sh, and
cross-reference the shell↔JS contract enforced by src/lib/do-config.js
(config lines must be single-line export UPPER_SNAKE=…; only the
SHELL_VAR_TO_ANSWER keys round-trip to generator answers). Add
docs/architecture/do-scripts.md as the Wave 6 corpus explainer.
Out of scope (filed as backlog)¶
The JSON parse-back idiom consolidation is deferred. It spans dozens of call
sites inside the large helper-driven scripts (register, train, tune,
adapter, …), is orthogonal to the contract-convention theme of this wave, and
carries real behavioral risk (each site parses different fields with subtly
different error handling). Filed as a backlog item to be tackled as its own
focused pass, with the aws-preflight.sh extraction as the proven template.
Consequences¶
- Positive: the contract is enforced by a test, so header/exit-code drift
cannot silently return. Target restrictions speak one exit code (3) and one
format. The AWS-preflight duplication collapses to one lib file. The registry
and doc match the shipped scripts. An AI maintainer reading one
do/script sees the same shape every time and can trust the doc. - Cost: header edits to three scripts; a new lib file + ~10 call-site migrations; four target-guard migrations; doc refresh. All behavior-preserving.
- Risk: low–medium. The
deploy.d/*/clean.d/*migrations touch the deploy/teardown path; mitigated by keeping each script's behavior identical and verifying against the existing do-script / do-config suites plus a generation smoke test. The JSON-idiom deferral keeps the high-risk change out of this wave.
Follow-up (post-wave re-audit)¶
A per-script re-audit after the wave produced
do-scripts-wave6-changelog.md
(a reviewable before/after record) and caught one behavior change that had been
made implicitly: do/adapter had started rejecting the eks target. eks
(EKS without the HyperPod Inference Operator) is a first-class — if currently
untested — target, so this was corrected to allow LoRA on eks (routed through
the same vLLM hot-load path as hyperpod-eks). The audit also found eks was
missing from _guard_deployment_active, the config status vars, do-config.js,
the regenerate preservation list, and the conformance target enum; all were
wired so eks is first-class at the guard/status layer. Adding a new target is
now documented in
deployment-target-authoring.md.
References¶
docs/do-script-contract.md— the contract convention + script registrydocs/architecture/do-scripts.md— how thedo/subsystem works today (Wave 6)docs/architecture/deployment-target-authoring.md— how to add a deployment targetdocs/architecture/do-scripts-wave6-changelog.md— per-script before/after recordtemplates/do/lib/script-contract.sh— the guard enforcer (+_restrict_targets)templates/do/lib/aws-preflight.sh— the shared credential preflightsrc/lib/do-config.js— the shell↔JS config contract (ADR-005, Wave 4)- ADR-002 — the consolidation program