Make kdl-specs discovery independent of member filenames and directory layout #182

Closed
opened 2026-07-02 04:16:47 +00:00 by coilysiren · 2 comments
Owner

Problem

kdl-specs currently defines a generated binary as every *.guardfile.kdl file in one directory that shares the selected member's wrap binary name.

Current loadGroup behavior in http/kdlspecs/kdlspecs.go:

  • --guardfile fixes discovery to filepath.Dir(selectedFile);
  • discovery is a non-recursive filepath.Glob(dir, "*.guardfile.kdl");
  • the suffix is therefore part of the semantic contract even though the KDL content already declares its dialect and wrap <binary> identity.

That makes these logically equivalent projects behave differently:

one-file/
  api.kdl
large-project/
  forge/reads.kdl
  forge/writes.kdl
  cloud/aws.kdl
  inspect/local-tools.kdl
  ... 100 members across 13 directories

A policy member's filename and folder placement should be organization, not semantics. The KDL content and an explicit discovery root/selection should determine the generated binary.

Parent product direction: cli-guard#239.

Product contract

Implement project discovery with these properties:

  1. Content-driven membership. A KDL operation member is recognized from its parsed dialect/root and its wrap declaration, not from the literal *.guardfile.kdl suffix. Existing suffixes remain accepted with no migration requirement.
  2. Recursive project scope. An explicit project/discovery root may contain members at arbitrary depth. A one-file root and a deeply nested root use the same grouping path.
  3. Binary grouping remains explicit. Members compose only when their parsed wrap binary identity (Group[0]) matches. Selecting one member selects that binary group; multiple binary groups without a selector fail with an actionable list rather than merging accidentally.
  4. Deterministic results. Normalize members to stable root-relative paths and sort them before generating, hashing, locking, documenting, or building. Moving a whole project directory must not perturb its logical member order or cache identity merely because the absolute path changed.
  5. Fail closed on ambiguity. Duplicate logical members, conflicting output/doc/lock paths, unreadable members, malformed operation KDL, and symlink escapes outside the selected root must fail before generation. Do not silently pick the first file returned by the filesystem.
  6. Mixed KDL repositories remain usable. Define and document how discovery distinguishes kdl-specs operation members from other recognized KDL dialects such as fleet/agent configuration. Unrelated KDL must not be accidentally compiled into the binary, while a malformed file that is clearly intended as an operation member must not be silently ignored.
  7. Artifacts follow members predictably. Per-member spec locks and generated reference docs retain an unambiguous mapping to their source member even when identical basenames exist in different directories. Shared specverb.lock, generated main.go, and materialization cache remain per generated binary.
  8. Backward compatibility. Existing same-directory *.guardfile.kdl consumers work unchanged. --guardfile remains a valid selector; add a project-root flag or equivalent explicit boundary rather than inferring an unbounded filesystem walk.

The exact CLI spelling for the discovery root is implementation judgment, but it must be visible in --help and documented. Avoid introducing a required manifest whose only purpose is to restate every member path; that would merely move filename/folder coupling into another file.

Acceptance tests

  • One arbitrarily named .kdl operation member builds successfully.
  • One binary composed from members across multiple nested directories builds in stable lexical root-relative order.
  • Two members with the same basename in different directories retain distinct locks/docs/cache inputs.
  • Two binary groups under one root produce an actionable selection error; choosing one member selects only its group.
  • A mixed root containing operation KDL plus a recognized non-operation dialect handles the latter according to the documented rule.
  • A malformed intended member, duplicate/conflicting artifact mapping, and a symlink escape each fail closed.
  • Re-rooting an identical project tree does not change the normalized member identity or generated order.
  • Legacy same-directory *.guardfile.kdl discovery and all five driver verbs (gen, lock, skew, build, run) remain covered.

Update docs/kdl-specs.md, docs/kdl-specs-materialization.md, CLI help, and docs/FEATURES.md; run the repository validation gates.

