T-JIBO-shared-layered-resolution-primitive-provenance
State: closed/done · Impact: medium · Complexity: medium
Build the one resolver D-LSLH-collapse-verbs-workflows-chain-hooks
centers on, with no callers touching it yet: a generic “env → local →
project → default, replace-or-wrap” step-list resolver, plus the
{layer, mode} provenance shape, as a standalone, fully-tested module.
Nothing else changes behavior in this task — it lands dead code exercised
only by its own unit tests, so it can be reviewed without also reviewing the
verbs:/workflows: schema cutover.
Verbs (resolveVerb, lib/config/verbs.ts) and workflows (resolveWorkflow,
lib/config/workflows.ts) each implement their own independent cascade:
verbs is env → local → project → default → none with full-replace-only
semantics per layer; workflows is default → project(replace-or-wrap) →
local(replace-or-wrap) with no env layer and no local/project distinction
(it reads the pre-merged config). Hooks have no resolver at all. Three
provenance vocabularies (VerbSource, WorkflowStepSource, and hooks’
total absence of one) exist for what should be one concept.
Proposed
Section titled “Proposed”A single layered-resolution primitive backs verbs, workflows, and hooks
alike: env → local → project → default, with local/project each able to
fully replace OR wrap (prepend/append) whatever the layer beneath
resolved to. Every resolved step carries {layer, mode} provenance. A
sibling scalar resolver (resolveLayeredValue) covers chain’s simpler
single-binding case (no mode, since there’s nothing to wrap).
Approach
Section titled “Approach”- New file
solutions/ontological/lib/config/resolve.ts:ConfigLayer = 'env' | 'local' | 'project' | 'default' | 'none',StepMode = 'default' | 'replace' | 'prepend' | 'append',StepProvenance = { layer: ConfigLayer; mode: StepMode }.resolveLayeredSteps<T>(name, opts): { steps: { step: T; source: StepProvenance }[] }generic over the step typeT, takingdefaultSteps: T[] | (() => Promise<T[]>)(the async form is howsetup’s heuristic plugs in later, inT-4OUZ— this task can stub it with a sync array and a unit test that passes an async default),readRaw(layer: 'local' | 'project'): { steps?: T[]; prepend?: T[]; append?: T[] } | undefined(T-4OUZ/T-4KYNsupply this per-section; this task’s own tests supply a fake in-memory version), andenvSteps?: T[] | undefined(already parsed by the caller — this module does not know aboutSDLC_WORKFLOW_<NAME>’s env-var name shape, only about “a caller-supplied full replacement list, if any”).resolveLayeredValue<V>(name, opts): { value: V; layer: 'local' | 'project' | 'default' }for the scalar case (chain’s stage binding) — nomode, noenv, reusing the samereadRaw-style shape narrowed to a single value.
- Unit tests (
resolve.test.ts) covering: default-only (mode'default', layer'default'); project full replace; project prepend/append wrapping default; local full replace overriding project; local prepend/append wrapping (project-wrapped-default); env full replace beating everything;resolveLayeredValue’s three-layer scalar case.
| Area | Note |
|---|---|
solutions/ontological/lib/config/resolve.ts | New: shared resolver + provenance types |
solutions/ontological/lib/config/resolve.test.ts | New: exhaustive layer/mode matrix |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
resolveLayeredStepsgiven onlydefaultStepsreturns those steps each tagged{layer: 'default', mode: 'default'}. - AC-2: A
project-layer full-replace list wins overdefaultand is tagged{layer: 'project', mode: 'replace'}on every step. - AC-3: A
project-layer{prepend, append}wrapsdefault’s list; the prepended steps are tagged{layer: 'project', mode: 'prepend'}, the original default steps keep{layer: 'default', mode: 'default'}, the appended steps are tagged{layer: 'project', mode: 'append'}. - AC-4: A
local-layer entry (replace or wrap) takes precedence over whateverproject+defaultalready resolved to, with matching per-step provenance. - AC-5: A supplied
envStepslist wins outright over every other layer, tagged{layer: 'env', mode: 'replace'}, with no wrapping supported at that layer (a caller passing bothenvStepsand a project/local wrap gets the flat env replacement, not a merge). - AC-6:
resolveLayeredValueresolveslocal > project > defaultcorrectly and reports the winninglayer. - AC-7:
bun test solutions/ontological/lib/config/resolve.test.tspasses standalone.
Out of scope
Section titled “Out of scope”- Wiring any real
sdlc.yamlsection to this resolver — that’sT-4OUZandT-4KYN.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Drawn from D-LSLH-collapse-verbs-workflows-chain-hooks’s “One resolution
algorithm” and “Provenance shape” sections.