Skip to content

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.

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).

  1. 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 type T, taking defaultSteps: T[] | (() => Promise<T[]>) (the async form is how setup’s heuristic plugs in later, in T-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-4KYN supply this per-section; this task’s own tests supply a fake in-memory version), and envSteps?: T[] | undefined (already parsed by the caller — this module does not know about SDLC_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) — no mode, no env, reusing the same readRaw-style shape narrowed to a single value.
  2. 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.
AreaNote
solutions/ontological/lib/config/resolve.tsNew: shared resolver + provenance types
solutions/ontological/lib/config/resolve.test.tsNew: exhaustive layer/mode matrix
  • AC-1: resolveLayeredSteps given only defaultSteps returns those steps each tagged {layer: 'default', mode: 'default'}.
  • AC-2: A project-layer full-replace list wins over default and is tagged {layer: 'project', mode: 'replace'} on every step.
  • AC-3: A project-layer {prepend, append} wraps default’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 whatever project+default already resolved to, with matching per-step provenance.
  • AC-5: A supplied envSteps list wins outright over every other layer, tagged {layer: 'env', mode: 'replace'}, with no wrapping supported at that layer (a caller passing both envSteps and a project/local wrap gets the flat env replacement, not a merge).
  • AC-6: resolveLayeredValue resolves local > project > default correctly and reports the winning layer.
  • AC-7: bun test solutions/ontological/lib/config/resolve.test.ts passes standalone.
  • Wiring any real sdlc.yaml section to this resolver — that’s T-4OUZ and T-4KYN.
  • none

Drawn from D-LSLH-collapse-verbs-workflows-chain-hooks’s “One resolution algorithm” and “Provenance shape” sections.


← Back to Tasks