9.7 KiB
Internal Package Audit And Cleanup Plan
This document records the internal package audit findings and the agreed corrections. It is a planning document only; implementation belongs in a later pass.
The package count is not itself a problem. Most internal packages have useful boundaries:
internal/domain: internal contracts and value types.internal/usecase: prompt preparation and execution orchestration.internal/adapter/cliandinternal/adapter/http: transport-specific mapping and IO.internal/config: app settings and CLI override precedence.internal/promptdef,internal/profile, andinternal/profile/builtin: prompt/profile repositories and built-in profile source.internal/artifact: artifact reference resolution.internal/prompt: prompt rendering.internal/llm: OpenAI-compatible model adapter.internal/validate: output validation.internal/format: prepared-run render formatting.
Do not collapse packages merely to reduce the number of directories. Prefer targeted cleanup where a boundary is unclear, duplicated, or security-sensitive.
Stage 1: Harden HTTP File Artifact Inputs
Problem:
internal/adapter/http accepts file input references and maps them directly to domain.ArtifactRef. internal/artifact then reads ref.URI with os.ReadFile. Because serve has no built-in authentication, an HTTP caller can cause the server process to read arbitrary local files accessible to it.
Decision:
Keep HTTP file inputs because they are a core use case, but require an explicit allowlisted artifact root for HTTP file reads and enforce path containment before reading.
Implementation:
- Add an HTTP/server artifact root setting at the app wiring layer.
- Prefer a config field such as
server.artifact_rootor a clearly named top-level field such asartifact_root. - Add the matching CLI
serveoverride only if it fits existing config/CLI precedence patterns. - The setting should apply to HTTP
serveonly; do not change CLIrun/renderfile input behavior.
- Prefer a config field such as
- Introduce an artifact reader mode that restricts file reads to the configured root.
- Resolve the configured root with
filepath.Absandfilepath.Clean. - Resolve each requested file path against the root.
- Reject paths that escape the root, including
..traversal and absolute paths outside the root. - Use
filepath.Relor equivalent path-containment checks; do not rely on string-prefix checks alone. - Consider rejecting symlink escapes if the threat model includes untrusted writable artifact roots. If symlinks are allowed, document that the root must not be writable by untrusted users.
- Resolve the configured root with
- Preserve inline input behavior.
- Decide startup behavior:
- Recommended: if HTTP
serveacceptsfilerefs,artifact_rootmust be configured before file refs are allowed. - If no root is configured, HTTP
filerefs should return400 artifact_read_failedor a more specific400 artifact_not_allowed; inline refs should continue to work.
- Recommended: if HTTP
- Keep public Go API and CLI behavior unchanged unless explicitly using the restricted artifact reader for HTTP wiring.
Tests:
- HTTP tests:
- inline refs still work without an artifact root;
- file refs under the artifact root work;
- relative traversal outside the root is rejected;
- absolute paths outside the root are rejected;
- unknown fields and existing HTTP error shapes remain stable.
- Artifact tests:
- restricted reader accepts contained paths;
- restricted reader rejects escaped paths;
- path cleaning and absolute/relative input behavior are covered.
- Config/CLI tests:
- configured artifact root is loaded and overridden according to normal precedence.
Documentation:
- Update
docs/config.md,docs/cli.md,docs/integrations/http-api.md,docs/operations.md, anddocs/internal/adapters.md. - Document only implemented behavior outside roadmap docs.
Stage 2: Consolidate Prompt/Profile YAML Catalog Helpers
Problem:
Prompt and profile repositories duplicate YAML discovery and fs.FS path handling. internal/filecatalog currently handles only OS directory walking, while promptdef and profile each carry similar fs.FS walking, file extension, path cleaning, display path, and stem logic.
Decision:
Expand internal/filecatalog into the shared YAML catalog/path helper for both OS directories and fs.FS roots. This is preferable to deleting it because both prompt and profile loaders need the same behavior.
Implementation:
- Extend
internal/filecatalogwith helpers for:- sorted YAML discovery under an
fs.FSroot; - clean
fs.FSroot handling; - display paths relative to an
fs.FSroot; - YAML file stem handling for both OS and
fs.FSpaths.
- sorted YAML discovery under an
- Replace duplicate helpers in
internal/promptdefandinternal/profile:- prompt
findPromptDefinitionYAMLFiles; - prompt
cleanFSRoot; - prompt
displayPath; - prompt
isPromptDefinitionYAMLFile; - profile
findProfileYAMLFiles; - profile
cleanFSRoot; - profile
displayPath; - profile
profileFileStem; - profile
isProfileYAMLFile.
- prompt
- Preserve behavior:
- recursive nested search;
.yamland.ymlsupport;- deterministic ordering;
- subdirectories remain organizational only;
- duplicate ID behavior remains unchanged.
Tests:
- Expand
internal/filecatalogtests to coverfs.FSroots and display paths. - Keep prompt/profile repository tests passing unchanged where possible.
- Add regression tests if any ordering or relative-path behavior is easy to break.
Stage 3: Deduplicate Validator Control Flow
Problem:
StandardValidator.Validate and FSValidator.Validate duplicate most validation-mode logic. The meaningful difference is how JSON Schema documents are loaded and compiled.
Decision:
Extract shared validation-mode control flow and isolate schema loading behind a small internal function/interface. Keep the public package boundary unchanged.
Implementation:
- Introduce an unexported schema validation helper, for example:
validateArtifact(ctx, artifact, contract, schemaValidatorFunc);- or a small unexported interface that compiles/validates schema-backed instances.
- Share handling for:
- context checks;
- nil artifact errors;
none;basic;json;- validation result initialization.
- Keep source-specific behavior separate:
- directory-backed schema path resolution and
jsonschema.Compile; fs.FSschema document loading andcompiler.AddResource.
- directory-backed schema path resolution and
- Preserve current validation semantics:
- content validation failures return
ValidationFailedresults; - schema path/load/compile/runtime failures return errors;
- JSON parse failures in
jsonandjson_schemamodes return failed validation results, not runtime errors.
- content validation failures return
Tests:
- Existing validator tests should remain the main regression suite.
- Add focused tests only if new helper behavior is not covered by current tests.
- Run
go test ./internal/validate ./....
Stage 4: Remove Stale Domain Surface
Problem:
domain.RunResult.Error and domain.RunMetadata appear unused. They make the central domain package broader than necessary and can confuse future contributors about intended result/error handling.
Decision:
Remove unused internal domain fields/types unless implementation-time review finds a live consumer.
Implementation:
- Remove
Error errorfromdomain.RunResult. - Remove
domain.RunMetadata. - Update tests, formatters, adapters, and public conversions only if compilation reveals hidden references.
- Do not remove fields from public root-package types in this stage.
Tests:
- Run
go test ./....
Stage 5: Reassess Thin Interface Files Without Collapsing Packages
Problem:
Several packages have small interface-only files, such as:
internal/llm/client.gointernal/prompt/renderer.gointernal/validate/validator.gointernal/profile/repository.gointernal/promptdef/repository.go
Decision:
Keep these package boundaries. The interfaces describe real use-case seams and are imported by adapters and internal/usecase. Do not merge these packages solely to remove small files.
Implementation:
- No code change is required for package consolidation.
- If a package is already being edited, it is acceptable to move an interface into a nearby implementation file only when that improves readability and does not blur ownership.
- Keep
internal/domainfree of adapter implementation details. - Keep
internal/usecasedependent on interfaces rather than concrete filesystem/model implementations.
Stage 6: Documentation And Final Verification
After implementation:
-
Update internal docs:
docs/internal/adapters.mdfor HTTP artifact-root behavior and any filecatalog/package-boundary changes;docs/internal/runner.mdonly if runner behavior or error classification changed as part of this cleanup.
-
Update user/operator docs for HTTP artifact root:
docs/config.md;docs/cli.md;docs/integrations/http-api.md;docs/operations.md;docs/troubleshooting.mdif new failure modes are introduced.
-
Run:
go test ./... go vet ./... go run ./examples/go-library/prepare go run ./cmd/scriptorium render \ --config ./examples/config.yml \ --prompt generic.markdown_summary \ --input transcript=./examples/fixtures/transcript.md \ --input glossary=./examples/fixtures/glossary.yml \ --format json
Non-Goals
- Do not collapse packages merely because they are small.
- Do not remove HTTP file inputs; restrict them with an explicit artifact root.
- Do not change public Go API behavior as part of internal package cleanup.
- Do not introduce a new auth system for HTTP
servein this cleanup pass. - Do not change prompt/profile YAML lookup semantics except by preserving them through shared helpers.