Define Module → Driver → Profile → Pack execution contract (v1)¶
Status¶
Proposed — Establish a stable contract for how HybridOps modules are executed and verified, independent of internal tooling changes.
1. Context¶
HybridOps.Core is the product runtime for governed, repeatable platform runs. It requires:
- A stable module contract that does not change when internal tooling evolves.
- A clear separation between intent (module) and execution policy (profile) and tool plans (packs).
- Deterministic runtime layout, evidence capture, and redaction behaviour.
- A packaging model that remains functional without a Git repository context (tarball-safe distribution).
This ADR formalises the execution contract as the basis for modules shipped to users and for internal platform evolution.
2. Decision¶
HybridOps.Core adopts a four-part execution model:
1) Module — intent contract (what) 2) Driver — execution engine (how) 3) Profile — policy and defaults (how, consistently) 4) Pack — tool plan bundle (what to run, tool-specific data)
The module spec MUST select:
execution.driver— driver reference (e.g.,iac/terragrunt)execution.profile— profile reference (e.g.,default@v1.0)execution.pack_ref.id— pack identifier, resolved by the driver (e.g.,gcp/org/00-project-factory@v1.0)
3. Contract definitions¶
3.1 Module¶
A module is declarative data describing intent and verification.
A module MUST define:
inputs.defaults— default inputs (operator may override)execution.driverexecution.profileexecution.pack_ref.id
A module SHOULD define:
requirements.credentials(credential pack identifiers, not values)outputs.publish(what to expose from normalized outputs)probes(verification steps or probe identifiers, where applicable)constraints(semantic constraints on inputs and environment)
A module MUST NOT embed tool implementation details, including but not limited to:
- Terraform/Terragrunt module sources
- backend/state/workspace wiring
- CLI flags and tool invocation parameters
- secrets or secret material
- environment naming assumptions (dev/prod/staging)
- repo-specific path assumptions
3.2 Driver¶
A driver is the execution engine that:
- prepares an isolated work directory for a run
- resolves and materialises the selected pack
- interprets the selected profile
- invokes tools (e.g., Terragrunt)
- captures evidence (redacted) and returns a deterministic result payload
Drivers MUST be tarball-safe and MUST NOT depend on .git, get_repo_root(), or working directory conventions.
Drivers MUST write evidence under the resolved runtime root and MUST NOT write secrets into evidence artifacts.
3.3 Profile¶
A profile is a versioned policy bundle interpreted by a driver.
Profiles MUST be owned by the driver domain (e.g., terragrunt driver profiles live with the terragrunt driver).
Profiles SHOULD govern:
- runner model (local vs remote)
- backend/state selection and workspace naming
- toolchain policy (pinned/allowed versions)
- templates/hooks that are driver-owned (generated into the run workdir)
- logging and redaction defaults
- retry/timeouts, safety flags
Profiles MUST NOT redefine module intent. Profiles are permitted to provide defaults only where consistent policy is required.
3.4 Pack¶
A pack is a tool-specific plan bundle executed by a driver (e.g., a Terragrunt stack tree).
Packs MUST be immutable inputs to a run (drivers copy packs into an isolated workdir).
Packs MUST be tarball-safe: packs MUST NOT depend on repo-specific functions or paths. Any runtime-injected configuration MUST be provided via environment variables or generated files owned by the driver/profile.
Packs MUST NOT own backend/workspace naming rules. This belongs to driver/profile policy.
4. Runtime invariants¶
4.1 Runtime root precedence¶
All commands that write runtime artifacts MUST resolve the runtime root using:
1) --root <path>
2) $HYOPS_RUNTIME_ROOT
3) ~/.hybridops
Commands MUST NOT infer a repository root for runtime outputs.
4.2 Evidence paths¶
Evidence MUST be deterministic and structured:
- Module runs:
<root>/logs/module/<module_id>/<run_id>/
Evidence must be redacted by default for subprocess stdout/stderr.
4.3 Execution workdir (pack materialisation)¶
For each module run, the driver MUST create an isolated work directory under the runtime root:
- Workdir:
<root>/work/<module_id>/<run_id>/ - Stack working copy:
<root>/work/<module_id>/<run_id>/stack/
The driver MUST materialise the selected pack by copying the pack stack directory into the stack working copy:
- Pack source:
packs/<execution.driver>/<execution.pack_ref.id>/stack/ - Run destination:
<root>/work/<module_id>/<run_id>/stack/
The driver MAY render profile-owned templates and generated inputs into the same stack working copy
(for example inputs.auto.tfvars.json and driver/profile-owned *.hcl overlays).
5. Input override contract¶
Operator-provided inputs are resolved in this precedence order:
1) inputs.defaults in module spec
2) --inputs <file>.yml (if provided)
3) Environment overrides (HYOPS_INPUTS_JSON, HYOPS_INPUT_*) when enabled by the runtime
The runtime MUST document the override mechanisms and MUST treat them as stable behaviour for users once enabled.
6. Consequences¶
6.1 Positive¶
- Stable module contract with clear boundaries.
- Drivers and packs can evolve independently without breaking the public spec.
- Tarball-safe packaging is achievable without repository assumptions.
- Evidence and output normalisation becomes consistent across tools.
6.2 Negative / risks¶
- Requires strict discipline to keep modules tool-agnostic.
- Requires CI checks to prevent “policy creep” into packs and “intent creep” into drivers.
7. Alternatives considered¶
- Tool-first structure (driver/packs define everything) — rejected due to unstable user contract.
- Repo-layout-driven execution (implicit roots) — rejected due to tarball incompatibility and non-deterministic runtime outputs.
- Embedding backend/state in packs — rejected; backend policy is a profile concern.