## Problem `kdl-specs` currently defines a generated binary as every `*.guardfile.kdl` file in **one directory** that shares the selected member's `wrap` binary name. Current `loadGroup` behavior in `http/kdlspecs/kdlspecs.go`: - `--guardfile` fixes discovery to `filepath.Dir(selectedFile)`; - discovery is a non-recursive `filepath.Glob(dir, "*.guardfile.kdl")`; - the suffix is therefore part of the semantic contract even though the KDL content already declares its dialect and `wrap <binary>` identity. That makes these logically equivalent projects behave differently: ```text one-file/ api.kdl ``` ```text large-project/ forge/reads.kdl forge/writes.kdl cloud/aws.kdl inspect/local-tools.kdl ... 100 members across 13 directories ``` A policy member's filename and folder placement should be organization, not semantics. The KDL content and an explicit discovery root/selection should determine the generated binary. Parent product direction: cli-guard#239. ## Product contract Implement project discovery with these properties: 1. **Content-driven membership.** A KDL operation member is recognized from its parsed dialect/root and its `wrap` declaration, not from the literal `*.guardfile.kdl` suffix. Existing suffixes remain accepted with no migration requirement. 2. **Recursive project scope.** An explicit project/discovery root may contain members at arbitrary depth. A one-file root and a deeply nested root use the same grouping path. 3. **Binary grouping remains explicit.** Members compose only when their parsed `wrap` binary identity (`Group[0]`) matches. Selecting one member selects that binary group; multiple binary groups without a selector fail with an actionable list rather than merging accidentally. 4. **Deterministic results.** Normalize members to stable root-relative paths and sort them before generating, hashing, locking, documenting, or building. Moving a whole project directory must not perturb its logical member order or cache identity merely because the absolute path changed. 5. **Fail closed on ambiguity.** Duplicate logical members, conflicting output/doc/lock paths, unreadable members, malformed operation KDL, and symlink escapes outside the selected root must fail before generation. Do not silently pick the first file returned by the filesystem. 6. **Mixed KDL repositories remain usable.** Define and document how discovery distinguishes kdl-specs operation members from other recognized KDL dialects such as fleet/agent configuration. Unrelated KDL must not be accidentally compiled into the binary, while a malformed file that is clearly intended as an operation member must not be silently ignored. 7. **Artifacts follow members predictably.** Per-member spec locks and generated reference docs retain an unambiguous mapping to their source member even when identical basenames exist in different directories. Shared `specverb.lock`, generated `main.go`, and materialization cache remain per generated binary. 8. **Backward compatibility.** Existing same-directory `*.guardfile.kdl` consumers work unchanged. `--guardfile` remains a valid selector; add a project-root flag or equivalent explicit boundary rather than inferring an unbounded filesystem walk. The exact CLI spelling for the discovery root is implementation judgment, but it must be visible in `--help` and documented. Avoid introducing a required manifest whose only purpose is to restate every member path; that would merely move filename/folder coupling into another file. ## Acceptance tests - One arbitrarily named `.kdl` operation member builds successfully. - One binary composed from members across multiple nested directories builds in stable lexical root-relative order. - Two members with the same basename in different directories retain distinct locks/docs/cache inputs. - Two binary groups under one root produce an actionable selection error; choosing one member selects only its group. - A mixed root containing operation KDL plus a recognized non-operation dialect handles the latter according to the documented rule. - A malformed intended member, duplicate/conflicting artifact mapping, and a symlink escape each fail closed. - Re-rooting an identical project tree does not change the normalized member identity or generated order. - Legacy same-directory `*.guardfile.kdl` discovery and all five driver verbs (`gen`, `lock`, `skew`, `build`, `run`) remain covered. Update `docs/kdl-specs.md`, `docs/kdl-specs-materialization.md`, CLI help, and `docs/FEATURES.md`; run the repository validation gates.
coilyco-ops changed title from Make KDL files 100% agnostic to file names and folder structures to Make kdl-specs discovery independent of member filenames and directory layout 2026-07-23 05:46:18 +00:00
Member

WARDED_WORKFLOW: coilyco-flight-deck/cli-guard#240

details

Review gate: intentionally skipped (temporary ward default pending brokered QA).

workflow: pull-request-and-merge; review summary: intentionally skipped (temporary ward default pending brokered QA)

Retrospective: the change was cohesive once discovery identity became root-relative; repository documentation caps required splitting the discovery contract.

Confidence: high — local gates and all PR checks passed.

Surprises: pre-commit enforces tight documentation and comment-size limits.

Follow-ups: none.

WARDED_WORKFLOW: https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/pulls/240 <details><summary>details</summary> Review gate: intentionally skipped (temporary ward default pending brokered QA). workflow: pull-request-and-merge; review summary: intentionally skipped (temporary ward default pending brokered QA) Retrospective: the change was cohesive once discovery identity became root-relative; repository documentation caps required splitting the discovery contract. Confidence: high — local gates and all PR checks passed. Surprises: pre-commit enforces tight documentation and comment-size limits. Follow-ups: none. </details>
Member

WARDED_WORKFLOW: reservation-released

release details

Run finished with WARDED_WORKFLOW: https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/pulls/240.

ward container reap released container engineer-codex-cli-guard-182 (--harness codex): the terminal outcome supersedes the reservation, so a later redispatch no longer needs --override-reservation.

Outcome summary: coilyco-flight-deck/cli-guard#240

— Codex, via ward agent

<!-- ward-agent-reservation-released --> WARDED_WORKFLOW: reservation-released <details><summary>release details</summary> Run finished with `WARDED_WORKFLOW: https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/pulls/240`. `ward container reap` released container `engineer-codex-cli-guard-182` (`--harness codex`): the terminal outcome supersedes the reservation, so a later redispatch no longer needs `--override-reservation`. **Outcome summary:** https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/pulls/240 </details> <!-- ward-agent-signature --> — Codex, via `ward agent`
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/umbra#182
No description provided.