akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
98 lines (88 loc) • 5.2 kB
Markdown
0004 — The shared input contract, generalized from workflow params
# Context
Before task source v4, workflow parameters (`WorkflowParameterPlan` /
`WorkflowParameterFlag` in `src/workflows/ir/params.ts`) had their own
pure validation/coercion vocabulary, and nothing else in the codebase
needed the same shape. Task source v4 introduced typed `inputs:`
declarations on tasks (P2a), which need the identical kind of contract —
declared names, types, defaults, required-ness, JSON-schema validation —
but for a different caller (a task) with its own diagnostics vocabulary
(different error codes and messages than a workflow parameter flag). Rather
than duplicate the logic, P2a generalized the workflow-only module into a
shared one.
# Decision
Moved verbatim from `src/execution/input-contract.ts`'s module header:
The shared input contract (P2a Lane B, docs/plans/specs/
p2a-task-source-v4.md §4, §1.3 D3, and the disambiguations
D3-N1/D3-N2/D3-N3 in §1.5).
> This module GENERALIZES the pure module `src/workflows/ir/params.ts`
(`WorkflowParameterPlan`, `WorkflowParameterFlag`,
`materializeWorkflowParameterFlags`, `validateWorkflowParams`,
`assertRunParamsSatisfyPlan`) into a contract-shaped vocabulary that both
workflow params AND task source v4's `inputs:` declarations can consume.
`src/workflows/ir/params.ts` becomes a THIN CONSUMER of this module: it
adapts a `WorkflowParameterPlan` into an `InputContract`
(`contractFromPlan`), supplies the workflow-specific diagnostics
vocabulary, and re-exports its three wrappers under their existing names
— byte-identical messages, codes, and hints
(`tests/workflows/workflow-param-flags.test.ts`,
`tests/integration/workflows/params-validation.test.ts`).
> `TaskInputBinding` is declared here for P2b (task source v4 `inputs:`
delivery). P2a only ever produces `kind: "literal"` bindings, and nothing
consumes them yet — P2a's contract for inputs is VALIDATION only
(docs/plans/specs/p2a-task-source-v4.md §0).
> **D3-N1 (import boundary):** `src/execution/**` must never import
`src/workflows/**` — `tests/architecture/import-cycle-ratchet.test.ts`
runs a shrink-only, EMPTY baseline, and an `execution -> workflows` edge
would close a cycle the moment `workflows/ir/params.ts` imports back into
this module. `INPUT_NAME_PATTERN` is therefore DEFINED here (identical
source/flags to today's `PROGRAM_PARAM_NAME_PATTERN`) rather than
imported from the workflow program vocabulary; `src/workflows/program/
schema.ts` becomes a re-export of this constant instead. Permitted
imports: `node:crypto`, `src/core/errors`, `src/core/json-schema`, and
`src/execution/**` — see `tests/execution/input-contract.test.ts`'s
purity scan, which reads this file as text/AST and asserts the allowlist.
> **D3-N2 (canonical JSON):** `canonicalInputJson`/`canonicalInputHash`
implement the SAME recursive key-sort + `JSON.stringify` as
`canonicalJson` (`src/workflows/ir/plan-hash.ts`) locally, rather than
importing it — `plan-hash.ts` also imports `./schema-v4` and
`../resource-limits`, so importing it would drag workflow IR into
`src/execution/**` and violate D3-N1. `tests/execution/
input-contract.test.ts` asserts BYTE EQUALITY with `canonicalJson` over a
fixture set covering nested objects, arrays, `null`, unicode keys, and
insertion-order permutations, so the duplication cannot drift unnoticed.
> **D3-N3 (injected diagnostics vocabulary):** `materializeInputFlags`
contains no literal user-facing string. Every message/code it raises is
produced by a caller-supplied `InputFlagDiagnostics` — `params.ts`'s
`WORKFLOW_PARAMETER_DIAGNOSTICS` reproduces today's five
workflow-parameter strings/codes exactly; `src/tasks/source/
task-input-diagnostics.ts` supplies the task vocabulary.
> Pure module: no IO, no engine imports.
# Consequences
- Workflow params and task `inputs:` share exactly one validation/coercion
implementation; a bug fix or a new capability (e.g. a new JSON-schema
subset feature) lands once and both callers get it.
- The two callers can still speak entirely different user-facing vocabulary
(different error codes, different hint text) because the diagnostics are
injected, not hardcoded — `materializeInputFlags` itself never needs to
know which caller it is serving.
- `src/execution/**` staying import-free of `src/workflows/**` is a
standing constraint, not a one-time fact: any future change that makes
this module (or anything it imports) reach into `src/workflows/**` closes
an import cycle and fails `tests/architecture/import-cycle-ratchet.test.ts`'s
shrink-only, empty baseline.
- The canonical-JSON duplication between here and `workflows/ir/plan-hash.ts`
is deliberate, not an oversight — a future refactor that "de-duplicates"
it by importing across the D3-N1 boundary is the violation, not the
duplication itself.
# Provenance
- Source: `src/execution/input-contract.ts`, module header.
- Spec: `docs/plans/specs/p2a-task-source-v4.md` §4, §1.3 D3, §1.5
(D3-N1/D3-N2/D3-N3).
- Extracted: P4 (`docs/plans/specs/p4-deletions-closeout.md` §4.2), 2026-08-27.