@ybotok/loam 0.2.0-alpha.7 → 0.2.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1315 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +83 -7
- package/SCHEMA.md +569 -98
- package/WORKFLOW.md +272 -62
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +33 -11
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/explore.js +4 -8
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +28 -33
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +21 -25
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +3 -5
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +5 -8
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +14 -41
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +17 -38
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +32 -5
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +23 -23
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +6 -10
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +1 -3
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +8 -28
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/report.js +5 -4
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +16 -104
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +1 -1
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +54 -51
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +56 -101
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +4 -3
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +32 -18
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +9 -3
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +20 -16
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +50 -18
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +6 -18
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +15 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/documents.js +31 -6
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +6 -17
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/findings.js +15 -20
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/coherence.js +30 -16
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +119 -8
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +3 -6
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +28 -16
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +5 -2
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +4 -2
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +5 -36
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +22 -8
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +56 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +2 -16
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/repo.js +24 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/walk.js +8 -30
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/staging/commit.js +10 -12
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +174 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +3 -0
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +12 -2
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +21 -6
- package/examples/docs/architecture/landscape.likec4 +8 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* leaves the living docs exactly as they were.
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
|
-
import { repoPath } from "../../../../core/envelope/json.js";
|
|
11
|
+
import { CodedFailure, repoPath } from "../../../../core/envelope/json.js";
|
|
12
12
|
import { planWrite, readUtf8 } from "../../../../core/staging/writes.js";
|
|
13
13
|
import { featureSpecPaths } from "../../../../core/repo/paths.js";
|
|
14
14
|
import { locateServicePaths } from "../../../../core/repo/service-target.js";
|
|
@@ -16,11 +16,12 @@ import { readOpenapi } from "../../../../core/openapi/doc.js";
|
|
|
16
16
|
import { OpenapiMergeError } from "../../../../core/openapi/merge/error.js";
|
|
17
17
|
import { stripOpenapiRemovalMarkers } from "../../../../core/openapi/merge/markers.js";
|
|
18
18
|
import { mergeOpenapiPaths } from "../../../../core/openapi/merge/merge.js";
|
|
19
|
-
import { ArchiveFailure } from "../refusal.js";
|
|
20
19
|
import {} from "../state.js";
|
|
20
|
+
import { consumerClause } from "./consumers.js";
|
|
21
|
+
import { sayQuoted, sayStale, warnOverwrite } from "./verdict-lines.js";
|
|
21
22
|
export async function planOpenapiContracts(config, gated, plan, say) {
|
|
22
23
|
const { featureDir, deltaServices } = gated;
|
|
23
|
-
const { writes,
|
|
24
|
+
const { writes, planGates, openapiRemovals } = plan;
|
|
24
25
|
// OpenAPI merge — fold the feature's openapi deltas into the living service APIs.
|
|
25
26
|
for (const svc of deltaServices) {
|
|
26
27
|
const featOpenapi = featureSpecPaths(featureDir, svc).openapi;
|
|
@@ -40,7 +41,7 @@ export async function planOpenapiContracts(config, gated, plan, say) {
|
|
|
40
41
|
// A document loam cannot read is one it cannot merge, and that is the same
|
|
41
42
|
// answer as any other merge it could not compute: nothing is written.
|
|
42
43
|
if (featDoc.unreadable) {
|
|
43
|
-
throw new
|
|
44
|
+
throw new CodedFailure("merge-failed", `feature openapi ${repoPath(config.docsDir, featOpenapi)} for ${svc} does not read as an OpenAPI document (${featDoc.error ?? "not a YAML mapping"}) — the API axis cannot be merged; fix the file, or delete it if this feature has no contract delta`);
|
|
44
45
|
}
|
|
45
46
|
const ops = featDoc.ops.filter((op) => !op.remove).map((op) => op.id);
|
|
46
47
|
// An `x-loam-remove: true` written at PATH level retires nothing: the marker
|
|
@@ -75,10 +76,10 @@ export async function planOpenapiContracts(config, gated, plan, say) {
|
|
|
75
76
|
}
|
|
76
77
|
catch (err) {
|
|
77
78
|
if (err instanceof OpenapiMergeError)
|
|
78
|
-
throw new
|
|
79
|
+
throw new CodedFailure("merge-failed", err.message);
|
|
79
80
|
throw err;
|
|
80
81
|
}
|
|
81
|
-
const { text, modified, pathItemModified, removed, quoted, pathItemQuoted, pathItemStale, componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved, } = merge;
|
|
82
|
+
const { text, modified, modifiedOperationIds, pathItemModified, removed, quoted, pathItemQuoted, pathItemStale, componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved, } = merge;
|
|
82
83
|
if (text !== null) {
|
|
83
84
|
writes.push(planWrite(livingOpenapi, text));
|
|
84
85
|
// The parenthesised list names the OPERATIONS the delta merged, and a
|
|
@@ -97,53 +98,35 @@ export async function planOpenapiContracts(config, gated, plan, say) {
|
|
|
97
98
|
for (const label of removed)
|
|
98
99
|
say(` - removes ${label}`);
|
|
99
100
|
}
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
say(` · quotes ${label} — unchanged since it was pinned, left as living has it`);
|
|
107
|
-
for (const label of pathItemQuoted) {
|
|
108
|
-
say(` · quotes path-level ${label} — unchanged since it was pinned, left as living has it`);
|
|
109
|
-
}
|
|
110
|
-
for (const comp of componentsQuoted) {
|
|
111
|
-
say(` · quotes component ${comp} — unchanged since it was pinned, left as living has it`);
|
|
112
|
-
}
|
|
113
|
-
// Stale surfaces reaching the merge at all means --approve pushed past
|
|
114
|
-
// the gate's openapi.baseline-stale — the plan still names what it cost.
|
|
115
|
-
for (const label of pathItemStale) {
|
|
116
|
-
say(` ⚠ stale baseline on path-level ${label} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);
|
|
117
|
-
}
|
|
118
|
-
for (const comp of componentsStale) {
|
|
119
|
-
say(` ⚠ stale baseline on component ${comp} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);
|
|
120
|
-
}
|
|
101
|
+
// The quote and stale lines (./verdict-lines.ts says why each is said).
|
|
102
|
+
sayQuoted(say, quoted);
|
|
103
|
+
sayQuoted(say, pathItemQuoted.map((label) => `path-level ${label}`));
|
|
104
|
+
sayQuoted(say, componentsQuoted.map((comp) => `component ${comp}`));
|
|
105
|
+
sayStale(say, pathItemStale.map((label) => `path-level ${label}`));
|
|
106
|
+
sayStale(say, componentsStale.map((comp) => `component ${comp}`));
|
|
121
107
|
for (const label of modified) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
108
|
+
// Who the overwrite lands on: the living fleet's consumers of the
|
|
109
|
+
// operation, through `loam diff`'s own scan (./consumers.ts). An
|
|
110
|
+
// operation with no operationId on either side has nothing anyone can
|
|
111
|
+
// name it by, so the warning stays the sentence it always was.
|
|
112
|
+
const id = modifiedOperationIds.get(label);
|
|
113
|
+
const named = id === undefined ? null : consumerClause(await config.consumers.op(svc, id));
|
|
114
|
+
warnOverwrite(plan, say, {
|
|
115
|
+
svc, code: "openapi.op-modified", label, named,
|
|
126
116
|
message: `${svc}: the delta redefines ${label}, which the living OpenAPI already has — the merge overwrites the living operation wholesale`,
|
|
127
117
|
});
|
|
128
|
-
say(` ⚠ overwrites ${label} — the living definition differs`);
|
|
129
118
|
}
|
|
130
119
|
for (const label of pathItemModified) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
code: "openapi.path-item-modified",
|
|
134
|
-
subject: svc,
|
|
120
|
+
warnOverwrite(plan, say, {
|
|
121
|
+
svc, code: "openapi.path-item-modified", label: `path-level ${label}`,
|
|
135
122
|
message: `${svc}: the delta redefines the path-level key ${label}, which the living OpenAPI already has — the merge overwrites it wholesale, and it applies to EVERY operation on that path, including ones this feature never mentions`,
|
|
136
123
|
});
|
|
137
|
-
say(` ⚠ overwrites path-level ${label} — the living definition differs`);
|
|
138
124
|
}
|
|
139
125
|
for (const comp of componentsModified) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
code: "openapi.component-modified",
|
|
143
|
-
subject: svc,
|
|
126
|
+
warnOverwrite(plan, say, {
|
|
127
|
+
svc, code: "openapi.component-modified", label: `component ${comp}`,
|
|
144
128
|
message: `${svc}: the delta declares component '${comp}', which the living OpenAPI already defines differently — the merge overwrites the living component wholesale`,
|
|
145
129
|
});
|
|
146
|
-
say(` ⚠ overwrites component ${comp} — the living definition differs`);
|
|
147
130
|
}
|
|
148
131
|
for (const u of unresolved) {
|
|
149
132
|
planGates.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../../../../src/commands/archive/plan/contracts/openapi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The OpenAPI contract merge: the feature's openapi deltas folded into each\n * living service API — the half of the archive plan that answers to the\n * contract axis, split out of ../specs.ts so the requirements merge and the\n * contract merges stop sharing one file.\n *\n * Nothing is written. Everything lands in the `Plan`, so a failure on any axis\n * leaves the living docs exactly as they were.\n */\nimport { existsSync } from \"node:fs\";\nimport { repoPath } from \"../../../../core/envelope/json.js\";\nimport { planWrite, readUtf8 } from \"../../../../core/staging/writes.js\";\nimport { featureSpecPaths } from \"../../../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../../../core/repo/service-target.js\";\nimport { readOpenapi } from \"../../../../core/openapi/doc.js\";\nimport { OpenapiMergeError } from \"../../../../core/openapi/merge/error.js\";\nimport { stripOpenapiRemovalMarkers } from \"../../../../core/openapi/merge/markers.js\";\nimport { mergeOpenapiPaths, type OpenapiMergeResult } from \"../../../../core/openapi/merge/merge.js\";\nimport { ArchiveFailure } from \"../refusal.js\";\nimport { type Gated, type Plan } from \"../state.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../../core/fleet-context.js\";\n\nexport async function planOpenapiContracts(\n config: { docsDir: DocsDir; fleet?: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { featureDir, deltaServices } = gated;\n const { writes, planWarns, planGates, openapiRemovals } = plan;\n // OpenAPI merge — fold the feature's openapi deltas into the living service APIs.\n for (const svc of deltaServices) {\n const featOpenapi = featureSpecPaths(featureDir, svc).openapi;\n if (!existsSync(featOpenapi)) continue;\n const featText = await readUtf8(featOpenapi);\n // The shared context: this planner runs once per delta service, and a\n // context-less locate is a full fleet walk per service.\n const livingOpenapi = (await locateServicePaths(config.docsDir, svc, config.fleet)).openapi;\n const featDoc = await readOpenapi(featOpenapi);\n // Every other reader of this flag suspends its own judgement when it is set\n // — validate grades `openapi.invalid`, show and status print that the file\n // does not parse. Archive was the only one that never looked, and it is the\n // command that WRITES: a feature openapi.yaml holding a sequence instead of\n // a mapping was planned verbatim into services/<svc>/openapi.yaml, `ok:\n // true`, printing `created ()` because the reader saw no operations in it.\n // A document loam cannot read is one it cannot merge, and that is the same\n // answer as any other merge it could not compute: nothing is written.\n if (featDoc.unreadable) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `feature openapi ${repoPath(config.docsDir, featOpenapi)} for ${svc} does not read as an OpenAPI document (${featDoc.error ?? \"not a YAML mapping\"}) — the API axis cannot be merged; fix the file, or delete it if this feature has no contract delta`,\n );\n }\n const ops = featDoc.ops.filter((op) => !op.remove).map((op) => op.id);\n // An `x-loam-remove: true` written at PATH level retires nothing: the marker\n // addresses one operation, and beside the methods there is no operation for\n // it to address. The merge now strips it on every branch, so the living\n // contract is safe either way — but the author asked for a removal that will\n // not happen, and silence there is how a retired endpoint stayed live.\n // Gated like the other plan-visible breaches, --approve and all.\n for (const path of featDoc.pathLevelRemovals) {\n planGates.push({\n severity: \"error\",\n code: \"openapi.remove-marker-path-level\",\n subject: svc,\n message: `${svc}: '${path}' carries x-loam-remove at PATH level, beside the methods — a removal marker names ONE operation, so this retires nothing and is not a contract key either. Move it inside the operation you are retiring (with its operationId), or delete it.`,\n });\n }\n if (!existsSync(livingOpenapi)) {\n // A removal against a non-existent contract is gated by coherence. Keep\n // the feature-only marker out of living docs even under --approve — and\n // ask the DOCUMENT, not the operation reader: a marker with no\n // operationId is invisible to `operations()`, so gating the strip on\n // \"does the reader see a removal\" let exactly that marker through into a\n // living contract, published to every consumer of the fleet.\n const content = stripOpenapiRemovalMarkers(featText, svc);\n writes.push(planWrite(livingOpenapi, content));\n say(` openapi: ${svc} — created (${ops.join(\", \")})`);\n } else {\n let merge: OpenapiMergeResult;\n try {\n merge = mergeOpenapiPaths(await readUtf8(livingOpenapi), featText, svc);\n } catch (err) {\n if (err instanceof OpenapiMergeError) throw new ArchiveFailure(\"merge-failed\", err.message);\n throw err;\n }\n const {\n text, modified, pathItemModified, removed, quoted, pathItemQuoted, pathItemStale,\n componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved,\n } = merge;\n if (text !== null) {\n writes.push(planWrite(livingOpenapi, text));\n // The parenthesised list names the OPERATIONS the delta merged, and a\n // delta whose whole change is a component has none — so this printed\n // `merged ()`, the same empty-parenthesis lie the create branch above\n // is commented for: it reads as \"this merge wrote nothing\" about a\n // merge that just published a shared schema to every consumer of the\n // fleet. Name the components it added instead, and drop the\n // parentheses entirely when there is nothing to put in them — a delta\n // that only EDITED existing surfaces is named by the lines below.\n const mergedNames = ops.length > 0 ? ops : componentsAdded.map((comp) => `component ${comp}`);\n say(` openapi: ${svc} — merged${mergedNames.length > 0 ? ` (${mergedNames.join(\", \")})` : \"\"}`);\n }\n if (removed.length > 0) {\n openapiRemovals.push({ service: svc, operations: removed });\n for (const label of removed) say(` - removes ${label}`);\n }\n // Said out loud, because \"the plan wrote less than my delta spells\" is\n // the one thing a reader cannot infer from a merged file. Not a warning:\n // quoting the contract around your change is correct authoring, and\n // leaving the quote alone is the correct merge. The path-level and\n // component quotes get the same sentence for the same reason.\n for (const label of quoted) say(` · quotes ${label} — unchanged since it was pinned, left as living has it`);\n for (const label of pathItemQuoted) {\n say(` · quotes path-level ${label} — unchanged since it was pinned, left as living has it`);\n }\n for (const comp of componentsQuoted) {\n say(` · quotes component ${comp} — unchanged since it was pinned, left as living has it`);\n }\n // Stale surfaces reaching the merge at all means --approve pushed past\n // the gate's openapi.baseline-stale — the plan still names what it cost.\n for (const label of pathItemStale) {\n say(` ⚠ stale baseline on path-level ${label} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);\n }\n for (const comp of componentsStale) {\n say(` ⚠ stale baseline on component ${comp} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);\n }\n for (const label of modified) {\n planWarns.push({\n severity: \"warn\",\n code: \"openapi.op-modified\",\n subject: svc,\n message: `${svc}: the delta redefines ${label}, which the living OpenAPI already has — the merge overwrites the living operation wholesale`,\n });\n say(` ⚠ overwrites ${label} — the living definition differs`);\n }\n for (const label of pathItemModified) {\n planWarns.push({\n severity: \"warn\",\n code: \"openapi.path-item-modified\",\n subject: svc,\n message: `${svc}: the delta redefines the path-level key ${label}, which the living OpenAPI already has — the merge overwrites it wholesale, and it applies to EVERY operation on that path, including ones this feature never mentions`,\n });\n say(` ⚠ overwrites path-level ${label} — the living definition differs`);\n }\n for (const comp of componentsModified) {\n planWarns.push({\n severity: \"warn\",\n code: \"openapi.component-modified\",\n subject: svc,\n message: `${svc}: the delta declares component '${comp}', which the living OpenAPI already defines differently — the merge overwrites the living component wholesale`,\n });\n say(` ⚠ overwrites component ${comp} — the living definition differs`);\n }\n for (const u of unresolved) {\n planGates.push({\n severity: \"error\",\n code: \"openapi.ref-unresolved\",\n subject: svc,\n message: `${svc}: $ref '${u.ref}' (referenced from ${u.from}) resolves in neither the feature's openapi.yaml nor the living one — the merged document would carry a dangling reference`,\n });\n }\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../../../../src/commands/archive/plan/contracts/openapi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The OpenAPI contract merge: the feature's openapi deltas folded into each\n * living service API — the half of the archive plan that answers to the\n * contract axis, split out of ../specs.ts so the requirements merge and the\n * contract merges stop sharing one file.\n *\n * Nothing is written. Everything lands in the `Plan`, so a failure on any axis\n * leaves the living docs exactly as they were.\n */\nimport { existsSync } from \"node:fs\";\nimport { CodedFailure, repoPath } from \"../../../../core/envelope/json.js\";\nimport { planWrite, readUtf8 } from \"../../../../core/staging/writes.js\";\nimport { featureSpecPaths } from \"../../../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../../../core/repo/service-target.js\";\nimport { readOpenapi } from \"../../../../core/openapi/doc.js\";\nimport { OpenapiMergeError } from \"../../../../core/openapi/merge/error.js\";\nimport { stripOpenapiRemovalMarkers } from \"../../../../core/openapi/merge/markers.js\";\nimport { mergeOpenapiPaths, type OpenapiMergeResult } from \"../../../../core/openapi/merge/merge.js\";\nimport { type Gated, type Plan } from \"../state.js\";\nimport { consumerClause, type ContractConsumers } from \"./consumers.js\";\nimport { sayQuoted, sayStale, warnOverwrite } from \"./verdict-lines.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../../core/fleet-context.js\";\n\nexport async function planOpenapiContracts(\n config: { docsDir: DocsDir; fleet: FleetContext; consumers: ContractConsumers },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { featureDir, deltaServices } = gated;\n const { writes, planGates, openapiRemovals } = plan;\n // OpenAPI merge — fold the feature's openapi deltas into the living service APIs.\n for (const svc of deltaServices) {\n const featOpenapi = featureSpecPaths(featureDir, svc).openapi;\n if (!existsSync(featOpenapi)) continue;\n const featText = await readUtf8(featOpenapi);\n // The shared context: this planner runs once per delta service, and a\n // context-less locate is a full fleet walk per service.\n const livingOpenapi = (await locateServicePaths(config.docsDir, svc, config.fleet)).openapi;\n const featDoc = await readOpenapi(featOpenapi);\n // Every other reader of this flag suspends its own judgement when it is set\n // — validate grades `openapi.invalid`, show and status print that the file\n // does not parse. Archive was the only one that never looked, and it is the\n // command that WRITES: a feature openapi.yaml holding a sequence instead of\n // a mapping was planned verbatim into services/<svc>/openapi.yaml, `ok:\n // true`, printing `created ()` because the reader saw no operations in it.\n // A document loam cannot read is one it cannot merge, and that is the same\n // answer as any other merge it could not compute: nothing is written.\n if (featDoc.unreadable) {\n throw new CodedFailure(\n \"merge-failed\",\n `feature openapi ${repoPath(config.docsDir, featOpenapi)} for ${svc} does not read as an OpenAPI document (${featDoc.error ?? \"not a YAML mapping\"}) — the API axis cannot be merged; fix the file, or delete it if this feature has no contract delta`,\n );\n }\n const ops = featDoc.ops.filter((op) => !op.remove).map((op) => op.id);\n // An `x-loam-remove: true` written at PATH level retires nothing: the marker\n // addresses one operation, and beside the methods there is no operation for\n // it to address. The merge now strips it on every branch, so the living\n // contract is safe either way — but the author asked for a removal that will\n // not happen, and silence there is how a retired endpoint stayed live.\n // Gated like the other plan-visible breaches, --approve and all.\n for (const path of featDoc.pathLevelRemovals) {\n planGates.push({\n severity: \"error\",\n code: \"openapi.remove-marker-path-level\",\n subject: svc,\n message: `${svc}: '${path}' carries x-loam-remove at PATH level, beside the methods — a removal marker names ONE operation, so this retires nothing and is not a contract key either. Move it inside the operation you are retiring (with its operationId), or delete it.`,\n });\n }\n if (!existsSync(livingOpenapi)) {\n // A removal against a non-existent contract is gated by coherence. Keep\n // the feature-only marker out of living docs even under --approve — and\n // ask the DOCUMENT, not the operation reader: a marker with no\n // operationId is invisible to `operations()`, so gating the strip on\n // \"does the reader see a removal\" let exactly that marker through into a\n // living contract, published to every consumer of the fleet.\n const content = stripOpenapiRemovalMarkers(featText, svc);\n writes.push(planWrite(livingOpenapi, content));\n say(` openapi: ${svc} — created (${ops.join(\", \")})`);\n } else {\n let merge: OpenapiMergeResult;\n try {\n merge = mergeOpenapiPaths(await readUtf8(livingOpenapi), featText, svc);\n } catch (err) {\n if (err instanceof OpenapiMergeError) throw new CodedFailure(\"merge-failed\", err.message);\n throw err;\n }\n const {\n text, modified, modifiedOperationIds, pathItemModified, removed, quoted, pathItemQuoted, pathItemStale,\n componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved,\n } = merge;\n if (text !== null) {\n writes.push(planWrite(livingOpenapi, text));\n // The parenthesised list names the OPERATIONS the delta merged, and a\n // delta whose whole change is a component has none — so this printed\n // `merged ()`, the same empty-parenthesis lie the create branch above\n // is commented for: it reads as \"this merge wrote nothing\" about a\n // merge that just published a shared schema to every consumer of the\n // fleet. Name the components it added instead, and drop the\n // parentheses entirely when there is nothing to put in them — a delta\n // that only EDITED existing surfaces is named by the lines below.\n const mergedNames = ops.length > 0 ? ops : componentsAdded.map((comp) => `component ${comp}`);\n say(` openapi: ${svc} — merged${mergedNames.length > 0 ? ` (${mergedNames.join(\", \")})` : \"\"}`);\n }\n if (removed.length > 0) {\n openapiRemovals.push({ service: svc, operations: removed });\n for (const label of removed) say(` - removes ${label}`);\n }\n // The quote and stale lines (./verdict-lines.ts says why each is said).\n sayQuoted(say, quoted);\n sayQuoted(say, pathItemQuoted.map((label) => `path-level ${label}`));\n sayQuoted(say, componentsQuoted.map((comp) => `component ${comp}`));\n sayStale(say, pathItemStale.map((label) => `path-level ${label}`));\n sayStale(say, componentsStale.map((comp) => `component ${comp}`));\n for (const label of modified) {\n // Who the overwrite lands on: the living fleet's consumers of the\n // operation, through `loam diff`'s own scan (./consumers.ts). An\n // operation with no operationId on either side has nothing anyone can\n // name it by, so the warning stays the sentence it always was.\n const id = modifiedOperationIds.get(label);\n const named = id === undefined ? null : consumerClause(await config.consumers.op(svc, id));\n warnOverwrite(plan, say, {\n svc, code: \"openapi.op-modified\", label, named,\n message: `${svc}: the delta redefines ${label}, which the living OpenAPI already has — the merge overwrites the living operation wholesale`,\n });\n }\n for (const label of pathItemModified) {\n warnOverwrite(plan, say, {\n svc, code: \"openapi.path-item-modified\", label: `path-level ${label}`,\n message: `${svc}: the delta redefines the path-level key ${label}, which the living OpenAPI already has — the merge overwrites it wholesale, and it applies to EVERY operation on that path, including ones this feature never mentions`,\n });\n }\n for (const comp of componentsModified) {\n warnOverwrite(plan, say, {\n svc, code: \"openapi.component-modified\", label: `component ${comp}`,\n message: `${svc}: the delta declares component '${comp}', which the living OpenAPI already defines differently — the merge overwrites the living component wholesale`,\n });\n }\n for (const u of unresolved) {\n planGates.push({\n severity: \"error\",\n code: \"openapi.ref-unresolved\",\n subject: svc,\n message: `${svc}: $ref '${u.ref}' (referenced from ${u.from}) resolves in neither the feature's openapi.yaml nor the living one — the merged document would carry a dangling reference`,\n });\n }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Said out loud, because "the plan wrote less than my delta spells" is the one
|
|
3
|
+
* thing a reader cannot infer from a merged file. Not a warning: quoting the
|
|
4
|
+
* contract around your change is correct authoring, and leaving the quote
|
|
5
|
+
* alone is the correct merge. The path-level and component quotes get the
|
|
6
|
+
* same sentence for the same reason.
|
|
7
|
+
*/
|
|
8
|
+
export function sayQuoted(say, labels) {
|
|
9
|
+
for (const label of labels)
|
|
10
|
+
say(` · quotes ${label} — unchanged since it was pinned, left as living has it`);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Stale surfaces reaching the merge at all means --approve pushed past the
|
|
14
|
+
* gate's `openapi.baseline-stale` / `asyncapi.baseline-stale` — the plan still
|
|
15
|
+
* names what it cost.
|
|
16
|
+
*/
|
|
17
|
+
export function sayStale(say, labels) {
|
|
18
|
+
for (const label of labels) {
|
|
19
|
+
say(` ⚠ stale baseline on ${label} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Warn about one overwrite and print it. The consumer clause, when there is
|
|
24
|
+
* one, ends both the warning's message and the printed line, and its evidence
|
|
25
|
+
* rides as the warning's `details` and as indented lines under it.
|
|
26
|
+
*/
|
|
27
|
+
export function warnOverwrite(plan, say, overwrite) {
|
|
28
|
+
const named = overwrite.named ?? null;
|
|
29
|
+
const clause = named === null ? "" : `; ${named.clause}`;
|
|
30
|
+
plan.planWarns.push({
|
|
31
|
+
severity: "warn",
|
|
32
|
+
code: overwrite.code,
|
|
33
|
+
subject: overwrite.svc,
|
|
34
|
+
message: overwrite.message + clause,
|
|
35
|
+
...(named === null ? {} : { details: named.details }),
|
|
36
|
+
});
|
|
37
|
+
say(` ⚠ overwrites ${overwrite.label} — the living definition differs${clause}`);
|
|
38
|
+
for (const line of named?.details ?? [])
|
|
39
|
+
say(` ${line}`);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=verdict-lines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verdict-lines.js","sourceRoot":"","sources":["../../../../../src/commands/archive/plan/contracts/verdict-lines.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAQ,EAAE,MAAyB;IAC3D,KAAK,MAAM,KAAK,IAAI,MAAM;QAAE,GAAG,CAAC,kBAAkB,KAAK,yDAAyD,CAAC,CAAC;AACpH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAQ,EAAE,MAAyB;IAC1D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,GAAG,CAAC,6BAA6B,KAAK,8FAA8F,CAAC,CAAC;IACxI,CAAC;AACH,CAAC;AAcD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAA6B,EAAE,GAAQ,EAAE,SAAoB;IACzF,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC;IACtC,MAAM,MAAM,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;IACzD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAClB,QAAQ,EAAE,MAAM;QAChB,IAAI,EAAE,SAAS,CAAC,IAAI;QACpB,OAAO,EAAE,SAAS,CAAC,GAAG;QACtB,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,MAAM;QACnC,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;KACtD,CAAC,CAAC;IACH,GAAG,CAAC,sBAAsB,SAAS,CAAC,KAAK,mCAAmC,MAAM,EAAE,CAAC,CAAC;IACtF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,OAAO,IAAI,EAAE;QAAE,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;AACpE,CAAC","sourcesContent":["/**\n * The three plan lines a contract merge's verdicts print — the quote, the\n * stale baseline and the overwrite — spelled once for both planners\n * (./openapi.ts, ./asyncapi.ts), which used to print them in fourteen parallel\n * loops. Every surface reaches the reader by exactly the same sentence an\n * operation or a slot does, because it is graded by exactly the same verdict;\n * one spelling is what keeps the two axes from describing one verdict in two\n * sentences. The label prefix (`path-level …`, `component …`, `components.…`)\n * stays at the call site: it is the one part that differs.\n */\nimport type { IssueCode } from \"../../../../core/vocabulary/issue.js\";\nimport type { Plan } from \"../state.js\";\n\ntype Say = (line?: string) => void;\n\n/**\n * Said out loud, because \"the plan wrote less than my delta spells\" is the one\n * thing a reader cannot infer from a merged file. Not a warning: quoting the\n * contract around your change is correct authoring, and leaving the quote\n * alone is the correct merge. The path-level and component quotes get the\n * same sentence for the same reason.\n */\nexport function sayQuoted(say: Say, labels: readonly string[]): void {\n for (const label of labels) say(` · quotes ${label} — unchanged since it was pinned, left as living has it`);\n}\n\n/**\n * Stale surfaces reaching the merge at all means --approve pushed past the\n * gate's `openapi.baseline-stale` / `asyncapi.baseline-stale` — the plan still\n * names what it cost.\n */\nexport function sayStale(say: Say, labels: readonly string[]): void {\n for (const label of labels) {\n say(` ⚠ stale baseline on ${label} — the living value moved since this delta was pinned; the overwrite is what --approve chose`);\n }\n}\n\n/** One overwrite the plan warns about. The code is spelled literally at each call site. */\nexport interface Overwrite {\n svc: string;\n code: IssueCode;\n /** What the printed line names: the label with its prefix. */\n label: string;\n /** The warning's sentence, before any consumer clause. */\n message: string;\n /** Who the overwrite lands on (./consumers.ts), or absent where nothing can name it. */\n named?: { clause: string; details: string[] } | null;\n}\n\n/**\n * Warn about one overwrite and print it. The consumer clause, when there is\n * one, ends both the warning's message and the printed line, and its evidence\n * rides as the warning's `details` and as indented lines under it.\n */\nexport function warnOverwrite(plan: Pick<Plan, \"planWarns\">, say: Say, overwrite: Overwrite): void {\n const named = overwrite.named ?? null;\n const clause = named === null ? \"\" : `; ${named.clause}`;\n plan.planWarns.push({\n severity: \"warn\",\n code: overwrite.code,\n subject: overwrite.svc,\n message: overwrite.message + clause,\n ...(named === null ? {} : { details: named.details }),\n });\n say(` ⚠ overwrites ${overwrite.label} — the living definition differs${clause}`);\n for (const line of named?.details ?? []) say(` ${line}`);\n}\n"]}
|
|
@@ -20,7 +20,6 @@ import { readUtf8 } from "../../../core/staging/writes.js";
|
|
|
20
20
|
import { loadFile } from "../../../core/c4/likec4.js";
|
|
21
21
|
import { archiveDir as archiveRoot, featurePaths, featureSpecPaths, SPEC_AXES } from "../../../core/repo/paths.js";
|
|
22
22
|
import { livingCapabilityPaths } from "../../../core/repo/authored/paths.js";
|
|
23
|
-
import { featureCapabilityDeltas } from "../../../core/capabilities/delta/tree.js";
|
|
24
23
|
import { locateServicePaths } from "../../../core/repo/service-target.js";
|
|
25
24
|
import { featureSpecServices, missingFeatureMessage, resolveFeature } from "../../../core/repo/repo.js";
|
|
26
25
|
import { featureCoherence } from "../../../core/coherence/coherence.js";
|
|
@@ -31,7 +30,7 @@ import { refuseFindings, refuseJson, sayRecovery } from "./refusal.js";
|
|
|
31
30
|
import {} from "./state.js";
|
|
32
31
|
export async function gate(config, featureId, opts, say) {
|
|
33
32
|
const json = opts.json === true;
|
|
34
|
-
|
|
33
|
+
// Before anything is read: a commit that was killed between two renames left
|
|
35
34
|
// the living docs half-written, and every other command in loam would have
|
|
36
35
|
// planned against them as if they were the truth. Under the same lock, so the
|
|
37
36
|
// repair cannot race a writer. It refuses when the half-merged files have been
|
|
@@ -62,9 +61,7 @@ export async function gate(config, featureId, opts, say) {
|
|
|
62
61
|
// no-context path `core/coherence/declared.ts` already documents paying for
|
|
63
62
|
// the arch delta — so this is not the run's only walk, only the one every
|
|
64
63
|
// phase downstream of the gate shares.
|
|
65
|
-
const capabilityDeltas = (config.fleet
|
|
66
|
-
? await featureCapabilityDeltas(featureDir)
|
|
67
|
-
: await config.fleet.featureCapabilityDeltas(featureDir)).docs;
|
|
64
|
+
const capabilityDeltas = (await config.fleet.featureCapabilityDeltas(featureDir)).docs;
|
|
68
65
|
// A `specs/<svc>/` whose NAME is not a legal service id, refused before
|
|
69
66
|
// anything joins that name into a path: the conflict scan below probes
|
|
70
67
|
// `services/<svc>/<axis>` with it, and the requirements merge would
|
|
@@ -104,7 +101,7 @@ export async function gate(config, featureId, opts, say) {
|
|
|
104
101
|
// Git conflict markers in a LIVING document this merge would rewrite, before
|
|
105
102
|
// anything else is asked of it. Both sides of somebody's merge are in the
|
|
106
103
|
// file, so nothing it says is anyone's text — and the requirements rewrite
|
|
107
|
-
// deletes whichever marker lines fall inside
|
|
104
|
+
// deletes whichever marker lines fall inside a requirement it replaces, turning a
|
|
108
105
|
// conflict anyone can see into a file nobody can tell is wrong. Checked ahead
|
|
109
106
|
// of the coherence gate for `inspectLandscape`'s reason: a conflicted
|
|
110
107
|
// document parses, so every verdict downstream of it is an opinion about half
|
|
@@ -304,7 +301,6 @@ export async function gate(config, featureId, opts, say) {
|
|
|
304
301
|
fail(json, "archive-exists", `archive ${id} — BLOCKED: features/archive/${dirName} already exists. Remove or rename it, then re-run.`);
|
|
305
302
|
return null;
|
|
306
303
|
}
|
|
307
|
-
say(`archive ${id}${dryRun ? " (dry run)" : ""}\n`);
|
|
308
304
|
return {
|
|
309
305
|
id,
|
|
310
306
|
dirName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/gate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Every refusal that happens before a byte is planned.\n *\n * They are gathered here because they share one doctrine that is easy to lose:\n * `--approve` overrides judgments about the FEATURE, and nothing else. A service\n * directory whose name the id grammar refuses, a living document holding git\n * conflict markers, a requirement outside `## Requirements` — those are\n * mechanical facts about paths and files, so `--approve` does not reach them,\n * and the dry run is gated too, since a plan built on one describes a merge loam\n * must never perform.\n *\n * `null` means the refusal has already been printed and the exit code set.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fail, repoPath, sayExplain } from \"../../../core/envelope/json.js\";\nimport { approveOverrides, gatesArchive, type Issue } from \"../../../core/vocabulary/issue.js\";\nimport { recoverInterruptedCommit } from \"../../../core/staging/recovery/recover.js\";\nimport { readUtf8 } from \"../../../core/staging/writes.js\";\nimport { loadFile } from \"../../../core/c4/likec4.js\";\nimport { archiveDir as archiveRoot, featurePaths, featureSpecPaths, SPEC_AXES } from \"../../../core/repo/paths.js\";\nimport { livingCapabilityPaths } from \"../../../core/repo/authored/paths.js\";\nimport { featureCapabilityDeltas } from \"../../../core/capabilities/delta/tree.js\";\nimport { locateServicePaths } from \"../../../core/repo/service-target.js\";\nimport { featureSpecServices, missingFeatureMessage, resolveFeature } from \"../../../core/repo/repo.js\";\nimport { featureCoherence } from \"../../../core/coherence/coherence.js\";\nimport {\n invalidSpecServiceFindings,\n livingMergeConflicts,\n unknownDeltaServices,\n} from \"../../../core/coherence/living.js\";\nimport { SEVERITY_MARK } from \"../../../core/vocabulary/report.js\";\nimport { isRequirementsHeading, parseRequirements } from \"../../../core/document/parse.js\";\nimport { refuseFindings, refuseJson, sayRecovery, type ArchiveOptions } from \"./refusal.js\";\nimport { type Gated } from \"./state.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\n\nexport async function gate(\n config: { docsDir: DocsDir; fleet?: FleetContext },\n featureId: string,\n opts: ArchiveOptions,\n say: (line?: string) => void,\n): Promise<Gated | null> {\n const json = opts.json === true;\n const dryRun = opts.dryRun === true;\n // the living docs half-written, and every other command in loam would have\n // planned against them as if they were the truth. Under the same lock, so the\n // repair cannot race a writer. It refuses when the half-merged files have been\n // edited since — that is a human's call, not a merge's.\n const recovered = await recoverInterruptedCommit(config.docsDir);\n if (recovered !== null && !json) sayRecovery(recovered);\n\n const feature = await resolveFeature(config.docsDir, featureId, \"exclude\");\n if (!feature) {\n fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureId));\n return null;\n }\n // The raw argument's last appearance: from here on the feature answers only to\n // its canonical id — tags, coherence, and the self-exclusion scans all match\n // on `id`, and `archive FEAT-5-slug` must plan exactly like `archive FEAT-5`.\n const { id, dirName, dir: featureDir } = feature;\n\n // Parse delta.likec4 ONCE for the whole run: the coherence gate and the\n // architecture merge below read the same file, and loading it spins up a\n // fresh Langium workspace each time. Nothing writes it in between.\n const deltaLikec4 = featurePaths(featureDir).delta;\n const deltaDoc = existsSync(deltaLikec4) ? await loadFile(deltaLikec4) : undefined;\n\n const deltaServices = await featureSpecServices(featureDir);\n // The business corpus's delta tree, walked once HERE and carried on `Gated`:\n // the conflict-marker scan, the strayed-requirement scan and the merge itself\n // all ask which capability documents this feature rewrites, and three walks\n // are three chances to answer differently. `featureCoherence` below walks it\n // again on its own — it is called without `config.fleet`, the same\n // no-context path `core/coherence/declared.ts` already documents paying for\n // the arch delta — so this is not the run's only walk, only the one every\n // phase downstream of the gate shares.\n const capabilityDeltas = (\n config.fleet === undefined\n ? await featureCapabilityDeltas(featureDir)\n : await config.fleet.featureCapabilityDeltas(featureDir)\n ).docs;\n\n // A `specs/<svc>/` whose NAME is not a legal service id, refused before\n // anything joins that name into a path: the conflict scan below probes\n // `services/<svc>/<axis>` with it, and the requirements merge would\n // materialise `services/<svc>/` from it — a directory `service.id-invalid`\n // then calls an error on the next `validate --all`, and one no loam command\n // can address or re-create. The set comes from coherence/living.ts's\n // `invalidSpecServiceFindings` — the same function validate reads — so the\n // two gates cannot drift.\n //\n // --approve does not apply, exactly as with the conflict-marker refusal\n // below: --approve overrides judgments about the FEATURE, and a name the id\n // grammar refuses is a mechanical fact about the path it would become. The\n // dry run is gated too: a plan built on that name describes a merge loam\n // must never perform.\n const illegalServices = await invalidSpecServiceFindings(featureDir);\n if (illegalServices.length > 0) {\n const msg = `archive ${id} — BLOCKED: ${illegalServices.length} per-service delta(s) named by an illegal service id; --approve does not override this`;\n if (json) {\n refuseFindings(\"not-coherent\", msg, illegalServices, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const f of illegalServices) console.error(` ✗ ${f.message}`);\n console.error(`\\n--approve does not override this — the directory the merge would create is one loam can never address, mechanically.`);\n // Every arm in this file refuses by hand — it prints a headline, a list and\n // a closing sentence rather than the one message `fail()` takes — so the\n // code and its lookup have to be asked for explicitly. `sayExplain` is\n // called rather than the format being copied six times, and the code passed\n // is the SAME one the `--json` branch a few lines up emits: text and\n // envelope must name one refusal, or a reader who switches modes to find\n // the code is handed a different answer than the one they saw.\n sayExplain(\"not-coherent\");\n process.exitCode = 1;\n return null;\n }\n\n // Git conflict markers in a LIVING document this merge would rewrite, before\n // anything else is asked of it. Both sides of somebody's merge are in the\n // file, so nothing it says is anyone's text — and the requirements rewrite\n // deletes whichever marker lines fall inside the section it owns, turning a\n // conflict anyone can see into a file nobody can tell is wrong. Checked ahead\n // of the coherence gate for `inspectLandscape`'s reason: a conflicted\n // document parses, so every verdict downstream of it is an opinion about half\n // of two people's work.\n //\n // --approve does not apply, exactly as with the strayed-requirement refusal\n // below: --approve overrides judgments about the FEATURE, and this is a fact\n // about the living document.\n const conflicted = await livingMergeConflicts(config.docsDir, deltaServices, capabilityDeltas.map((d) => d.id));\n if (conflicted.length > 0) {\n const msg = `archive ${id} — BLOCKED: ${conflicted.length} living document(s) still hold git conflict markers`;\n if (json) {\n refuseFindings(\"merge-failed\", msg, conflicted, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const f of conflicted) console.error(` ✗ ${f.message}`);\n console.error(`\\n--approve does not override this — the loss is mechanical, not a judgment call.`);\n sayExplain(\"merge-failed\");\n process.exitCode = 1;\n return null;\n }\n\n // Gate: GATING issues block the archive. Severity and gating are two\n // different questions (issue.ts): errors gate because the merge would write\n // something wrong, and the rare warning marked `gates` blocks too — the\n // document is legal but the merge would drop authored content. Advisory\n // warnings are printed (and carried into --json) but never block, matching\n // validate's own doctrine (report.ts: \"warnings never gate\"). --approve\n // overrides the gating issues ONLY, and must say exactly which ones it is\n // walking past. A dry run is gated too: a plan for a merge that would be\n // refused describes nothing that will happen.\n const issues = await featureCoherence({ docsDir: config.docsDir, featureDir, featureId: id, preloadedDelta: deltaDoc });\n // The issues --approve cannot move, carved out before the gate below reads\n // the rest. The register is issue.ts's `approveOverrides` — the same data\n // the `overridable` key in the envelope is resolved from — never a code\n // string spelled here, because two spellings of \"what the flag moves\" would\n // drift. What the register HOLDS has grown: it began as one code (a\n // `metadata { service }` binding the id grammar refuses, where the merge\n // would splice the name into the living map verbatim and probe\n // `services/<binding>/` with it) and now holds the create-only collisions of\n // every whole-file axis besides. They share the doctrine and nothing else —\n // each is a mechanical fact about what the merge would write, never a\n // judgment about the feature — which is why the message below counts them\n // and lets each issue say its own sentence.\n //\n // IT USED TO NAME BINDINGS, and had been wrong for every other code in the\n // register since the use-case slot landed: a feature whose flow collided\n // with a living one was refused with \"1 element binding(s) name an illegal\n // service id\", about a feature that binds nothing. A refusal headline that\n // describes the wrong breach sends its reader to the wrong file first.\n const nonOverridable = issues.filter((i) => !approveOverrides(i));\n if (nonOverridable.length > 0) {\n const msg = `archive ${id} — BLOCKED: ${nonOverridable.length} issue(s) --approve does not override; each names what the merge would write`;\n if (json) {\n refuseJson(\"not-coherent\", msg, issues, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n // The whole list, not just the carve-out: the reader is about to fix the\n // feature, and the issues --approve COULD have moved are work they still\n // owe — hiding them here made the second refusal a surprise. The closing\n // line says which of the marks the flag cannot move.\n for (const i of issues) console.error(` ${SEVERITY_MARK[i.severity]} ${i.message}`);\n console.error(\n `\\nOf these, --approve does not override ${nonOverridable.length}: ${[...new Set(nonOverridable.map((i) => i.code))].sort().join(\", \")}. ` +\n \"Each is a mechanical fact about what the merge would write — a name loam cannot resolve, or a file it would replace wholesale — not a judgement the flag can move. \" +\n \"Fix those; the other issues gate as usual.\",\n );\n sayExplain(\"not-coherent\");\n process.exitCode = 1;\n return null;\n }\n const gating = issues.filter(gatesArchive);\n const advisory = issues.filter((i) => !gatesArchive(i));\n if (gating.length > 0 && !opts.approve) {\n const msg = `archive ${id} — BLOCKED: not coherent (${gating.length} gating issue(s), ${advisory.length} advisory warning(s))`;\n if (json) {\n refuseJson(\"not-coherent\", msg, issues, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const i of issues) console.error(` ${SEVERITY_MARK[i.severity]} ${i.message}`);\n console.error(`\\nFix the gating issues (advisory warnings never block), or re-run with --approve to override them (may corrupt the living docs).`);\n sayExplain(\"not-coherent\");\n process.exitCode = 1;\n return null;\n }\n if (gating.length > 0) {\n say(`⚠ archiving despite ${gating.length} gating issue(s) (--approve):`);\n for (const i of gating) say(` ${SEVERITY_MARK[i.severity]} ${i.message}`);\n say();\n }\n if (advisory.length > 0) {\n say(`⚠ ${advisory.length} warning(s) (non-blocking):`);\n for (const i of advisory) say(` ⚠ ${i.message}`);\n say();\n }\n\n // A `specs/<svc>/` addressed to a service that exists nowhere. `validate`\n // graded this and archive did not, which is the half that costs something:\n // `specs/<svc>/` is what the merge MATERIALISES `services/<svc>/` from, so a\n // one-character slip in `--touches` archived at exit 0 and left the fleet a\n // living service nobody meant to adopt. The set comes from coherence/living.ts's\n // `unknownDeltaServices` — the same function validate reads — so the two\n // gates cannot drift into disagreeing about which ids are real.\n //\n // Gated exactly like a coherence error, --approve included: which service you\n // meant is a judgment about the FEATURE. A feature that genuinely introduces\n // one has a better escape hatch than --approve anyway — tag its element in\n // delta.likec4, which is also what makes the fleet map true afterwards.\n const unknownServices = await unknownDeltaServices(config.docsDir, featureDir, id, { preloadedDelta: deltaDoc });\n if (unknownServices.length > 0 && !opts.approve) {\n const msg = `archive ${id} — BLOCKED: ${unknownServices.length} per-service delta(s) address a service that does not exist`;\n if (json) {\n refuseFindings(\"not-coherent\", msg, unknownServices, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const f of unknownServices) console.error(` ✗ ${f.message}`);\n console.error(`\\nFix the id, or introduce the service in this feature's delta.likec4 — or re-run with --approve to create it anyway.`);\n sayExplain(\"not-coherent\");\n process.exitCode = 1;\n return null;\n }\n if (unknownServices.length > 0) {\n say(`⚠ archiving despite ${unknownServices.length} unknown service(s) (--approve) — this merge creates them:`);\n for (const f of unknownServices) say(` ✗ ${f.message}`);\n say();\n }\n\n // A LIVING requirement outside `## Requirements` is a merge loam cannot do\n // correctly: the rewrite replaces only the requirements RUN inside that\n // section (rewriteRequirementsRun) while parseRequirements collects from\n // every section — so the strayed requirement keeps its authored copy in the\n // preserved prose AND lands again in the rewritten run, and the next\n // archive's MODIFIED replaces only one of them. Refused rather than\n // repaired: excising blocks from prose programmatically would be archive\n // editing text it does not understand. --approve does not apply — it\n // overrides judgments about the FEATURE, and this is neither.\n const strayed: Issue[] = [];\n const scanStrayed = async (livingPath: string, subject: string): Promise<void> => {\n if (!existsSync(livingPath)) return;\n for (const r of parseRequirements(await readUtf8(livingPath))) {\n // The ONE definition of the heading (spec.ts): the guard and the rewrite\n // boundary match the same way, so they cannot disagree about \"outside\".\n if (r.section !== undefined && isRequirementsHeading(r.section)) continue;\n const where = r.section === undefined ? \"above every heading\" : `under '${r.section}'`;\n strayed.push({\n severity: \"error\",\n code: \"living.requirement-outside-requirements\",\n subject,\n message: `${subject}: living requirement '${r.name}' sits ${where} in ${repoPath(config.docsDir, livingPath)} — the merge rewrites only '## Requirements', so it would land in the file twice. Re-home it under '## Requirements' first, then re-run.`,\n });\n }\n };\n for (const svc of deltaServices) {\n for (const axis of SPEC_AXES) {\n if (!existsSync(featureSpecPaths(featureDir, svc)[axis.key])) continue;\n // The shared context, because this sits in a deltaServices × SPEC_AXES\n // loop: without it every iteration re-walks and re-reads the fleet.\n await scanStrayed((await locateServicePaths(config.docsDir, svc, config.fleet))[axis.key], svc);\n }\n }\n // The business corpus, for the identical mechanical reason: a capability\n // document is narrative ABOVE `## Requirements`, `rewriteRequirementsRun`\n // rewrites only the run inside that heading, and a requirement filed under\n // `## Notes` would keep its authored copy in the preserved prose AND land\n // again in the rewritten run.\n for (const doc of capabilityDeltas) {\n await scanStrayed(livingCapabilityPaths(config.docsDir, doc.id).spec, doc.id);\n }\n if (strayed.length > 0) {\n const msg = `archive ${id} — BLOCKED: ${strayed.length} living requirement(s) outside '## Requirements'`;\n if (json) {\n refuseJson(\"living-outside-requirements\", msg, strayed, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const i of strayed) console.error(` ✗ ${i.message}`);\n console.error(`\\n--approve does not override this — the duplication is mechanical, not a judgment call.`);\n sayExplain(\"living-outside-requirements\");\n process.exitCode = 1;\n return null;\n }\n\n // Pre-flight: the archive destination must be free, or the final move would fail\n // after the living docs were already rewritten.\n const archiveDir = archiveRoot(config.docsDir);\n const archiveDest = join(archiveDir, dirName);\n if (existsSync(archiveDest)) {\n fail(json, \"archive-exists\", `archive ${id} — BLOCKED: features/archive/${dirName} already exists. Remove or rename it, then re-run.`);\n return null;\n }\n\n say(`archive ${id}${dryRun ? \" (dry run)\" : \"\"}\\n`);\n\n return {\n id,\n dirName,\n featureDir,\n deltaDoc,\n deltaServices,\n capabilityDeltas,\n gating,\n advisory,\n archiveDir,\n archiveDest,\n recovered,\n issues,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAc,MAAM,mCAAmC,CAAC;AAC/F,OAAO,EAAE,wBAAwB,EAAE,MAAM,2CAA2C,CAAC;AACrF,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACnH,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACxG,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACxE,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAuB,MAAM,cAAc,CAAC;AAC5F,OAAO,EAAc,MAAM,YAAY,CAAC;AAIxC,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,MAAiD,EACjD,SAAiB,EACjB,IAAoB,EACpB,GAA4B;IAE5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;IAChC,6EAA6E;IAC7E,2EAA2E;IAC3E,8EAA8E;IAC9E,+EAA+E;IAC/E,wDAAwD;IACxD,MAAM,SAAS,GAAG,MAAM,wBAAwB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjE,IAAI,SAAS,KAAK,IAAI,IAAI,CAAC,IAAI;QAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IAExD,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IAC3E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QACrF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,+EAA+E;IAC/E,6EAA6E;IAC7E,8EAA8E;IAC9E,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAEjD,wEAAwE;IACxE,yEAAyE;IACzE,mEAAmE;IACnE,MAAM,WAAW,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC;IACnD,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEnF,MAAM,aAAa,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC5D,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,mEAAmE;IACnE,4EAA4E;IAC5E,0EAA0E;IAC1E,uCAAuC;IACvC,MAAM,gBAAgB,GAAG,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IAEvF,wEAAwE;IACxE,uEAAuE;IACvE,oEAAoE;IACpE,2EAA2E;IAC3E,4EAA4E;IAC5E,qEAAqE;IACrE,2EAA2E;IAC3E,0BAA0B;IAC1B,EAAE;IACF,wEAAwE;IACxE,4EAA4E;IAC5E,2EAA2E;IAC3E,yEAAyE;IACzE,sBAAsB;IACtB,MAAM,eAAe,GAAG,MAAM,0BAA0B,CAAC,UAAU,CAAC,CAAC;IACrE,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,WAAW,EAAE,eAAe,eAAe,CAAC,MAAM,wFAAwF,CAAC;QACvJ,IAAI,IAAI,EAAE,CAAC;YACT,cAAc,CAAC,cAAc,EAAE,GAAG,EAAE,eAAe,EAAE,YAAY,OAAO,EAAE,CAAC,CAAC;YAC5E,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,eAAe;YAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACnE,OAAO,CAAC,KAAK,CAAC,wHAAwH,CAAC,CAAC;QACxI,4EAA4E;QAC5E,yEAAyE;QACzE,uEAAuE;QACvE,4EAA4E;QAC5E,qEAAqE;QACrE,yEAAyE;QACzE,+DAA+D;QAC/D,UAAU,CAAC,cAAc,CAAC,CAAC;QAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6EAA6E;IAC7E,0EAA0E;IAC1E,2EAA2E;IAC3E,kFAAkF;IAClF,8EAA8E;IAC9E,sEAAsE;IACtE,8EAA8E;IAC9E,wBAAwB;IACxB,EAAE;IACF,4EAA4E;IAC5E,6EAA6E;IAC7E,6BAA6B;IAC7B,MAAM,UAAU,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChH,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,WAAW,EAAE,eAAe,UAAU,CAAC,MAAM,qDAAqD,CAAC;QAC/G,IAAI,IAAI,EAAE,CAAC;YACT,cAAc,CAAC,cAAc,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,OAAO,EAAE,CAAC,CAAC;YACvE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,UAAU;YAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,KAAK,CAAC,mFAAmF,CAAC,CAAC;QACnG,UAAU,CAAC,cAAc,CAAC,CAAC;QAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qEAAqE;IACrE,4EAA4E;IAC5E,wEAAwE;IACxE,wEAAwE;IACxE,2EAA2E;IAC3E,wEAAwE;IACxE,0EAA0E;IAC1E,yEAAyE;IACzE,8CAA8C;IAC9C,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,CAAC;IACxH,2EAA2E;IAC3E,0EAA0E;IAC1E,wEAAwE;IACxE,4EAA4E;IAC5E,oEAAoE;IACpE,yEAAyE;IACzE,+DAA+D;IAC/D,6EAA6E;IAC7E,4EAA4E;IAC5E,sEAAsE;IACtE,0EAA0E;IAC1E,4CAA4C;IAC5C,EAAE;IACF,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,WAAW,EAAE,eAAe,cAAc,CAAC,MAAM,8EAA8E,CAAC;QAC5I,IAAI,IAAI,EAAE,CAAC;YACT,UAAU,CAAC,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,OAAO,EAAE,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACzB,yEAAyE;QACzE,yEAAyE;QACzE,yEAAyE;QACzE,qDAAqD;QACrD,KAAK,MAAM,CAAC,IAAI,MAAM;YAAE,OAAO,CAAC,KAAK,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACrF,OAAO,CAAC,KAAK,CACX,2CAA2C,cAAc,CAAC,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACxI,qKAAqK;YACrK,4CAA4C,CAC/C,CAAC;QACF,UAAU,CAAC,cAAc,CAAC,CAAC;QAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,WAAW,EAAE,6BAA6B,MAAM,CAAC,MAAM,qBAAqB,QAAQ,CAAC,MAAM,uBAAuB,CAAC;QAC/H,IAAI,IAAI,EAAE,CAAC;YACT,UAAU,CAAC,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,OAAO,EAAE,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,MAAM;YAAE,OAAO,CAAC,KAAK,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACrF,OAAO,CAAC,KAAK,CAAC,mIAAmI,CAAC,CAAC;QACnJ,UAAU,CAAC,cAAc,CAAC,CAAC;QAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,GAAG,CAAC,uBAAuB,MAAM,CAAC,MAAM,+BAA+B,CAAC,CAAC;QACzE,KAAK,MAAM,CAAC,IAAI,MAAM;YAAE,GAAG,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3E,GAAG,EAAE,CAAC;IACR,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,GAAG,CAAC,KAAK,QAAQ,CAAC,MAAM,6BAA6B,CAAC,CAAC;QACvD,KAAK,MAAM,CAAC,IAAI,QAAQ;YAAE,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAClD,GAAG,EAAE,CAAC;IACR,CAAC;IAED,0EAA0E;IAC1E,2EAA2E;IAC3E,6EAA6E;IAC7E,4EAA4E;IAC5E,iFAAiF;IACjF,yEAAyE;IACzE,gEAAgE;IAChE,EAAE;IACF,8EAA8E;IAC9E,6EAA6E;IAC7E,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,eAAe,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,CAAC;IACjH,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,WAAW,EAAE,eAAe,eAAe,CAAC,MAAM,6DAA6D,CAAC;QAC5H,IAAI,IAAI,EAAE,CAAC;YACT,cAAc,CAAC,cAAc,EAAE,GAAG,EAAE,eAAe,EAAE,YAAY,OAAO,EAAE,CAAC,CAAC;YAC5E,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,eAAe;YAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACnE,OAAO,CAAC,KAAK,CAAC,uHAAuH,CAAC,CAAC;QACvI,UAAU,CAAC,cAAc,CAAC,CAAC;QAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,GAAG,CAAC,uBAAuB,eAAe,CAAC,MAAM,4DAA4D,CAAC,CAAC;QAC/G,KAAK,MAAM,CAAC,IAAI,eAAe;YAAE,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACzD,GAAG,EAAE,CAAC;IACR,CAAC;IAED,2EAA2E;IAC3E,wEAAwE;IACxE,yEAAyE;IACzE,4EAA4E;IAC5E,qEAAqE;IACrE,oEAAoE;IACpE,yEAAyE;IACzE,qEAAqE;IACrE,8DAA8D;IAC9D,MAAM,OAAO,GAAY,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,KAAK,EAAE,UAAkB,EAAE,OAAe,EAAiB,EAAE;QAC/E,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO;QACpC,KAAK,MAAM,CAAC,IAAI,iBAAiB,CAAC,MAAM,QAAQ,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;YAC9D,yEAAyE;YACzE,wEAAwE;YACxE,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,qBAAqB,CAAC,CAAC,CAAC,OAAO,CAAC;gBAAE,SAAS;YAC1E,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,OAAO,GAAG,CAAC;YACvF,OAAO,CAAC,IAAI,CAAC;gBACX,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,yCAAyC;gBAC/C,OAAO;gBACP,OAAO,EAAE,GAAG,OAAO,yBAAyB,CAAC,CAAC,IAAI,UAAU,KAAK,OAAO,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,0IAA0I;aACvP,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAAE,SAAS;YACvE,uEAAuE;YACvE,oEAAoE;YACpE,MAAM,WAAW,CAAC,CAAC,MAAM,kBAAkB,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;QAClG,CAAC;IACH,CAAC;IACD,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,8BAA8B;IAC9B,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACnC,MAAM,WAAW,CAAC,qBAAqB,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,WAAW,EAAE,eAAe,OAAO,CAAC,MAAM,kDAAkD,CAAC;QACzG,IAAI,IAAI,EAAE,CAAC;YACT,UAAU,CAAC,6BAA6B,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,OAAO,EAAE,CAAC,CAAC;YAC/E,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3D,OAAO,CAAC,KAAK,CAAC,0FAA0F,CAAC,CAAC;QAC1G,UAAU,CAAC,6BAA6B,CAAC,CAAC;QAC1C,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iFAAiF;IACjF,gDAAgD;IAChD,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,gCAAgC,OAAO,oDAAoD,CAAC,CAAC;QACvI,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,EAAE;QACF,OAAO;QACP,UAAU;QACV,QAAQ;QACR,aAAa;QACb,gBAAgB;QAChB,MAAM;QACN,QAAQ;QACR,UAAU;QACV,WAAW;QACX,SAAS;QACT,MAAM;KACP,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Every refusal that happens before a byte is planned.\n *\n * They are gathered here because they share one doctrine that is easy to lose:\n * `--approve` overrides judgments about the FEATURE, and nothing else. A service\n * directory whose name the id grammar refuses, a living document holding git\n * conflict markers, a requirement outside `## Requirements` — those are\n * mechanical facts about paths and files, so `--approve` does not reach them,\n * and the dry run is gated too, since a plan built on one describes a merge loam\n * must never perform.\n *\n * `null` means the refusal has already been printed and the exit code set.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fail, repoPath, sayExplain } from \"../../../core/envelope/json.js\";\nimport { approveOverrides, gatesArchive, type Issue } from \"../../../core/vocabulary/issue.js\";\nimport { recoverInterruptedCommit } from \"../../../core/staging/recovery/recover.js\";\nimport { readUtf8 } from \"../../../core/staging/writes.js\";\nimport { loadFile } from \"../../../core/c4/likec4.js\";\nimport { archiveDir as archiveRoot, featurePaths, featureSpecPaths, SPEC_AXES } from \"../../../core/repo/paths.js\";\nimport { livingCapabilityPaths } from \"../../../core/repo/authored/paths.js\";\nimport { locateServicePaths } from \"../../../core/repo/service-target.js\";\nimport { featureSpecServices, missingFeatureMessage, resolveFeature } from \"../../../core/repo/repo.js\";\nimport { featureCoherence } from \"../../../core/coherence/coherence.js\";\nimport {\n invalidSpecServiceFindings,\n livingMergeConflicts,\n unknownDeltaServices,\n} from \"../../../core/coherence/living.js\";\nimport { SEVERITY_MARK } from \"../../../core/vocabulary/report.js\";\nimport { isRequirementsHeading, parseRequirements } from \"../../../core/document/parse.js\";\nimport { refuseFindings, refuseJson, sayRecovery, type ArchiveOptions } from \"./refusal.js\";\nimport { type Gated } from \"./state.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\n\nexport async function gate(\n config: { docsDir: DocsDir; fleet: FleetContext },\n featureId: string,\n opts: ArchiveOptions,\n say: (line?: string) => void,\n): Promise<Gated | null> {\n const json = opts.json === true;\n // Before anything is read: a commit that was killed between two renames left\n // the living docs half-written, and every other command in loam would have\n // planned against them as if they were the truth. Under the same lock, so the\n // repair cannot race a writer. It refuses when the half-merged files have been\n // edited since — that is a human's call, not a merge's.\n const recovered = await recoverInterruptedCommit(config.docsDir);\n if (recovered !== null && !json) sayRecovery(recovered);\n\n const feature = await resolveFeature(config.docsDir, featureId, \"exclude\");\n if (!feature) {\n fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureId));\n return null;\n }\n // The raw argument's last appearance: from here on the feature answers only to\n // its canonical id — tags, coherence, and the self-exclusion scans all match\n // on `id`, and `archive FEAT-5-slug` must plan exactly like `archive FEAT-5`.\n const { id, dirName, dir: featureDir } = feature;\n\n // Parse delta.likec4 ONCE for the whole run: the coherence gate and the\n // architecture merge below read the same file, and loading it spins up a\n // fresh Langium workspace each time. Nothing writes it in between.\n const deltaLikec4 = featurePaths(featureDir).delta;\n const deltaDoc = existsSync(deltaLikec4) ? await loadFile(deltaLikec4) : undefined;\n\n const deltaServices = await featureSpecServices(featureDir);\n // The business corpus's delta tree, walked once HERE and carried on `Gated`:\n // the conflict-marker scan, the strayed-requirement scan and the merge itself\n // all ask which capability documents this feature rewrites, and three walks\n // are three chances to answer differently. `featureCoherence` below walks it\n // again on its own — it is called without `config.fleet`, the same\n // no-context path `core/coherence/declared.ts` already documents paying for\n // the arch delta — so this is not the run's only walk, only the one every\n // phase downstream of the gate shares.\n const capabilityDeltas = (await config.fleet.featureCapabilityDeltas(featureDir)).docs;\n\n // A `specs/<svc>/` whose NAME is not a legal service id, refused before\n // anything joins that name into a path: the conflict scan below probes\n // `services/<svc>/<axis>` with it, and the requirements merge would\n // materialise `services/<svc>/` from it — a directory `service.id-invalid`\n // then calls an error on the next `validate --all`, and one no loam command\n // can address or re-create. The set comes from coherence/living.ts's\n // `invalidSpecServiceFindings` — the same function validate reads — so the\n // two gates cannot drift.\n //\n // --approve does not apply, exactly as with the conflict-marker refusal\n // below: --approve overrides judgments about the FEATURE, and a name the id\n // grammar refuses is a mechanical fact about the path it would become. The\n // dry run is gated too: a plan built on that name describes a merge loam\n // must never perform.\n const illegalServices = await invalidSpecServiceFindings(featureDir);\n if (illegalServices.length > 0) {\n const msg = `archive ${id} — BLOCKED: ${illegalServices.length} per-service delta(s) named by an illegal service id; --approve does not override this`;\n if (json) {\n refuseFindings(\"not-coherent\", msg, illegalServices, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const f of illegalServices) console.error(` ✗ ${f.message}`);\n console.error(`\\n--approve does not override this — the directory the merge would create is one loam can never address, mechanically.`);\n // Every arm in this file refuses by hand — it prints a headline, a list and\n // a closing sentence rather than the one message `fail()` takes — so the\n // code and its lookup have to be asked for explicitly. `sayExplain` is\n // called rather than the format being copied six times, and the code passed\n // is the SAME one the `--json` branch a few lines up emits: text and\n // envelope must name one refusal, or a reader who switches modes to find\n // the code is handed a different answer than the one they saw.\n sayExplain(\"not-coherent\");\n process.exitCode = 1;\n return null;\n }\n\n // Git conflict markers in a LIVING document this merge would rewrite, before\n // anything else is asked of it. Both sides of somebody's merge are in the\n // file, so nothing it says is anyone's text — and the requirements rewrite\n // deletes whichever marker lines fall inside a requirement it replaces, turning a\n // conflict anyone can see into a file nobody can tell is wrong. Checked ahead\n // of the coherence gate for `inspectLandscape`'s reason: a conflicted\n // document parses, so every verdict downstream of it is an opinion about half\n // of two people's work.\n //\n // --approve does not apply, exactly as with the strayed-requirement refusal\n // below: --approve overrides judgments about the FEATURE, and this is a fact\n // about the living document.\n const conflicted = await livingMergeConflicts(config.docsDir, deltaServices, capabilityDeltas.map((d) => d.id));\n if (conflicted.length > 0) {\n const msg = `archive ${id} — BLOCKED: ${conflicted.length} living document(s) still hold git conflict markers`;\n if (json) {\n refuseFindings(\"merge-failed\", msg, conflicted, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const f of conflicted) console.error(` ✗ ${f.message}`);\n console.error(`\\n--approve does not override this — the loss is mechanical, not a judgment call.`);\n sayExplain(\"merge-failed\");\n process.exitCode = 1;\n return null;\n }\n\n // Gate: GATING issues block the archive. Severity and gating are two\n // different questions (issue.ts): errors gate because the merge would write\n // something wrong, and the rare warning marked `gates` blocks too — the\n // document is legal but the merge would drop authored content. Advisory\n // warnings are printed (and carried into --json) but never block, matching\n // validate's own doctrine (report.ts: \"warnings never gate\"). --approve\n // overrides the gating issues ONLY, and must say exactly which ones it is\n // walking past. A dry run is gated too: a plan for a merge that would be\n // refused describes nothing that will happen.\n const issues = await featureCoherence({ docsDir: config.docsDir, featureDir, featureId: id, preloadedDelta: deltaDoc });\n // The issues --approve cannot move, carved out before the gate below reads\n // the rest. The register is issue.ts's `approveOverrides` — the same data\n // the `overridable` key in the envelope is resolved from — never a code\n // string spelled here, because two spellings of \"what the flag moves\" would\n // drift. What the register HOLDS has grown: it began as one code (a\n // `metadata { service }` binding the id grammar refuses, where the merge\n // would splice the name into the living map verbatim and probe\n // `services/<binding>/` with it) and now holds the create-only collisions of\n // every whole-file axis besides. They share the doctrine and nothing else —\n // each is a mechanical fact about what the merge would write, never a\n // judgment about the feature — which is why the message below counts them\n // and lets each issue say its own sentence.\n //\n // IT USED TO NAME BINDINGS, and had been wrong for every other code in the\n // register since the use-case slot landed: a feature whose flow collided\n // with a living one was refused with \"1 element binding(s) name an illegal\n // service id\", about a feature that binds nothing. A refusal headline that\n // describes the wrong breach sends its reader to the wrong file first.\n const nonOverridable = issues.filter((i) => !approveOverrides(i));\n if (nonOverridable.length > 0) {\n const msg = `archive ${id} — BLOCKED: ${nonOverridable.length} issue(s) --approve does not override; each names what the merge would write`;\n if (json) {\n refuseJson(\"not-coherent\", msg, issues, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n // The whole list, not just the carve-out: the reader is about to fix the\n // feature, and the issues --approve COULD have moved are work they still\n // owe — hiding them here made the second refusal a surprise. The closing\n // line says which of the marks the flag cannot move.\n for (const i of issues) console.error(` ${SEVERITY_MARK[i.severity]} ${i.message}`);\n console.error(\n `\\nOf these, --approve does not override ${nonOverridable.length}: ${[...new Set(nonOverridable.map((i) => i.code))].sort().join(\", \")}. ` +\n \"Each is a mechanical fact about what the merge would write — a name loam cannot resolve, or a file it would replace wholesale — not a judgement the flag can move. \" +\n \"Fix those; the other issues gate as usual.\",\n );\n sayExplain(\"not-coherent\");\n process.exitCode = 1;\n return null;\n }\n const gating = issues.filter(gatesArchive);\n const advisory = issues.filter((i) => !gatesArchive(i));\n if (gating.length > 0 && !opts.approve) {\n const msg = `archive ${id} — BLOCKED: not coherent (${gating.length} gating issue(s), ${advisory.length} advisory warning(s))`;\n if (json) {\n refuseJson(\"not-coherent\", msg, issues, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const i of issues) console.error(` ${SEVERITY_MARK[i.severity]} ${i.message}`);\n console.error(`\\nFix the gating issues (advisory warnings never block), or re-run with --approve to override them (may corrupt the living docs).`);\n sayExplain(\"not-coherent\");\n process.exitCode = 1;\n return null;\n }\n if (gating.length > 0) {\n say(`⚠ archiving despite ${gating.length} gating issue(s) (--approve):`);\n for (const i of gating) say(` ${SEVERITY_MARK[i.severity]} ${i.message}`);\n say();\n }\n if (advisory.length > 0) {\n say(`⚠ ${advisory.length} warning(s) (non-blocking):`);\n for (const i of advisory) say(` ⚠ ${i.message}`);\n say();\n }\n\n // A `specs/<svc>/` addressed to a service that exists nowhere. `validate`\n // graded this and archive did not, which is the half that costs something:\n // `specs/<svc>/` is what the merge MATERIALISES `services/<svc>/` from, so a\n // one-character slip in `--touches` archived at exit 0 and left the fleet a\n // living service nobody meant to adopt. The set comes from coherence/living.ts's\n // `unknownDeltaServices` — the same function validate reads — so the two\n // gates cannot drift into disagreeing about which ids are real.\n //\n // Gated exactly like a coherence error, --approve included: which service you\n // meant is a judgment about the FEATURE. A feature that genuinely introduces\n // one has a better escape hatch than --approve anyway — tag its element in\n // delta.likec4, which is also what makes the fleet map true afterwards.\n const unknownServices = await unknownDeltaServices(config.docsDir, featureDir, id, { preloadedDelta: deltaDoc });\n if (unknownServices.length > 0 && !opts.approve) {\n const msg = `archive ${id} — BLOCKED: ${unknownServices.length} per-service delta(s) address a service that does not exist`;\n if (json) {\n refuseFindings(\"not-coherent\", msg, unknownServices, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const f of unknownServices) console.error(` ✗ ${f.message}`);\n console.error(`\\nFix the id, or introduce the service in this feature's delta.likec4 — or re-run with --approve to create it anyway.`);\n sayExplain(\"not-coherent\");\n process.exitCode = 1;\n return null;\n }\n if (unknownServices.length > 0) {\n say(`⚠ archiving despite ${unknownServices.length} unknown service(s) (--approve) — this merge creates them:`);\n for (const f of unknownServices) say(` ✗ ${f.message}`);\n say();\n }\n\n // A LIVING requirement outside `## Requirements` is a merge loam cannot do\n // correctly: the rewrite replaces only the requirements RUN inside that\n // section (rewriteRequirementsRun) while parseRequirements collects from\n // every section — so the strayed requirement keeps its authored copy in the\n // preserved prose AND lands again in the rewritten run, and the next\n // archive's MODIFIED replaces only one of them. Refused rather than\n // repaired: excising blocks from prose programmatically would be archive\n // editing text it does not understand. --approve does not apply — it\n // overrides judgments about the FEATURE, and this is neither.\n const strayed: Issue[] = [];\n const scanStrayed = async (livingPath: string, subject: string): Promise<void> => {\n if (!existsSync(livingPath)) return;\n for (const r of parseRequirements(await readUtf8(livingPath))) {\n // The ONE definition of the heading (spec.ts): the guard and the rewrite\n // boundary match the same way, so they cannot disagree about \"outside\".\n if (r.section !== undefined && isRequirementsHeading(r.section)) continue;\n const where = r.section === undefined ? \"above every heading\" : `under '${r.section}'`;\n strayed.push({\n severity: \"error\",\n code: \"living.requirement-outside-requirements\",\n subject,\n message: `${subject}: living requirement '${r.name}' sits ${where} in ${repoPath(config.docsDir, livingPath)} — the merge rewrites only '## Requirements', so it would land in the file twice. Re-home it under '## Requirements' first, then re-run.`,\n });\n }\n };\n for (const svc of deltaServices) {\n for (const axis of SPEC_AXES) {\n if (!existsSync(featureSpecPaths(featureDir, svc)[axis.key])) continue;\n // The shared context, because this sits in a deltaServices × SPEC_AXES\n // loop: without it every iteration re-walks and re-reads the fleet.\n await scanStrayed((await locateServicePaths(config.docsDir, svc, config.fleet))[axis.key], svc);\n }\n }\n // The business corpus, for the identical mechanical reason: a capability\n // document is narrative ABOVE `## Requirements`, `rewriteRequirementsRun`\n // rewrites only the run inside that heading, and a requirement filed under\n // `## Notes` would keep its authored copy in the preserved prose AND land\n // again in the rewritten run.\n for (const doc of capabilityDeltas) {\n await scanStrayed(livingCapabilityPaths(config.docsDir, doc.id).spec, doc.id);\n }\n if (strayed.length > 0) {\n const msg = `archive ${id} — BLOCKED: ${strayed.length} living requirement(s) outside '## Requirements'`;\n if (json) {\n refuseJson(\"living-outside-requirements\", msg, strayed, `features/${dirName}`);\n return null;\n }\n console.error(`${msg}:`);\n for (const i of strayed) console.error(` ✗ ${i.message}`);\n console.error(`\\n--approve does not override this — the duplication is mechanical, not a judgment call.`);\n sayExplain(\"living-outside-requirements\");\n process.exitCode = 1;\n return null;\n }\n\n // Pre-flight: the archive destination must be free, or the final move would fail\n // after the living docs were already rewritten.\n const archiveDir = archiveRoot(config.docsDir);\n const archiveDest = join(archiveDir, dirName);\n if (existsSync(archiveDest)) {\n fail(json, \"archive-exists\", `archive ${id} — BLOCKED: features/archive/${dirName} already exists. Remove or rename it, then re-run.`);\n return null;\n }\n\n return {\n id,\n dirName,\n featureDir,\n deltaDoc,\n deltaServices,\n capabilityDeltas,\n gating,\n advisory,\n archiveDir,\n archiveDest,\n recovered,\n issues,\n };\n}\n"]}
|
|
@@ -34,7 +34,7 @@ import { enumeratedServiceIds } from "../../../core/repo/service-target.js";
|
|
|
34
34
|
import { extendingModels } from "../../../core/c4/service-model/fleet/extending.js";
|
|
35
35
|
import { modelMergeErrors } from "./model/extending.js";
|
|
36
36
|
import {} from "./state.js";
|
|
37
|
-
import {
|
|
37
|
+
import { CodedFailure } from "../../../core/envelope/json.js";
|
|
38
38
|
import { featurePaths } from "../../../core/repo/paths.js";
|
|
39
39
|
import { featureFlows, livingFlowPath, USECASE_SUBDIR } from "../../../core/usecases/delta/flows.js";
|
|
40
40
|
import { featureDeployments, livingDeploymentPath } from "../../../core/deployment/delta.js";
|
|
@@ -50,7 +50,7 @@ export async function planLandscape(config, gated, plan, say) {
|
|
|
50
50
|
// read an axis. Skipping here would silently drop one merge axis in the one
|
|
51
51
|
// command engineered against quiet partial merges — same rule as an
|
|
52
52
|
// unparseable landscape or openapi: the plan stops before anything is written.
|
|
53
|
-
throw new
|
|
53
|
+
throw new CodedFailure("merge-failed", `delta.likec4 has ${delta.errors.length} parse error(s) — the architecture axis cannot be merged; fix it (\`loam validate --feature ${id}\`) or delete the file`);
|
|
54
54
|
}
|
|
55
55
|
// The feature tag is the whole selection mechanism of this merge, and since
|
|
56
56
|
// LikeC4 1.59.0 a specification KIND can carry a tag that every element or
|
|
@@ -65,7 +65,7 @@ export async function planLandscape(config, gated, plan, say) {
|
|
|
65
65
|
...Object.entries(delta.specification?.relationshipKindTags ?? {}),
|
|
66
66
|
].find(([, tags]) => tags.includes(id));
|
|
67
67
|
if (kindTagged !== undefined) {
|
|
68
|
-
throw new
|
|
68
|
+
throw new CodedFailure("merge-failed", `delta.likec4 declares the feature tag '#${id}' on kind '${kindTagged[0]}' in its specification block, so every ` +
|
|
69
69
|
`'${kindTagged[0]}' in the file inherits it — the merge would treat the whole document as this feature's ` +
|
|
70
70
|
`additions, context elements included, and splice them into architecture/landscape.likec4. Nothing was ` +
|
|
71
71
|
`written. Remove '#${id}' from the kind and tag only the declarations ${id} actually adds.`);
|
|
@@ -126,7 +126,7 @@ export async function planLandscape(config, gated, plan, say) {
|
|
|
126
126
|
model: { path: additions.model.path, content },
|
|
127
127
|
});
|
|
128
128
|
if (errors.length > 0) {
|
|
129
|
-
throw new
|
|
129
|
+
throw new CodedFailure("merge-failed", `the merged ${additions.model.path} would not parse (${errors.length} error(s): ${errors.slice(0, 3).join("; ")}) — ` +
|
|
130
130
|
`nothing was written. This model is read beside architecture/landscape.likec4 and nothing else, so an addition ` +
|
|
131
131
|
`fits only if the map declares its kind and every element it names. Most often it is a kind or tag missing from ` +
|
|
132
132
|
`the map's specification block, or a reference to ANOTHER service's interior, which lives in that service's own ` +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet map merge, the flows drawn over it, and the services this archive\n * brings into existence.\n *\n * The landscape is the one document every feature writes into, so it is spliced\n * rather than rewritten: the authored file keeps its shape and the delta's\n * elements are placed into it. A service arriving on the architecture axis alone\n * has no `model.likec4` yet, and the warning for that is carried out of the plan\n * — `validate` would report it next, and reporting it here is what stops that\n * being a surprise.\n *\n * The map is not the only document the C4 axis writes. What a delta nests\n * INSIDE a service belongs to that service's `model.likec4` when the model\n * extends the map — the splicer routes it there, this stages the write, and\n * `./model/extending.ts` proves the result parses beside the merged map before\n * anything lands.\n *\n * `planFlows` at the bottom is the other half of the same axis and shares this\n * module for that reason rather than for the file cap: both write into\n * `architecture/`, and the two merges are read together — the map gains the\n * elements, the flow gains the ordered hops over them. It is a whole-file copy\n * where the landscape is a splice, because a flow is a document of its own and\n * there is nothing to merge partially.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { planWrite, readUtf8 } from \"../../../core/staging/writes.js\";\nimport { planLandscapeMerge } from \"../../../core/c4/splice/landscape-merge.js\";\nimport { spliceModel } from \"../../../core/c4/splice/model/merge.js\";\nimport { titleOf } from \"../../../core/c4/splice/identity/edges.js\";\nimport { parseServiceId } from \"../../../core/kernel/ids/service.js\";\nimport { landscapePath as landscapeFile } from \"../../../core/repo/paths.js\";\nimport { enumeratedServiceIds } from \"../../../core/repo/service-target.js\";\nimport { extendingModels } from \"../../../core/c4/service-model/fleet/extending.js\";\nimport { modelMergeErrors } from \"./model/extending.js\";\nimport { type Gated, type Plan } from \"./state.js\";\nimport { ArchiveFailure } from \"./refusal.js\";\nimport { featurePaths } from \"../../../core/repo/paths.js\";\nimport { featureFlows, livingFlowPath, USECASE_SUBDIR } from \"../../../core/usecases/delta/flows.js\";\nimport { featureDeployments, livingDeploymentPath } from \"../../../core/deployment/delta.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\n\nexport async function planLandscape(\n config: { docsDir: DocsDir; fleet: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { id, featureDir, deltaDoc, deltaServices } = gated;\n const deltaLikec4 = featurePaths(featureDir).delta;\n const { writes, planWarns, architectureServices } = plan;\n const landscapePath = landscapeFile(config.docsDir);\n if (deltaDoc !== undefined) {\n const delta = deltaDoc;\n if (delta.errors.length > 0) {\n // --approve overrides loam's JUDGMENT about coherence, never its ability to\n // read an axis. Skipping here would silently drop one merge axis in the one\n // command engineered against quiet partial merges — same rule as an\n // unparseable landscape or openapi: the plan stops before anything is written.\n throw new ArchiveFailure(\n \"merge-failed\",\n `delta.likec4 has ${delta.errors.length} parse error(s) — the architecture axis cannot be merged; fix it (\\`loam validate --feature ${id}\\`) or delete the file`,\n );\n }\n // The feature tag is the whole selection mechanism of this merge, and since\n // LikeC4 1.59.0 a specification KIND can carry a tag that every element or\n // edge of that kind then inherits. A delta declaring its own feature tag on\n // a kind makes `newEls`/`newRels` below select the entire document —\n // including the context declarations the scaffold ships commented out\n // precisely so they are NOT merged — and archive would splice somebody\n // else's services into the fleet map at exit 0. Mechanical, so `--approve`\n // does not reach it, and refused before the merge is even planned.\n const kindTagged = [\n ...Object.entries(delta.specification?.elementKindTags ?? {}),\n ...Object.entries(delta.specification?.relationshipKindTags ?? {}),\n ].find(([, tags]) => tags.includes(id));\n if (kindTagged !== undefined) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `delta.likec4 declares the feature tag '#${id}' on kind '${kindTagged[0]}' in its specification block, so every ` +\n `'${kindTagged[0]}' in the file inherits it — the merge would treat the whole document as this feature's ` +\n `additions, context elements included, and splice them into architecture/landscape.likec4. Nothing was ` +\n `written. Remove '#${id}' from the kind and tag only the declarations ${id} actually adds.`,\n );\n }\n const newEls = delta.elements.filter((e) => e.tags.includes(id));\n const newRels = delta.relationships.filter((r) => r.tags.includes(id));\n if (existsSync(landscapePath)) {\n const deltaText = await readUtf8(deltaLikec4);\n const plan = await planLandscapeMerge({\n landscapeText: await readUtf8(landscapePath),\n deltaText,\n deltaElements: delta.elements,\n newEls,\n newRels,\n featureId: id,\n // A service whose model EXTENDS the map owns its own interior, so the\n // merge routes what is nested under it into that model instead of the\n // map (verification 2026-09-04, E1).\n models: await extendingModels(config.docsDir, config.fleet),\n });\n // A service can arrive on the ARCHITECTURE axis alone: an element this\n // merge ADDS, carrying a `metadata { service }` binding, with no\n // `specs/<svc>/` anywhere in the feature. It is a service the fleet gate\n // will demand a directory for the moment this merge lands, so it owes the\n // same warning as one arriving with a requirement delta — and until it\n // did, the closing \"complete + current\" line printed over a landscape\n // this very archive had just made red. Read off the ADDED elements, not\n // the tagged ones: an element the living landscape already had is not\n // arriving, and one that is never merged is not there to demand anything.\n // A binding is document text, which the service path builders no longer accept.\n // The parse cannot actually filter anything here: an illegal binding is\n // `c4.service-binding-invalid`, a coherence ERROR `--approve` does not\n // override, refused before any merge is planned — so the `ok` test only\n // carries the compiler's proof that document text never reaches a path.\n for (const e of plan.addedEls) {\n const parsed = e.service === undefined ? undefined : parseServiceId(e.service);\n if (parsed?.ok === true) architectureServices.add(parsed.id);\n }\n if (plan.content !== null) writes.push(planWrite(landscapePath, plan.content));\n say(`\\n architecture: merged into landscape.likec4 — +${plan.addedEls.length} element(s), +${plan.addedRels.length} relationship(s)`);\n for (const e of plan.addedEls) say(` + ${e.title} (${e.kind})`);\n for (const r of plan.addedRels) {\n say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} \"${r.title ?? \"\"}\"`);\n }\n for (const additions of plan.models) {\n const content = spliceModel({ additions, deltaText, featureId: id });\n // The model's own parse net, and it has to be a PROJECT one: an\n // extending model resolves against the map, and against the map this\n // very archive would leave — so the merged landscape goes into the set\n // beside it. Refused here, at plan time, nothing written, exactly as an\n // unparseable merged landscape is.\n const errors = await modelMergeErrors({\n docsDir: config.docsDir,\n landscape: plan.content,\n model: { path: additions.model.path, content },\n });\n if (errors.length > 0) {\n throw new ArchiveFailure(\n \"merge-failed\",\n `the merged ${additions.model.path} would not parse (${errors.length} error(s): ${errors.slice(0, 3).join(\"; \")}) — ` +\n `nothing was written. This model is read beside architecture/landscape.likec4 and nothing else, so an addition ` +\n `fits only if the map declares its kind and every element it names. Most often it is a kind or tag missing from ` +\n `the map's specification block, or a reference to ANOTHER service's interior, which lives in that service's own ` +\n `model and is not in this project — draw that call service to service on the map instead. Fix the map's ` +\n `specification or the delta, then re-run`,\n );\n }\n writes.push(planWrite(join(config.docsDir, ...additions.model.path.split(\"/\")), content));\n say(\n ` architecture: merged into ${additions.model.path} — ` +\n `+${additions.els.length} element(s), +${additions.rels.length} relationship(s)`,\n );\n for (const e of additions.els) say(` + ${e.title} (${e.kind})`);\n for (const r of additions.rels) {\n say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} \"${r.title ?? \"\"}\"`);\n }\n }\n } else {\n say(`\\n architecture: no landscape.likec4 — ${newEls.length} element(s) not merged`);\n }\n }\n\n // A service this archive BRINGS INTO EXISTENCE arrives without the one file\n // `validate` demands of every service: its own model.likec4. The merge cannot\n // write it — the delta's tagged subtree is a landscape-level box, not a\n // container model, and inventing a plausible one is the kind of quiet fiction\n // the rest of loam exists to prevent — so the archive says so instead, and\n // stops claiming the docs are complete. Non-gating: the feature is coherent\n // and the merge is correct; what is missing is the next step, and refusing\n // here would make onboarding a new service impossible in one command.\n // \"New\" is enumeration membership, not an existsSync of services/<svc>/ at\n // the root: a FILED service exists wherever the tree walk found it, and the\n // root probe would grade every filed service as newly created here.\n const enumerated = new Set<string>(await enumeratedServiceIds(config.docsDir));\n const newServices = [...new Set([...deltaServices, ...architectureServices])].filter(\n (svc) => !enumerated.has(svc),\n );\n for (const svc of newServices) {\n // Two shapes of the same debt: a service with a requirement delta gets its\n // directory from this merge, one that arrives only in the landscape gets no\n // directory at all — and `validate --all` fails the second harder\n // (`landscape.service-unmodelled` names the binding with nothing behind it).\n const creates = deltaServices.some((d) => d === svc)\n ? `this archive creates services/${svc}/, but nothing writes services/${svc}/model.likec4`\n : `this archive puts '${svc}' in the landscape, but the fleet has no services/${svc}/ at all`;\n planWarns.push({\n severity: \"warn\",\n code: \"service.no-model\",\n subject: svc,\n message: `${svc}: ${creates} — 'loam validate --all' will report the service as incomplete until it exists. Run 'loam adopt --service ${svc}' from the service repo, or write the model by hand.`,\n });\n }\n}\n\n/**\n * The flows this feature brings, copied into `architecture/usecases/`.\n *\n * A create-only whole-file copy, and the smallest merge in the command: the\n * document is views-only, it names nothing the landscape merge has not already\n * placed (`planFlowOverlay` in the gate is what proves that, before this runs),\n * and a flow the living tree already holds was refused at the gate\n * (`usecase.flow-exists`, `core/usecases/delta/flows.ts` has the reasoning). So\n * by the time this runs every path here is a file that does not exist, and\n * `planWrite` marks it `exclusive` — a race that created it between the gate and\n * the commit fails the write rather than overwriting somebody.\n *\n * It lives beside `planLandscape` rather than beside the requirement merges\n * because it writes into `architecture/`, and the two merges are read together:\n * the landscape gains the elements, the flow gains the hops over them.\n * `unarchive` needs nothing new — a create is undone by deleting, which the\n * snapshot manifest already records for every write in the plan.\n */\nexport async function planFlows(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const flows = await featureFlows(gated.featureDir);\n if (flows.length === 0) return;\n for (const flow of flows) {\n plan.writes.push(planWrite(livingFlowPath(config.docsDir, flow.rel), await readUtf8(flow.path)));\n say(` use case: ${flow.rel} — created architecture/${USECASE_SUBDIR}/${flow.rel}`);\n }\n}\n\n/**\n * The same copy for the third whole-file axis: the topology a feature brings.\n *\n * Beside `planFlows` rather than folded into it, because the two write to\n * different places — a flow into `architecture/usecases/`, a topology straight\n * into `architecture/` — and one function taking a subdirectory argument would\n * make that difference a parameter instead of a decision each axis states.\n * `unarchive` needs nothing here either: a create is undone by deleting.\n */\nexport async function planDeployments(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const docs = await featureDeployments(gated.featureDir);\n if (docs.length === 0) return;\n for (const doc of docs) {\n plan.writes.push(planWrite(livingDeploymentPath(config.docsDir, doc.rel), await readUtf8(doc.path)));\n say(` deployment: ${doc.rel} — created architecture/${doc.rel}`);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/archive/plan/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet map merge, the flows drawn over it, and the services this archive\n * brings into existence.\n *\n * The landscape is the one document every feature writes into, so it is spliced\n * rather than rewritten: the authored file keeps its shape and the delta's\n * elements are placed into it. A service arriving on the architecture axis alone\n * has no `model.likec4` yet, and the warning for that is carried out of the plan\n * — `validate` would report it next, and reporting it here is what stops that\n * being a surprise.\n *\n * The map is not the only document the C4 axis writes. What a delta nests\n * INSIDE a service belongs to that service's `model.likec4` when the model\n * extends the map — the splicer routes it there, this stages the write, and\n * `./model/extending.ts` proves the result parses beside the merged map before\n * anything lands.\n *\n * `planFlows` at the bottom is the other half of the same axis and shares this\n * module for that reason rather than for the file cap: both write into\n * `architecture/`, and the two merges are read together — the map gains the\n * elements, the flow gains the ordered hops over them. It is a whole-file copy\n * where the landscape is a splice, because a flow is a document of its own and\n * there is nothing to merge partially.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { planWrite, readUtf8 } from \"../../../core/staging/writes.js\";\nimport { planLandscapeMerge } from \"../../../core/c4/splice/landscape-merge.js\";\nimport { spliceModel } from \"../../../core/c4/splice/model/merge.js\";\nimport { titleOf } from \"../../../core/c4/splice/identity/edges.js\";\nimport { parseServiceId } from \"../../../core/kernel/ids/service.js\";\nimport { landscapePath as landscapeFile } from \"../../../core/repo/paths.js\";\nimport { enumeratedServiceIds } from \"../../../core/repo/service-target.js\";\nimport { extendingModels } from \"../../../core/c4/service-model/fleet/extending.js\";\nimport { modelMergeErrors } from \"./model/extending.js\";\nimport { type Gated, type Plan } from \"./state.js\";\nimport { CodedFailure } from \"../../../core/envelope/json.js\";\nimport { featurePaths } from \"../../../core/repo/paths.js\";\nimport { featureFlows, livingFlowPath, USECASE_SUBDIR } from \"../../../core/usecases/delta/flows.js\";\nimport { featureDeployments, livingDeploymentPath } from \"../../../core/deployment/delta.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\n\nexport async function planLandscape(\n config: { docsDir: DocsDir; fleet: FleetContext },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const { id, featureDir, deltaDoc, deltaServices } = gated;\n const deltaLikec4 = featurePaths(featureDir).delta;\n const { writes, planWarns, architectureServices } = plan;\n const landscapePath = landscapeFile(config.docsDir);\n if (deltaDoc !== undefined) {\n const delta = deltaDoc;\n if (delta.errors.length > 0) {\n // --approve overrides loam's JUDGMENT about coherence, never its ability to\n // read an axis. Skipping here would silently drop one merge axis in the one\n // command engineered against quiet partial merges — same rule as an\n // unparseable landscape or openapi: the plan stops before anything is written.\n throw new CodedFailure(\n \"merge-failed\",\n `delta.likec4 has ${delta.errors.length} parse error(s) — the architecture axis cannot be merged; fix it (\\`loam validate --feature ${id}\\`) or delete the file`,\n );\n }\n // The feature tag is the whole selection mechanism of this merge, and since\n // LikeC4 1.59.0 a specification KIND can carry a tag that every element or\n // edge of that kind then inherits. A delta declaring its own feature tag on\n // a kind makes `newEls`/`newRels` below select the entire document —\n // including the context declarations the scaffold ships commented out\n // precisely so they are NOT merged — and archive would splice somebody\n // else's services into the fleet map at exit 0. Mechanical, so `--approve`\n // does not reach it, and refused before the merge is even planned.\n const kindTagged = [\n ...Object.entries(delta.specification?.elementKindTags ?? {}),\n ...Object.entries(delta.specification?.relationshipKindTags ?? {}),\n ].find(([, tags]) => tags.includes(id));\n if (kindTagged !== undefined) {\n throw new CodedFailure(\n \"merge-failed\",\n `delta.likec4 declares the feature tag '#${id}' on kind '${kindTagged[0]}' in its specification block, so every ` +\n `'${kindTagged[0]}' in the file inherits it — the merge would treat the whole document as this feature's ` +\n `additions, context elements included, and splice them into architecture/landscape.likec4. Nothing was ` +\n `written. Remove '#${id}' from the kind and tag only the declarations ${id} actually adds.`,\n );\n }\n const newEls = delta.elements.filter((e) => e.tags.includes(id));\n const newRels = delta.relationships.filter((r) => r.tags.includes(id));\n if (existsSync(landscapePath)) {\n const deltaText = await readUtf8(deltaLikec4);\n const plan = await planLandscapeMerge({\n landscapeText: await readUtf8(landscapePath),\n deltaText,\n deltaElements: delta.elements,\n newEls,\n newRels,\n featureId: id,\n // A service whose model EXTENDS the map owns its own interior, so the\n // merge routes what is nested under it into that model instead of the\n // map (verification 2026-09-04, E1).\n models: await extendingModels(config.docsDir, config.fleet),\n });\n // A service can arrive on the ARCHITECTURE axis alone: an element this\n // merge ADDS, carrying a `metadata { service }` binding, with no\n // `specs/<svc>/` anywhere in the feature. It is a service the fleet gate\n // will demand a directory for the moment this merge lands, so it owes the\n // same warning as one arriving with a requirement delta — and until it\n // did, the closing \"complete + current\" line printed over a landscape\n // this very archive had just made red. Read off the ADDED elements, not\n // the tagged ones: an element the living landscape already had is not\n // arriving, and one that is never merged is not there to demand anything.\n // A binding is document text, which the service path builders no longer accept.\n // The parse cannot actually filter anything here: an illegal binding is\n // `c4.service-binding-invalid`, a coherence ERROR `--approve` does not\n // override, refused before any merge is planned — so the `ok` test only\n // carries the compiler's proof that document text never reaches a path.\n for (const e of plan.addedEls) {\n const parsed = e.service === undefined ? undefined : parseServiceId(e.service);\n if (parsed?.ok === true) architectureServices.add(parsed.id);\n }\n if (plan.content !== null) writes.push(planWrite(landscapePath, plan.content));\n say(`\\n architecture: merged into landscape.likec4 — +${plan.addedEls.length} element(s), +${plan.addedRels.length} relationship(s)`);\n for (const e of plan.addedEls) say(` + ${e.title} (${e.kind})`);\n for (const r of plan.addedRels) {\n say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} \"${r.title ?? \"\"}\"`);\n }\n for (const additions of plan.models) {\n const content = spliceModel({ additions, deltaText, featureId: id });\n // The model's own parse net, and it has to be a PROJECT one: an\n // extending model resolves against the map, and against the map this\n // very archive would leave — so the merged landscape goes into the set\n // beside it. Refused here, at plan time, nothing written, exactly as an\n // unparseable merged landscape is.\n const errors = await modelMergeErrors({\n docsDir: config.docsDir,\n landscape: plan.content,\n model: { path: additions.model.path, content },\n });\n if (errors.length > 0) {\n throw new CodedFailure(\n \"merge-failed\",\n `the merged ${additions.model.path} would not parse (${errors.length} error(s): ${errors.slice(0, 3).join(\"; \")}) — ` +\n `nothing was written. This model is read beside architecture/landscape.likec4 and nothing else, so an addition ` +\n `fits only if the map declares its kind and every element it names. Most often it is a kind or tag missing from ` +\n `the map's specification block, or a reference to ANOTHER service's interior, which lives in that service's own ` +\n `model and is not in this project — draw that call service to service on the map instead. Fix the map's ` +\n `specification or the delta, then re-run`,\n );\n }\n writes.push(planWrite(join(config.docsDir, ...additions.model.path.split(\"/\")), content));\n say(\n ` architecture: merged into ${additions.model.path} — ` +\n `+${additions.els.length} element(s), +${additions.rels.length} relationship(s)`,\n );\n for (const e of additions.els) say(` + ${e.title} (${e.kind})`);\n for (const r of additions.rels) {\n say(` + ${titleOf(delta.elements, r.source)} -> ${titleOf(delta.elements, r.target)} \"${r.title ?? \"\"}\"`);\n }\n }\n } else {\n say(`\\n architecture: no landscape.likec4 — ${newEls.length} element(s) not merged`);\n }\n }\n\n // A service this archive BRINGS INTO EXISTENCE arrives without the one file\n // `validate` demands of every service: its own model.likec4. The merge cannot\n // write it — the delta's tagged subtree is a landscape-level box, not a\n // container model, and inventing a plausible one is the kind of quiet fiction\n // the rest of loam exists to prevent — so the archive says so instead, and\n // stops claiming the docs are complete. Non-gating: the feature is coherent\n // and the merge is correct; what is missing is the next step, and refusing\n // here would make onboarding a new service impossible in one command.\n // \"New\" is enumeration membership, not an existsSync of services/<svc>/ at\n // the root: a FILED service exists wherever the tree walk found it, and the\n // root probe would grade every filed service as newly created here.\n const enumerated = new Set<string>(await enumeratedServiceIds(config.docsDir));\n const newServices = [...new Set([...deltaServices, ...architectureServices])].filter(\n (svc) => !enumerated.has(svc),\n );\n for (const svc of newServices) {\n // Two shapes of the same debt: a service with a requirement delta gets its\n // directory from this merge, one that arrives only in the landscape gets no\n // directory at all — and `validate --all` fails the second harder\n // (`landscape.service-unmodelled` names the binding with nothing behind it).\n const creates = deltaServices.some((d) => d === svc)\n ? `this archive creates services/${svc}/, but nothing writes services/${svc}/model.likec4`\n : `this archive puts '${svc}' in the landscape, but the fleet has no services/${svc}/ at all`;\n planWarns.push({\n severity: \"warn\",\n code: \"service.no-model\",\n subject: svc,\n message: `${svc}: ${creates} — 'loam validate --all' will report the service as incomplete until it exists. Run 'loam adopt --service ${svc}' from the service repo, or write the model by hand.`,\n });\n }\n}\n\n/**\n * The flows this feature brings, copied into `architecture/usecases/`.\n *\n * A create-only whole-file copy, and the smallest merge in the command: the\n * document is views-only, it names nothing the landscape merge has not already\n * placed (`planFlowOverlay` in the gate is what proves that, before this runs),\n * and a flow the living tree already holds was refused at the gate\n * (`usecase.flow-exists`, `core/usecases/delta/flows.ts` has the reasoning). So\n * by the time this runs every path here is a file that does not exist, and\n * `planWrite` marks it `exclusive` — a race that created it between the gate and\n * the commit fails the write rather than overwriting somebody.\n *\n * It lives beside `planLandscape` rather than beside the requirement merges\n * because it writes into `architecture/`, and the two merges are read together:\n * the landscape gains the elements, the flow gains the hops over them.\n * `unarchive` needs nothing new — a create is undone by deleting, which the\n * snapshot manifest already records for every write in the plan.\n */\nexport async function planFlows(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const flows = await featureFlows(gated.featureDir);\n if (flows.length === 0) return;\n for (const flow of flows) {\n plan.writes.push(planWrite(livingFlowPath(config.docsDir, flow.rel), await readUtf8(flow.path)));\n say(` use case: ${flow.rel} — created architecture/${USECASE_SUBDIR}/${flow.rel}`);\n }\n}\n\n/**\n * The same copy for the third whole-file axis: the topology a feature brings.\n *\n * Beside `planFlows` rather than folded into it, because the two write to\n * different places — a flow into `architecture/usecases/`, a topology straight\n * into `architecture/` — and one function taking a subdirectory argument would\n * make that difference a parameter instead of a decision each axis states.\n * `unarchive` needs nothing here either: a create is undone by deleting.\n */\nexport async function planDeployments(\n config: { docsDir: DocsDir },\n gated: Gated,\n plan: Plan,\n say: (line?: string) => void,\n): Promise<void> {\n const docs = await featureDeployments(gated.featureDir);\n if (docs.length === 0) return;\n for (const doc of docs) {\n plan.writes.push(planWrite(livingDeploymentPath(config.docsDir, doc.rel), await readUtf8(doc.path)));\n say(` deployment: ${doc.rel} — created architecture/${doc.rel}`);\n }\n}\n"]}
|