Document internal component contracts
This commit is contained in:
64
docs/internal/app.md
Normal file
64
docs/internal/app.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Application Orchestration
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
`internal/app` owns top-level use cases for `run`, `validate`, and `inspect`. It wires configuration, storage backends, transforms, publish planning, execution, summaries, and notification handoff.
|
||||||
|
|
||||||
|
## Inputs and outputs
|
||||||
|
|
||||||
|
`Run` accepts a context, optional config path, dry-run flag, stdout writer, and optional notifier. It loads YAML config, discovers source bundles for each configured pipeline, plans each destination independently, optionally executes publish plans, writes summary output when stdout is supplied, and returns an aggregated error if any destination fails.
|
||||||
|
|
||||||
|
`Validate` and `Inspect` accept a local path. `Validate` discovers and validates bundles. `Inspect` writes bundle metadata and manifest file entries to stdout when provided.
|
||||||
|
|
||||||
|
## Run flow
|
||||||
|
|
||||||
|
The runner:
|
||||||
|
|
||||||
|
1. loads config from the supplied path or `config.DefaultConfigPath`;
|
||||||
|
2. opens the configured source backend;
|
||||||
|
3. discovers validated bundles from the source root;
|
||||||
|
4. opens each destination backend independently;
|
||||||
|
5. builds a publish plan for each bundle and destination;
|
||||||
|
6. prints plan lines and records summary counters;
|
||||||
|
7. executes publish or replacement plans unless dry-run is enabled;
|
||||||
|
8. invokes the notifier after successful publish or replacement actions.
|
||||||
|
|
||||||
|
Destination failures are collected while later destinations continue to run. Source open and source discovery failures stop the run because there are no valid bundles to fan out.
|
||||||
|
|
||||||
|
## Backend and transform wiring
|
||||||
|
|
||||||
|
The app-level backend factory registers only the local backend for execution. Config validation accepts other backend shapes, but `Run` can execute only local sources and local destinations.
|
||||||
|
|
||||||
|
The app-level transform registry registers Markdown-to-HTML using `internal/transform/markdown`. Lower-level publish code receives a resolver and does not import concrete transform implementations.
|
||||||
|
|
||||||
|
## Dry-run behavior
|
||||||
|
|
||||||
|
Dry-run still loads config, opens backends, discovers bundles, inspects destinations, resolves transforms, and builds publish plans. It does not write destination outputs, write `.distributor.json`, delete managed outputs, or notify.
|
||||||
|
|
||||||
|
## Failure behavior
|
||||||
|
|
||||||
|
`Run` returns immediately for config loading errors, context cancellation before work starts, source open errors, and source discovery errors. Per-destination backend, planning, execution, and notification errors are aggregated into one run error after remaining destinations have been attempted.
|
||||||
|
|
||||||
|
Stdout write errors are returned immediately because the caller's requested output stream can no longer be trusted.
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
`internal/app` coordinates packages but does not own manifest validation rules, destination state comparison, storage path rules, output planning, transform rendering, or backend-specific filesystem behavior.
|
||||||
|
|
||||||
|
`Validate` and `Inspect` are local path commands. Remote execution wiring is outside current behavior.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Before changing app orchestration, inspect tests under:
|
||||||
|
|
||||||
|
- `internal/app`
|
||||||
|
- `internal/cli`
|
||||||
|
- `internal/publish`
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- One source fans out to each destination independently.
|
||||||
|
- Destination failures do not prevent later destinations from being planned.
|
||||||
|
- Dry-run must not mutate destination storage or invoke notifications.
|
||||||
|
- Concrete backend and transform registration stays at the app layer.
|
||||||
|
- The default notifier is `notify.Noop`.
|
||||||
@@ -32,10 +32,22 @@ The bundle digest is SHA-256 of a deterministic JSON array of file records in ma
|
|||||||
|
|
||||||
Discovery walks a storage backend beneath a source root, finds `manifest.json` files, sorts bundle paths lexically, and rejects nested manifests.
|
Discovery walks a storage backend beneath a source root, finds `manifest.json` files, sorts bundle paths lexically, and rejects nested manifests.
|
||||||
|
|
||||||
|
## Failure behavior
|
||||||
|
|
||||||
|
Manifest parsing and validation fail before destination planning. Storage-backed validation fails when listed files are missing, are not regular files, have unexpected sizes, have unexpected SHA-256 digests, or when a source bundle includes unsafe or reserved paths.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
Bundle code uses `internal/storage` and does not import local, SSH, or S3 adapters. CLI local path support is wired in `internal/app`.
|
Bundle code uses `internal/storage` and does not import concrete adapters. CLI local path support is wired in `internal/app`.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Before changing bundle behavior, inspect tests under `internal/bundle`.
|
Before changing bundle behavior, inspect tests under `internal/bundle`.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- `manifest.json` is the only source bundle contract.
|
||||||
|
- Source file paths must stay relative to the bundle root.
|
||||||
|
- The top-level bundle digest is derived from manifest file records in order.
|
||||||
|
- Discovery order is lexical and deterministic.
|
||||||
|
- Nested manifests are rejected.
|
||||||
|
|||||||
61
docs/internal/config.md
Normal file
61
docs/internal/config.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Configuration Internals
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
`internal/config` defines YAML-backed configuration structs, defaulting, and validation for distributor pipelines.
|
||||||
|
|
||||||
|
## Inputs and outputs
|
||||||
|
|
||||||
|
Input is a YAML file containing `pipelines`. Output is a `Config` value with defaults applied and validation completed. Load failures include the config path and whether the failure occurred during file loading, YAML parsing, or validation.
|
||||||
|
|
||||||
|
## Loading flow
|
||||||
|
|
||||||
|
`LoadFile` opens the requested path, decodes YAML with known-field checking enabled, applies defaults, and validates the result. The app uses `DefaultConfigPath` when the CLI does not supply a config path.
|
||||||
|
|
||||||
|
Known-field checking rejects misspelled or unknown YAML keys before defaults and validation run.
|
||||||
|
|
||||||
|
## Defaults
|
||||||
|
|
||||||
|
Defaults are applied in `ApplyDefaults`:
|
||||||
|
|
||||||
|
- pipeline validation defaults `on_digest_mismatch` to `fail`;
|
||||||
|
- destination publish policy defaults to source output only;
|
||||||
|
- `transfer.on_destination_same` defaults to `skip`;
|
||||||
|
- `transfer.on_destination_older` defaults to `replace`;
|
||||||
|
- `transfer.on_destination_newer` defaults to `skip`;
|
||||||
|
- `transfer.on_conflict` defaults to `fail`.
|
||||||
|
|
||||||
|
## Validation responsibilities
|
||||||
|
|
||||||
|
Validation requires at least one pipeline, slug-like unique pipeline ids, one source per pipeline, at least one destination, slug-like unique destination ids within each pipeline, backend-specific required fields, valid validation policy, valid publish and transform combinations, and valid transfer actions.
|
||||||
|
|
||||||
|
`ValidatePublishTransformPolicy` is shared with publish planning so destination policy combinations are checked consistently. Publishing HTML requires an enabled Markdown-to-HTML transform in `sidecar` mode. A publish policy must select source output, HTML output, or both.
|
||||||
|
|
||||||
|
## Executable support boundary
|
||||||
|
|
||||||
|
Config validation accepts `local`, `ssh`, and `s3` backend shapes so config files can be validated as schemas. Runtime execution currently opens only local backends through `internal/app`.
|
||||||
|
|
||||||
|
The user-facing configuration reference is `docs/config.md`; this file documents package behavior for maintainers.
|
||||||
|
|
||||||
|
## Failure behavior
|
||||||
|
|
||||||
|
Load errors wrap the underlying file, YAML, or validation error with context. Validation collects all detected field errors into one error value instead of stopping at the first invalid field.
|
||||||
|
|
||||||
|
Unsupported backend names fail validation. Accepted backend names without runtime execution support fail later during app backend opening.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Before changing config behavior, inspect:
|
||||||
|
|
||||||
|
- `internal/config/load_test.go`
|
||||||
|
- `internal/config/validate_test.go`
|
||||||
|
- example-loading coverage in `internal/config`
|
||||||
|
- user-facing examples under `examples/`
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- Defaults are applied before validation.
|
||||||
|
- Unknown YAML fields are rejected.
|
||||||
|
- `docs/config.md` remains the canonical user-facing config reference.
|
||||||
|
- Runtime backend execution support is not inferred from config validation support.
|
||||||
|
- New user-visible config behavior must be covered by tests and docs in the same change.
|
||||||
@@ -4,10 +4,32 @@
|
|||||||
|
|
||||||
`internal/notify` defines the internal notification interface used by the application runner.
|
`internal/notify` defines the internal notification interface used by the application runner.
|
||||||
|
|
||||||
|
## Inputs and outputs
|
||||||
|
|
||||||
|
Input is a notification event containing pipeline id, destination id, bundle id, bundle path, action, and output metadata. The interface returns an error so app orchestration can treat notification failures as destination failures.
|
||||||
|
|
||||||
## Current behavior
|
## Current behavior
|
||||||
|
|
||||||
The implemented notifier is a no-op. It is invoked only after a successful publish or replacement. Dry-run, skipped destinations, and failed destinations do not invoke it.
|
The implemented notifier is a no-op. It is invoked only after a successful publish or replacement. Dry-run, skipped destinations, and failed destinations do not invoke it.
|
||||||
|
|
||||||
|
## Failure behavior
|
||||||
|
|
||||||
|
`notify.Noop` always succeeds unless the context is already canceled. If a configured notifier returns an error, `internal/app` records that destination as failed and continues with remaining destinations.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
No external notification adapters are implemented. Notification configuration is not part of the current user-facing config schema.
|
External notification adapters and user-facing notification configuration are outside current behavior.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Before changing notification behavior, inspect:
|
||||||
|
|
||||||
|
- `internal/notify`
|
||||||
|
- `internal/app/run_test.go`
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- Notifications are emitted only after successful publish or replacement execution.
|
||||||
|
- Dry-run never notifies.
|
||||||
|
- Skipped and failed destinations never notify.
|
||||||
|
- The default app notifier is `notify.Noop`.
|
||||||
|
|||||||
@@ -12,11 +12,17 @@ Output is a plan with an action, reason, and selected source or generated output
|
|||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
|
||||||
Supported actions are publish new, replace older destination, skip same source, skip newer destination, fail conflict, and fail unmanaged destination.
|
Supported actions are `publish_new`, `replace_older`, `skip_same`, `skip_destination_newer`, `fail_conflict`, and `fail_unmanaged`.
|
||||||
|
|
||||||
|
## Failure behavior
|
||||||
|
|
||||||
|
Planning fails when request fields are incomplete, publish and transform policies are invalid, selected outputs collide, HTML output is requested without Markdown inputs, destination state is invalid, destination content is unmanaged, or transfer policy maps the comparison outcome to failure.
|
||||||
|
|
||||||
|
Execution fails if a write, delete, state serialization, or context check fails. Outputs written during a failed publish attempt are cleaned up through managed deletion where possible.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
The current implementation publishes source files and Markdown-to-HTML sidecar outputs. Remote backend execution is not implemented.
|
The current implementation publishes source files and Markdown-to-HTML sidecar outputs. Backend behavior is supplied through `internal/storage`; app runtime currently supplies local backends.
|
||||||
|
|
||||||
The package uses `internal/state` for destination comparison, `internal/storage` for IO, and the shared `internal/config` publish/transform policy helper for request validation. It resolves transforms through a narrow resolver supplied by the caller; concrete transform registration is owned by the app layer. It does not parse CLI flags or load config files.
|
The package uses `internal/state` for destination comparison, `internal/storage` for IO, and the shared `internal/config` publish/transform policy helper for request validation. It resolves transforms through a narrow resolver supplied by the caller; concrete transform registration is owned by the app layer. It does not parse CLI flags or load config files.
|
||||||
|
|
||||||
@@ -27,3 +33,11 @@ Replacement deletes only outputs recorded in existing destination state plus `.d
|
|||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Before changing publish behavior, inspect tests under `internal/publish` and local run tests under `internal/app`.
|
Before changing publish behavior, inspect tests under `internal/publish` and local run tests under `internal/app`.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- Publish planning is deterministic for the same source, destination state, policies, and transform outputs.
|
||||||
|
- Replacement deletes only managed paths recorded in existing state plus `.distributor.json`.
|
||||||
|
- Publish execution writes destination state after selected outputs are written.
|
||||||
|
- Transform implementations are resolved through an interface supplied by the caller.
|
||||||
|
- Unmanaged destination content is never overwritten.
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ Each output records `path`, `kind`, `source_path`, `sha256`, and `size`. Support
|
|||||||
|
|
||||||
Comparison outcomes cover absent destination state, unmanaged destination content, invalid state, pipeline or destination mismatch, same source manifest, older destination source, newer destination source, same-created digest conflict, and different source id conflict.
|
Comparison outcomes cover absent destination state, unmanaged destination content, invalid state, pipeline or destination mismatch, same source manifest, older destination source, newer destination source, same-created digest conflict, and different source id conflict.
|
||||||
|
|
||||||
|
## Failure behavior
|
||||||
|
|
||||||
|
Invalid JSON, invalid state schema, invalid embedded source manifests, unsafe output paths, unsupported output kinds, missing generated-output transform names, and mismatched pipeline or destination ids produce comparison outcomes that publish planning can turn into fail actions.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
This package does not publish files, delete files, inspect storage backends, or choose transfer policy actions. Publish planning consumes these comparison outcomes later.
|
This package does not publish files, delete files, inspect storage backends, or choose transfer policy actions. Publish planning consumes these comparison outcomes later.
|
||||||
@@ -38,3 +42,11 @@ This package does not publish files, delete files, inspect storage backends, or
|
|||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Before changing destination state behavior, inspect tests under `internal/state`.
|
Before changing destination state behavior, inspect tests under `internal/state`.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- `.distributor.json` is the destination sentinel and state record.
|
||||||
|
- Embedded source manifests use the same validation rules as source bundles.
|
||||||
|
- Generated outputs always record a transform id.
|
||||||
|
- Comparison returns outcomes and reasons; it does not mutate storage.
|
||||||
|
- `distributor_version` is diagnostic metadata, not a comparison key.
|
||||||
|
|||||||
@@ -24,10 +24,18 @@ Logical file paths must be non-empty, relative, clean, slash-separated, and must
|
|||||||
|
|
||||||
Storage errors use typed categories such as not found, already exists, invalid path, conflict, permission, temporary, unsupported, and unknown. Callers should use helper predicates rather than matching error strings.
|
Storage errors use typed categories such as not found, already exists, invalid path, conflict, permission, temporary, unsupported, and unknown. Callers should use helper predicates rather than matching error strings.
|
||||||
|
|
||||||
|
Backends may wrap implementation-specific errors, but callers should receive storage errors where practical. Traversal can stop cleanly with `ErrStopWalk`.
|
||||||
|
|
||||||
## Deletion
|
## Deletion
|
||||||
|
|
||||||
Backends expose guarded managed deletion only. `DeleteManagedBundle` may delete listed managed outputs plus `.distributor.json`; it does not provide broad recursive deletion.
|
Backends expose guarded managed deletion only. `DeleteManagedBundle` may delete listed managed outputs plus `.distributor.json`; it does not provide broad recursive deletion.
|
||||||
|
|
||||||
|
## Local and fake backends
|
||||||
|
|
||||||
|
The local adapter maps logical paths to a configured filesystem root and keeps adapter-specific path handling behind the storage interface.
|
||||||
|
|
||||||
|
The fake backend is an in-memory implementation for package tests. It is not registered for runtime use.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Before changing storage behavior, inspect tests under:
|
Before changing storage behavior, inspect tests under:
|
||||||
@@ -35,3 +43,11 @@ Before changing storage behavior, inspect tests under:
|
|||||||
- `internal/storage`
|
- `internal/storage`
|
||||||
- `internal/storage/fake`
|
- `internal/storage/fake`
|
||||||
- `internal/adapters/local`
|
- `internal/adapters/local`
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- Core packages depend on `internal/storage`, not concrete adapters.
|
||||||
|
- Logical paths are slash-separated and confined to the backend root.
|
||||||
|
- `storage.List` uses backend traversal and returns deterministic entries.
|
||||||
|
- Managed deletion is limited to recorded outputs plus `.distributor.json`.
|
||||||
|
- Runtime backend registration is owned by `internal/app`.
|
||||||
|
|||||||
@@ -8,12 +8,20 @@
|
|||||||
|
|
||||||
Inputs are a validated source bundle and source backend. Outputs include destination path, source path, transform id, generated bytes, SHA-256, and size.
|
Inputs are a validated source bundle and source backend. Outputs include destination path, source path, transform id, generated bytes, SHA-256, and size.
|
||||||
|
|
||||||
|
## Registry
|
||||||
|
|
||||||
|
`internal/transform` defines the transform interface and registry. The app layer registers the Markdown implementation; publish planning receives only a resolver.
|
||||||
|
|
||||||
## Markdown behavior
|
## Markdown behavior
|
||||||
|
|
||||||
Markdown files ending in `.md` generate `.html` files in the same logical directory. Non-Markdown files do not generate outputs. Raw HTML embedded in Markdown is not passed through by the renderer.
|
Markdown files ending in `.md` generate `.html` files in the same logical directory. Non-Markdown files do not generate outputs. Raw HTML embedded in Markdown is not passed through by the renderer.
|
||||||
|
|
||||||
Generated HTML is deterministic for the same source content and transform configuration.
|
Generated HTML is deterministic for the same source content and transform configuration.
|
||||||
|
|
||||||
|
## Failure behavior
|
||||||
|
|
||||||
|
Transform resolution fails when a requested transform id is not registered. Markdown rendering fails when the source file cannot be read or rendered. Publish planning fails when HTML output is requested and the selected transform produces no outputs for a bundle.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
Transforms do not publish files, mutate source bundles, or write destination state. Publish planning selects and writes transform outputs.
|
Transforms do not publish files, mutate source bundles, or write destination state. Publish planning selects and writes transform outputs.
|
||||||
@@ -22,4 +30,15 @@ The app layer owns default transform registration. The transform package does no
|
|||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Before changing transform behavior, inspect tests under `internal/transform`.
|
Before changing transform behavior, inspect tests under:
|
||||||
|
|
||||||
|
- `internal/transform`
|
||||||
|
- `internal/transform/markdown`
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- Source bundle files are never mutated by transforms.
|
||||||
|
- Generated outputs record destination path, source path, transform id, SHA-256, and size.
|
||||||
|
- Markdown sidecar naming changes only the `.md` extension to `.html`.
|
||||||
|
- Non-Markdown source files do not generate Markdown outputs.
|
||||||
|
- Transform registration stays outside publish planning.
|
||||||
|
|||||||
Reference in New Issue
Block a user