@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13
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 +3448 -0
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +47 -23
- package/QUICKSTART.md +189 -12
- package/README.md +274 -65
- package/ROADMAP.md +572 -31
- package/SCHEMA.md +1709 -155
- package/WORKFLOW.md +298 -72
- 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/adopt/render.js +50 -9
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/archive.js +16 -19
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
- 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 +77 -43
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +21 -19
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +167 -30
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +14 -19
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +137 -56
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +3 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +134 -30
- 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 +85 -18
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +29 -7
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +102 -14
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +17 -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/doctor.js +60 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +89 -13
- package/dist/commands/explain/explain.js.map +1 -1
- package/dist/commands/explore.js +16 -10
- 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 +5 -7
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +44 -29
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +52 -9
- 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 +37 -34
- 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 +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +158 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +13 -11
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +9 -5
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +35 -6
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +41 -33
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/show/show.js +17 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +2 -0
- package/dist/commands/status/json.js.map +1 -1
- package/dist/commands/status/print.js +89 -37
- 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 +103 -23
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +106 -46
- 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 +289 -74
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +147 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +200 -21
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +42 -26
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +83 -33
- 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 +78 -27
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +34 -126
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- 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 +11 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +26 -20
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +144 -126
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +44 -14
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +10 -2
- 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/place.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +50 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +40 -47
- package/dist/commands/validate/fleet/views/stale.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/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +36 -160
- 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 +21 -107
- 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 +36 -14
- 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/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +121 -61
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +88 -28
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +38 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +85 -60
- 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 +55 -52
- 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 +81 -106
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +21 -9
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
- 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 +24 -8
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +80 -8
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +88 -10
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +49 -22
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +39 -6
- 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/protocol.js +1 -1
- package/dist/core/agent/protocol.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/support/report.js +46 -5
- package/dist/core/agent/support/report.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 +158 -11
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +78 -39
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +88 -27
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +128 -13
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +96 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +106 -4
- 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/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +6 -3
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +68 -27
- 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 +32 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +33 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +12 -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 +96 -36
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +209 -130
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +109 -31
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +14 -3
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/arch.js +90 -182
- package/dist/core/c4/arch.js.map +1 -1
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +169 -14
- 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/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +123 -7
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +43 -12
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +79 -43
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- 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 +78 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/seed/template.js +65 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +297 -10
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +222 -141
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +79 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js +20 -0
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +20 -20
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +37 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +182 -114
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +160 -56
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +13 -19
- 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/delta/uncovered.js +2 -2
- package/dist/core/capabilities/delta/uncovered.js.map +1 -1
- package/dist/core/capabilities/findings.js +18 -22
- 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/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +103 -22
- 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/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.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 +147 -16
- 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 +28 -87
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.js.map +1 -0
- 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 +51 -6
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/dependencies/fanin.js +2 -2
- package/dist/core/dependencies/fanin.js.map +1 -1
- package/dist/core/deployment/delta.js +191 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- 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 +49 -21
- 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/docs.js +31 -22
- package/dist/core/docs.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/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +91 -11
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/document/apply.js +158 -11
- 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 +26 -21
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/lookup.js +14 -1
- package/dist/core/explain/lookup.js.map +1 -1
- package/dist/core/explain/refusals.js +5 -5
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +68 -109
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +40 -10
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +81 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +11 -4
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +69 -28
- 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/delta.js +10 -7
- package/dist/core/glossary/delta.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/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/kernel/path-safety.js +38 -0
- package/dist/core/kernel/path-safety.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 +51 -37
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/links/findings.js.map +1 -1
- package/dist/core/links/parse.js +38 -9
- package/dist/core/links/parse.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 +40 -10
- 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 +301 -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 +28 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +17 -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 +43 -4
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +50 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- 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/authored/paths.js +18 -0
- package/dist/core/repo/authored/paths.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +92 -6
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +74 -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/render/body.js +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +53 -32
- 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/scaffold/landscape.js +121 -13
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.js.map +1 -0
- package/dist/core/staging/commit.js +26 -15
- 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 +84 -6
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +24 -14
- package/dist/core/staging/txn/forward.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/contracts.js +39 -15
- package/dist/core/status/contracts.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +33 -8
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +24 -17
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/feature/next.js +20 -7
- package/dist/core/status/feature/next.js.map +1 -1
- package/dist/core/status/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +43 -14
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +153 -58
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/scan.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 +199 -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/claims.js +31 -7
- package/dist/core/usecases/delta/claims.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/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +32 -191
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +195 -36
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +26 -14
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/place.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- 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 +70 -12
- 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 +4 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +75 -4
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/dist/core/vocabulary/report.js +16 -1
- package/dist/core/vocabulary/report.js.map +1 -1
- package/examples/README.md +54 -11
- package/examples/docs/architecture/landscape.likec4 +62 -6
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +7 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
- 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/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -37
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -31
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -220
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -165
- 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 -247
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/repo/tree/views.js +0 -172
- package/dist/core/repo/tree/views.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `architecture/` project AS THIS FEATURE'S MERGE WOULD LEAVE IT, staged
|
|
3
|
+
* and parsed, with a feature's own documents standing beside it.
|
|
4
|
+
*
|
|
5
|
+
* WHY THE POST-MERGE MAP AND NOT THE LIVING ONE. A document a feature brings
|
|
6
|
+
* names elements, and a feature that adds a service adds the element its own
|
|
7
|
+
* document names. Graded against the LIVING landscape that name is unresolved —
|
|
8
|
+
* LikeC4 refuses the document, the project blanks, and loam reports a hole
|
|
9
|
+
* against a file whose only fault is arriving with the change that makes it
|
|
10
|
+
* true. Graded against the merge it resolves exactly when the merge lands the
|
|
11
|
+
* element it names, which is `ARCH-LOAM-FEATURE-CORPUS`: inside a feature
|
|
12
|
+
* window, the corpus is feature ∪ living.
|
|
13
|
+
*
|
|
14
|
+
* IT IS ALSO THE CHECK. The merge preview is the only reading under which "a
|
|
15
|
+
* document naming an element the merge does not land" is answerable at all, and
|
|
16
|
+
* that refusal is why it runs before `loam archive` writes rather than after:
|
|
17
|
+
* without it the document is copied into `architecture/` and the NEXT reader's
|
|
18
|
+
* `loam validate --all` carries the failure, against a file they did not write.
|
|
19
|
+
*
|
|
20
|
+
* ## Why it lives here rather than in one axis
|
|
21
|
+
*
|
|
22
|
+
* It was `core/usecases/delta/overlay.ts`'s private machinery while the
|
|
23
|
+
* use-case slot was the only feature-local `.likec4` document. The deployment
|
|
24
|
+
* slot is the second, and a second copy of this staging is the copy that
|
|
25
|
+
* drifts — the two would disagree first about the merge preview, which is the
|
|
26
|
+
* one thing both refusals rest on. So the staging moved down to the package
|
|
27
|
+
* that owns the project loader and the merge, and each axis keeps only its own
|
|
28
|
+
* INTERPRETATION of the parse: which views are the feature's, or which
|
|
29
|
+
* documents failed.
|
|
30
|
+
*
|
|
31
|
+
* ## Why it stages its own tree
|
|
32
|
+
*
|
|
33
|
+
* `loadProject` copies documents from disk, keyed on their path relative to a
|
|
34
|
+
* base — so it can be handed neither a landscape that exists only in memory nor
|
|
35
|
+
* a document living outside `architecture/`, and both are exactly what this
|
|
36
|
+
* needs. Teaching it to take content would put a second, rarely-exercised
|
|
37
|
+
* staging rule inside a loader whose docstring already carries two rules that
|
|
38
|
+
* invert what holds elsewhere. Staging here costs one copy of a handful of
|
|
39
|
+
* small files and keeps that loader's contract at one.
|
|
40
|
+
*
|
|
41
|
+
* THE PRICE OF THAT CHOICE IS PATHS, and it is paid here rather than left to
|
|
42
|
+
* the caller: every path LikeC4 reports — a parse error's `sourceFsPath`, a
|
|
43
|
+
* view's `sourcePath` — names a temp tree, and a finding naming a temp
|
|
44
|
+
* directory is a finding nobody can act on. `real()` maps them back to the file
|
|
45
|
+
* the author wrote, and the temp tree is gone before this returns.
|
|
46
|
+
*
|
|
47
|
+
* FAIL CLOSED, and not silently. Every failure is the `unreadable` arm carrying
|
|
48
|
+
* LikeC4's own messages: a document this cannot read is never one that grades
|
|
49
|
+
* clean.
|
|
50
|
+
*/
|
|
51
|
+
import { existsSync } from "node:fs";
|
|
52
|
+
import { copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
53
|
+
import { tmpdir } from "node:os";
|
|
54
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
55
|
+
import { errorText, loadFile } from "../likec4.js";
|
|
56
|
+
import { createdServiceModels } from "../service-model/created/models.js";
|
|
57
|
+
import { planLandscapeMerge } from "../splice/landscape-merge.js";
|
|
58
|
+
import { inOrder } from "../../kernel/concurrency.js";
|
|
59
|
+
import { decodeDocument } from "../../kernel/document-bytes.js";
|
|
60
|
+
import { featurePaths, landscapePath } from "../../repo/paths.js";
|
|
61
|
+
import { architectureDir, generatedArchitectureDocuments } from "./architecture.js";
|
|
62
|
+
import { architectureDocuments } from "./documents.js";
|
|
63
|
+
import { asLoadedDoc, loadProject } from "./load.js";
|
|
64
|
+
/**
|
|
65
|
+
* Stage the living `architecture/` with the merge preview in place of the
|
|
66
|
+
* landscape, add the feature's own documents, and parse the result.
|
|
67
|
+
*
|
|
68
|
+
* A feature bringing no documents is not this function's business: both callers
|
|
69
|
+
* return their own empty answer before reaching it, at the cost of one walk
|
|
70
|
+
* over a directory that is not there. That walk is the axis's per-feature
|
|
71
|
+
* opt-in and the whole price a fleet that has not adopted it pays.
|
|
72
|
+
*/
|
|
73
|
+
export async function stageMergedProject(req) {
|
|
74
|
+
const merged = await mergedLandscape(req);
|
|
75
|
+
if (merged.kind === "failed")
|
|
76
|
+
return { kind: "unreadable", errors: [merged.reason] };
|
|
77
|
+
const arch = architectureDir(req.docsDir);
|
|
78
|
+
// BOTH generated documents come out, through the one list every project read
|
|
79
|
+
// shares: a views-only file does not parse standalone, so staging one that
|
|
80
|
+
// `sync` has not caught up with would blank the merge preview this whole
|
|
81
|
+
// module exists to parse (`./architecture.ts` states the rule).
|
|
82
|
+
const living = await architectureDocuments(arch, generatedArchitectureDocuments(req.docsDir));
|
|
83
|
+
const root = await mkdtemp(join(tmpdir(), "loam-staged-"));
|
|
84
|
+
try {
|
|
85
|
+
// Staged rel -> the path the AUTHOR wrote, so every message that leaves
|
|
86
|
+
// here names a file they can open. Built as the tree is staged, because
|
|
87
|
+
// that is the one moment both spellings are in hand.
|
|
88
|
+
const authored = new Map();
|
|
89
|
+
const landscape = resolve(landscapePath(req.docsDir));
|
|
90
|
+
await inOrder(living, async (path) => {
|
|
91
|
+
// The living landscape is staged from the merge preview when there is
|
|
92
|
+
// one; copying it as well would declare every element twice.
|
|
93
|
+
if (merged.kind === "merged" && resolve(path) === landscape)
|
|
94
|
+
return null;
|
|
95
|
+
await stage(root, spell(relative(arch, path)), path, authored);
|
|
96
|
+
return null;
|
|
97
|
+
});
|
|
98
|
+
if (merged.kind === "merged") {
|
|
99
|
+
const rel = spell(relative(arch, landscape));
|
|
100
|
+
await write(root, rel, merged.content);
|
|
101
|
+
authored.set(rel, landscape);
|
|
102
|
+
}
|
|
103
|
+
// The feature's own documents LAST, so one whose living twin exists wins
|
|
104
|
+
// the staging rather than colliding with it. That case is already an error
|
|
105
|
+
// the gate refuses before any merge — `usecase.flow-exists`,
|
|
106
|
+
// `deployment.doc-exists` — and grading the feature's version is the more
|
|
107
|
+
// useful of the two answers to give an author who is about to be told to
|
|
108
|
+
// delete the file.
|
|
109
|
+
const mine = new Set();
|
|
110
|
+
for (const document of req.documents) {
|
|
111
|
+
await stage(root, document.rel, document.path, authored);
|
|
112
|
+
mine.add(document.rel);
|
|
113
|
+
}
|
|
114
|
+
const doc = asLoadedDoc(await loadProject(root, await architectureDocuments(root)));
|
|
115
|
+
// TWO SPELLINGS REACH HERE and both must map, which is the one thing this
|
|
116
|
+
// helper cannot be allowed to get half right. A parse error's
|
|
117
|
+
// `sourceFsPath` is an ABSOLUTE path into the temp tree; a view's
|
|
118
|
+
// `sourcePath` is already the project-RELATIVE rel LikeC4 reports. The
|
|
119
|
+
// rel-first lookup answers the second, and the relative() computation the
|
|
120
|
+
// first. Handling only one leaves the other silently returning the temp
|
|
121
|
+
// spelling, which is a finding naming a directory that no longer exists.
|
|
122
|
+
const real = (path) => authored.get(spell(path)) ?? authored.get(spell(relative(root, path))) ?? path;
|
|
123
|
+
if (doc.errors.length > 0) {
|
|
124
|
+
// An error naming no document is LikeC4 declining to attribute one. It
|
|
125
|
+
// still travels: a hole loam cannot place is still a hole, and dropping
|
|
126
|
+
// it here would grade the feature clean on the strength of a missing
|
|
127
|
+
// field.
|
|
128
|
+
//
|
|
129
|
+
// DOCS-RELATIVE, `/`-separated, and carrying the line — the spelling the
|
|
130
|
+
// SERVICE arm of `usecase.flow-invalid` uses on purpose. These strings are
|
|
131
|
+
// the feature arm's whole payload (text, `--json` and the `loam archive`
|
|
132
|
+
// block message), and `real()` alone answered with an absolute Windows path
|
|
133
|
+
// and no line at all: `C:\Users\…\docs\features\FEAT-1\usecases\x.likec4:
|
|
134
|
+
// <message>` in a finding a person is asked to act on (verification
|
|
135
|
+
// 2026-09-04, W7). A path outside the docs root — which nothing here
|
|
136
|
+
// produces, but `relative` would spell `..\..` — keeps its absolute form.
|
|
137
|
+
return {
|
|
138
|
+
kind: "unreadable",
|
|
139
|
+
errors: doc.errors.map((e) => e.sourceFsPath === undefined ? errorText(e) : `${spellReal(req.docsDir, real(e.sourceFsPath))}: ${errorText(e)}`),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return { kind: "read", doc, real, mine };
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
// The `unreadable` arm is a PROMISE, not a description of the happy path.
|
|
146
|
+
// Two things under here throw rather than return: `loadProject` refuses an
|
|
147
|
+
// error it cannot attribute to a document, and every `mkdir`/`copyFile` in
|
|
148
|
+
// the staging above can fail on a full disk or a denied temp directory.
|
|
149
|
+
// Callers run inside `loam validate --feature` and the archive gate — and
|
|
150
|
+
// the one behaviour a check added to those may never have is failing the
|
|
151
|
+
// run it was added to. So a throw becomes the same could-not-look answer a
|
|
152
|
+
// parse error already produces, which each caller turns into its own
|
|
153
|
+
// refusal that writes nothing.
|
|
154
|
+
return { kind: "unreadable", errors: [err instanceof Error ? err.message : String(err)] };
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
await rm(root, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The merge preview, computed with the SAME function `loam archive` commits
|
|
162
|
+
* with.
|
|
163
|
+
*
|
|
164
|
+
* Reusing `planLandscapeMerge` rather than approximating it is what makes this
|
|
165
|
+
* an answer about the archive rather than a second opinion beside it: an
|
|
166
|
+
* element placed differently, a tag stripped differently, or a splice refused
|
|
167
|
+
* there and allowed here would be a document graded against a map the merge
|
|
168
|
+
* never writes.
|
|
169
|
+
*
|
|
170
|
+
* THE PARITY IS THE MERGE, NOT THE WHOLE GATE, and the two places the sides
|
|
171
|
+
* differ are worth naming rather than leaving for a reader to discover.
|
|
172
|
+
*
|
|
173
|
+
* The first changes no verdict. `planLandscape` runs one check BEFORE it
|
|
174
|
+
* computes the merge and this does not: a delta declaring the feature tag on a
|
|
175
|
+
* specification KIND is refused there, because every element of that kind
|
|
176
|
+
* inherits the tag and the merge would splice the whole document. Such a delta
|
|
177
|
+
* reaches `newEls` here unfiltered, so the preview this grades against is one
|
|
178
|
+
* the archive would never write — but that archive refuses either way, before
|
|
179
|
+
* anything is written.
|
|
180
|
+
*
|
|
181
|
+
* The second changed every verdict about a fleet with extending models, and is
|
|
182
|
+
* why `models` is on the request at all. `planLandscape` names the fleet's
|
|
183
|
+
* extending models, so a nested addition is ROUTED into the model that owns
|
|
184
|
+
* that service's interior and never reaches the map; a request that omits them
|
|
185
|
+
* previews a map holding interior the archive will put somewhere else. A
|
|
186
|
+
* feature-local `usecases/<x>.likec4` whose hop names `<service-fqn>.<container>`
|
|
187
|
+
* then resolved in the preview, passed `usecase.flow-invalid` — the gate whose
|
|
188
|
+
* own message is "the archive would land an architecture/ nothing has checked",
|
|
189
|
+
* and which `--approve` cannot override — and was copied verbatim into
|
|
190
|
+
* `architecture/`, where it resolves against nothing and takes the whole fleet
|
|
191
|
+
* map down (verification 2026-09-04, review C; SCHEMA names that outcome as the
|
|
192
|
+
* thing that must not happen). A caller that leaves `models` out is asking for
|
|
193
|
+
* the pre-routing preview, and gets exactly the answers it got before.
|
|
194
|
+
*
|
|
195
|
+
* A throw is `failed` rather than propagated. Every `LandscapeSpliceError` this
|
|
196
|
+
* can raise is one `planLandscape` raises again — with its own message, its own
|
|
197
|
+
* refusal code, and before anything is written — so throwing here would only
|
|
198
|
+
* decide which of two identical refusals an author reads first.
|
|
199
|
+
*/
|
|
200
|
+
async function mergedLandscape(req) {
|
|
201
|
+
const deltaPath = featurePaths(req.featureDir).delta;
|
|
202
|
+
const landscape = landscapePath(req.docsDir);
|
|
203
|
+
if (!existsSync(deltaPath) || !existsSync(landscape))
|
|
204
|
+
return { kind: "living" };
|
|
205
|
+
const delta = await (req.load ?? loadFile)(deltaPath);
|
|
206
|
+
if (delta.errors.length > 0) {
|
|
207
|
+
return {
|
|
208
|
+
kind: "failed",
|
|
209
|
+
reason: `delta.likec4 has ${delta.errors.length} parse error(s) — a feature's own documents cannot be graded against a map that does not parse`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// The models the archive will CREATE route exactly as the living ones do —
|
|
213
|
+
// a new service's interior lands in its new model, never on the map — so a
|
|
214
|
+
// preview asking for the routing gets them too (`createdServiceModels` says why).
|
|
215
|
+
const models = req.models === undefined
|
|
216
|
+
? undefined
|
|
217
|
+
: [
|
|
218
|
+
...req.models,
|
|
219
|
+
...(await createdServiceModels({
|
|
220
|
+
docsDir: req.docsDir,
|
|
221
|
+
featureDir: req.featureDir,
|
|
222
|
+
featureId: req.featureId,
|
|
223
|
+
drawn: [...(await (req.load ?? loadFile)(landscape)).elements, ...delta.elements.filter((e) => e.tags.includes(req.featureId))],
|
|
224
|
+
})),
|
|
225
|
+
];
|
|
226
|
+
try {
|
|
227
|
+
const plan = await planLandscapeMerge({
|
|
228
|
+
landscapeText: await text(landscape),
|
|
229
|
+
deltaText: await text(deltaPath),
|
|
230
|
+
deltaElements: delta.elements,
|
|
231
|
+
newEls: delta.elements.filter((e) => e.tags.includes(req.featureId)),
|
|
232
|
+
newRels: delta.relationships.filter((r) => r.tags.includes(req.featureId)),
|
|
233
|
+
featureId: req.featureId,
|
|
234
|
+
// As `planLandscape` passes it: without the table every edge of a tagged
|
|
235
|
+
// kind is a statement the splice cannot locate, and the preview fails a
|
|
236
|
+
// feature's flows over a merge the archive itself computes.
|
|
237
|
+
deltaKindTags: delta.specification?.relationshipKindTags ?? {},
|
|
238
|
+
...(models === undefined ? {} : { models }),
|
|
239
|
+
});
|
|
240
|
+
return plan.content === null ? { kind: "living" } : { kind: "merged", content: plan.content };
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
return { kind: "failed", reason: err instanceof Error ? err.message : String(err) };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/** Read a document loam is about to hand LikeC4, refusing the encodings that decode as empty. */
|
|
247
|
+
async function text(path) {
|
|
248
|
+
return decodeDocument(await readFile(path), path);
|
|
249
|
+
}
|
|
250
|
+
/** A path spelled the way a staged rel is spelled — `/`-separated, on every platform. */
|
|
251
|
+
function spell(path) {
|
|
252
|
+
return path.split(/[\\/]/).join("/");
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* An authored path as a FINDING spells it: relative to the docs root,
|
|
256
|
+
* `/`-separated. A path that does not sit under the docs root keeps its absolute
|
|
257
|
+
* form — `relative` would answer with `..` segments, and a finding naming a
|
|
258
|
+
* document outside the repository is more honest as the absolute path it is.
|
|
259
|
+
*/
|
|
260
|
+
function spellReal(docsDir, path) {
|
|
261
|
+
const rel = relative(docsDir, path);
|
|
262
|
+
return rel.length > 0 && !rel.startsWith("..") ? spell(rel) : spell(path);
|
|
263
|
+
}
|
|
264
|
+
/** Copy one authored document into the staged tree, remembering where it came from. */
|
|
265
|
+
async function stage(root, rel, path, authored) {
|
|
266
|
+
const dest = join(root, ...rel.split("/"));
|
|
267
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
268
|
+
await copyFile(path, dest);
|
|
269
|
+
authored.set(rel, resolve(path));
|
|
270
|
+
}
|
|
271
|
+
/** Write one document into the staged tree from bytes loam holds rather than a file. */
|
|
272
|
+
async function write(root, rel, content) {
|
|
273
|
+
const dest = join(root, ...rel.split("/"));
|
|
274
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
275
|
+
await writeFile(dest, content, "utf8");
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=staged.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staged.js","sourceRoot":"","sources":["../../../../src/core/c4/project/staged.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `architecture/` project AS THIS FEATURE'S MERGE WOULD LEAVE IT, staged\n * and parsed, with a feature's own documents standing beside it.\n *\n * WHY THE POST-MERGE MAP AND NOT THE LIVING ONE. A document a feature brings\n * names elements, and a feature that adds a service adds the element its own\n * document names. Graded against the LIVING landscape that name is unresolved —\n * LikeC4 refuses the document, the project blanks, and loam reports a hole\n * against a file whose only fault is arriving with the change that makes it\n * true. Graded against the merge it resolves exactly when the merge lands the\n * element it names, which is `ARCH-LOAM-FEATURE-CORPUS`: inside a feature\n * window, the corpus is feature ∪ living.\n *\n * IT IS ALSO THE CHECK. The merge preview is the only reading under which \"a\n * document naming an element the merge does not land\" is answerable at all, and\n * that refusal is why it runs before `loam archive` writes rather than after:\n * without it the document is copied into `architecture/` and the NEXT reader's\n * `loam validate --all` carries the failure, against a file they did not write.\n *\n * ## Why it lives here rather than in one axis\n *\n * It was `core/usecases/delta/overlay.ts`'s private machinery while the\n * use-case slot was the only feature-local `.likec4` document. The deployment\n * slot is the second, and a second copy of this staging is the copy that\n * drifts — the two would disagree first about the merge preview, which is the\n * one thing both refusals rest on. So the staging moved down to the package\n * that owns the project loader and the merge, and each axis keeps only its own\n * INTERPRETATION of the parse: which views are the feature's, or which\n * documents failed.\n *\n * ## Why it stages its own tree\n *\n * `loadProject` copies documents from disk, keyed on their path relative to a\n * base — so it can be handed neither a landscape that exists only in memory nor\n * a document living outside `architecture/`, and both are exactly what this\n * needs. Teaching it to take content would put a second, rarely-exercised\n * staging rule inside a loader whose docstring already carries two rules that\n * invert what holds elsewhere. Staging here costs one copy of a handful of\n * small files and keeps that loader's contract at one.\n *\n * THE PRICE OF THAT CHOICE IS PATHS, and it is paid here rather than left to\n * the caller: every path LikeC4 reports — a parse error's `sourceFsPath`, a\n * view's `sourcePath` — names a temp tree, and a finding naming a temp\n * directory is a finding nobody can act on. `real()` maps them back to the file\n * the author wrote, and the temp tree is gone before this returns.\n *\n * FAIL CLOSED, and not silently. Every failure is the `unreadable` arm carrying\n * LikeC4's own messages: a document this cannot read is never one that grades\n * clean.\n */\nimport { existsSync } from \"node:fs\";\nimport { copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport { errorText, loadFile, type LoadedDoc } from \"../likec4.js\";\nimport { createdServiceModels } from \"../service-model/created/models.js\";\nimport type { ExtendingModel } from \"../splice/contract.js\";\nimport { planLandscapeMerge } from \"../splice/landscape-merge.js\";\nimport { inOrder } from \"../../kernel/concurrency.js\";\nimport { decodeDocument } from \"../../kernel/document-bytes.js\";\nimport type { DocsDir, FeatureDir } from \"../../kernel/ids/dirs.js\";\nimport { featurePaths, landscapePath } from \"../../repo/paths.js\";\nimport { architectureDir, generatedArchitectureDocuments } from \"./architecture.js\";\nimport { architectureDocuments } from \"./documents.js\";\nimport { asLoadedDoc, loadProject } from \"./load.js\";\n\n/** One document a feature brings, and where it would live under `architecture/`. */\nexport interface StagedDocument {\n /** Its path relative to `architecture/`, `/`-separated — where the merge would write it. */\n rel: string;\n /** The absolute path of the authored file. */\n path: string;\n}\n\nexport interface StagedProjectRequest {\n docsDir: DocsDir;\n featureDir: FeatureDir;\n /** The feature's id — the tag that selects the delta's own additions. */\n featureId: string;\n /** The feature's own documents, staged LAST so they win over a living twin. */\n documents: readonly StagedDocument[];\n /**\n * The caller's memoised LikeC4 read, when it has one. Only `delta.likec4`\n * goes through it, and only to compute the merge preview — but a caller\n * inside `featureCoherence` has already loaded that exact document, and\n * loading one spins a fresh Langium workspace, which\n * `core/coherence/coherence.ts` calls the dominant per-feature cost.\n */\n load?: (path: string) => Promise<LoadedDoc>;\n /**\n * The fleet's extending models — the SAME list `loam archive` merges with.\n * The merge routes a nested addition into the model that owns that service's\n * interior, so a preview computed without this list holds interior the\n * archive will put somewhere else, and every gate resting on the preview\n * grades a map that will never exist (verification 2026-09-04, review C).\n *\n * Optional because it must be INJECTED rather than read here: the shape scan\n * that decides which models extend the map lives in\n * `../service-model/shape.ts`, and that package imports this one — reading it\n * from here would be a package cycle. The reader is\n * `../service-model/fleet/extending.ts`, one level below that scan, and both\n * shipped callers fill this in from it: `core/delta/delta.ts` for the\n * use-case overlay, `core/coherence/coherence.ts` for the deployment one.\n * Omitted, the preview is the pre-routing one — the behaviour every caller\n * had before this field existed, and what a fleet whose models all stand\n * alone gets either way.\n */\n models?: readonly ExtendingModel[];\n}\n\n/** The staged parse, or the honest refusal to say. */\nexport type StagedProject =\n | {\n kind: \"read\";\n /** The whole staged project, parsed. */\n doc: LoadedDoc;\n /** A path LikeC4 reported, spelled as the author wrote it. */\n real: (path: string) => string;\n /** The staged rels that are THIS feature's documents — everything else is living. */\n mine: ReadonlySet<string>;\n }\n | { kind: \"unreadable\"; errors: string[] };\n\n/**\n * Stage the living `architecture/` with the merge preview in place of the\n * landscape, add the feature's own documents, and parse the result.\n *\n * A feature bringing no documents is not this function's business: both callers\n * return their own empty answer before reaching it, at the cost of one walk\n * over a directory that is not there. That walk is the axis's per-feature\n * opt-in and the whole price a fleet that has not adopted it pays.\n */\nexport async function stageMergedProject(req: StagedProjectRequest): Promise<StagedProject> {\n const merged = await mergedLandscape(req);\n if (merged.kind === \"failed\") return { kind: \"unreadable\", errors: [merged.reason] };\n\n const arch = architectureDir(req.docsDir);\n // BOTH generated documents come out, through the one list every project read\n // shares: a views-only file does not parse standalone, so staging one that\n // `sync` has not caught up with would blank the merge preview this whole\n // module exists to parse (`./architecture.ts` states the rule).\n const living = await architectureDocuments(arch, generatedArchitectureDocuments(req.docsDir));\n const root = await mkdtemp(join(tmpdir(), \"loam-staged-\"));\n try {\n // Staged rel -> the path the AUTHOR wrote, so every message that leaves\n // here names a file they can open. Built as the tree is staged, because\n // that is the one moment both spellings are in hand.\n const authored = new Map<string, string>();\n const landscape = resolve(landscapePath(req.docsDir));\n await inOrder(living, async (path) => {\n // The living landscape is staged from the merge preview when there is\n // one; copying it as well would declare every element twice.\n if (merged.kind === \"merged\" && resolve(path) === landscape) return null;\n await stage(root, spell(relative(arch, path)), path, authored);\n return null;\n });\n if (merged.kind === \"merged\") {\n const rel = spell(relative(arch, landscape));\n await write(root, rel, merged.content);\n authored.set(rel, landscape);\n }\n // The feature's own documents LAST, so one whose living twin exists wins\n // the staging rather than colliding with it. That case is already an error\n // the gate refuses before any merge — `usecase.flow-exists`,\n // `deployment.doc-exists` — and grading the feature's version is the more\n // useful of the two answers to give an author who is about to be told to\n // delete the file.\n const mine = new Set<string>();\n for (const document of req.documents) {\n await stage(root, document.rel, document.path, authored);\n mine.add(document.rel);\n }\n const doc = asLoadedDoc(await loadProject(root, await architectureDocuments(root)));\n // TWO SPELLINGS REACH HERE and both must map, which is the one thing this\n // helper cannot be allowed to get half right. A parse error's\n // `sourceFsPath` is an ABSOLUTE path into the temp tree; a view's\n // `sourcePath` is already the project-RELATIVE rel LikeC4 reports. The\n // rel-first lookup answers the second, and the relative() computation the\n // first. Handling only one leaves the other silently returning the temp\n // spelling, which is a finding naming a directory that no longer exists.\n const real = (path: string): string =>\n authored.get(spell(path)) ?? authored.get(spell(relative(root, path))) ?? path;\n if (doc.errors.length > 0) {\n // An error naming no document is LikeC4 declining to attribute one. It\n // still travels: a hole loam cannot place is still a hole, and dropping\n // it here would grade the feature clean on the strength of a missing\n // field.\n //\n // DOCS-RELATIVE, `/`-separated, and carrying the line — the spelling the\n // SERVICE arm of `usecase.flow-invalid` uses on purpose. These strings are\n // the feature arm's whole payload (text, `--json` and the `loam archive`\n // block message), and `real()` alone answered with an absolute Windows path\n // and no line at all: `C:\\Users\\…\\docs\\features\\FEAT-1\\usecases\\x.likec4:\n // <message>` in a finding a person is asked to act on (verification\n // 2026-09-04, W7). A path outside the docs root — which nothing here\n // produces, but `relative` would spell `..\\..` — keeps its absolute form.\n return {\n kind: \"unreadable\",\n errors: doc.errors.map((e) =>\n e.sourceFsPath === undefined ? errorText(e) : `${spellReal(req.docsDir, real(e.sourceFsPath))}: ${errorText(e)}`,\n ),\n };\n }\n return { kind: \"read\", doc, real, mine };\n } catch (err) {\n // The `unreadable` arm is a PROMISE, not a description of the happy path.\n // Two things under here throw rather than return: `loadProject` refuses an\n // error it cannot attribute to a document, and every `mkdir`/`copyFile` in\n // the staging above can fail on a full disk or a denied temp directory.\n // Callers run inside `loam validate --feature` and the archive gate — and\n // the one behaviour a check added to those may never have is failing the\n // run it was added to. So a throw becomes the same could-not-look answer a\n // parse error already produces, which each caller turns into its own\n // refusal that writes nothing.\n return { kind: \"unreadable\", errors: [err instanceof Error ? err.message : String(err)] };\n } finally {\n await rm(root, { recursive: true, force: true });\n }\n}\n\n/** What the landscape looks like after this feature's C4 delta is spliced in. */\ntype MergedLandscape =\n /** The delta adds nothing, or there is none — the living landscape stands. */\n | { kind: \"living\" }\n /** The merge preview, as authored bytes. */\n | { kind: \"merged\"; content: string }\n /** The merge could not be computed; `planLandscape` owns the refusal and says why. */\n | { kind: \"failed\"; reason: string };\n\n/**\n * The merge preview, computed with the SAME function `loam archive` commits\n * with.\n *\n * Reusing `planLandscapeMerge` rather than approximating it is what makes this\n * an answer about the archive rather than a second opinion beside it: an\n * element placed differently, a tag stripped differently, or a splice refused\n * there and allowed here would be a document graded against a map the merge\n * never writes.\n *\n * THE PARITY IS THE MERGE, NOT THE WHOLE GATE, and the two places the sides\n * differ are worth naming rather than leaving for a reader to discover.\n *\n * The first changes no verdict. `planLandscape` runs one check BEFORE it\n * computes the merge and this does not: a delta declaring the feature tag on a\n * specification KIND is refused there, because every element of that kind\n * inherits the tag and the merge would splice the whole document. Such a delta\n * reaches `newEls` here unfiltered, so the preview this grades against is one\n * the archive would never write — but that archive refuses either way, before\n * anything is written.\n *\n * The second changed every verdict about a fleet with extending models, and is\n * why `models` is on the request at all. `planLandscape` names the fleet's\n * extending models, so a nested addition is ROUTED into the model that owns\n * that service's interior and never reaches the map; a request that omits them\n * previews a map holding interior the archive will put somewhere else. A\n * feature-local `usecases/<x>.likec4` whose hop names `<service-fqn>.<container>`\n * then resolved in the preview, passed `usecase.flow-invalid` — the gate whose\n * own message is \"the archive would land an architecture/ nothing has checked\",\n * and which `--approve` cannot override — and was copied verbatim into\n * `architecture/`, where it resolves against nothing and takes the whole fleet\n * map down (verification 2026-09-04, review C; SCHEMA names that outcome as the\n * thing that must not happen). A caller that leaves `models` out is asking for\n * the pre-routing preview, and gets exactly the answers it got before.\n *\n * A throw is `failed` rather than propagated. Every `LandscapeSpliceError` this\n * can raise is one `planLandscape` raises again — with its own message, its own\n * refusal code, and before anything is written — so throwing here would only\n * decide which of two identical refusals an author reads first.\n */\nasync function mergedLandscape(req: StagedProjectRequest): Promise<MergedLandscape> {\n const deltaPath = featurePaths(req.featureDir).delta;\n const landscape = landscapePath(req.docsDir);\n if (!existsSync(deltaPath) || !existsSync(landscape)) return { kind: \"living\" };\n const delta = await (req.load ?? loadFile)(deltaPath);\n if (delta.errors.length > 0) {\n return {\n kind: \"failed\",\n reason: `delta.likec4 has ${delta.errors.length} parse error(s) — a feature's own documents cannot be graded against a map that does not parse`,\n };\n }\n // The models the archive will CREATE route exactly as the living ones do —\n // a new service's interior lands in its new model, never on the map — so a\n // preview asking for the routing gets them too (`createdServiceModels` says why).\n const models =\n req.models === undefined\n ? undefined\n : [\n ...req.models,\n ...(await createdServiceModels({\n docsDir: req.docsDir,\n featureDir: req.featureDir,\n featureId: req.featureId,\n drawn: [...(await (req.load ?? loadFile)(landscape)).elements, ...delta.elements.filter((e) => e.tags.includes(req.featureId))],\n })),\n ];\n try {\n const plan = await planLandscapeMerge({\n landscapeText: await text(landscape),\n deltaText: await text(deltaPath),\n deltaElements: delta.elements,\n newEls: delta.elements.filter((e) => e.tags.includes(req.featureId)),\n newRels: delta.relationships.filter((r) => r.tags.includes(req.featureId)),\n featureId: req.featureId,\n // As `planLandscape` passes it: without the table every edge of a tagged\n // kind is a statement the splice cannot locate, and the preview fails a\n // feature's flows over a merge the archive itself computes.\n deltaKindTags: delta.specification?.relationshipKindTags ?? {},\n ...(models === undefined ? {} : { models }),\n });\n return plan.content === null ? { kind: \"living\" } : { kind: \"merged\", content: plan.content };\n } catch (err) {\n return { kind: \"failed\", reason: err instanceof Error ? err.message : String(err) };\n }\n}\n\n/** Read a document loam is about to hand LikeC4, refusing the encodings that decode as empty. */\nasync function text(path: string): Promise<string> {\n return decodeDocument(await readFile(path), path);\n}\n\n/** A path spelled the way a staged rel is spelled — `/`-separated, on every platform. */\nfunction spell(path: string): string {\n return path.split(/[\\\\/]/).join(\"/\");\n}\n\n/**\n * An authored path as a FINDING spells it: relative to the docs root,\n * `/`-separated. A path that does not sit under the docs root keeps its absolute\n * form — `relative` would answer with `..` segments, and a finding naming a\n * document outside the repository is more honest as the absolute path it is.\n */\nfunction spellReal(docsDir: DocsDir, path: string): string {\n const rel = relative(docsDir, path);\n return rel.length > 0 && !rel.startsWith(\"..\") ? spell(rel) : spell(path);\n}\n\n/** Copy one authored document into the staged tree, remembering where it came from. */\nasync function stage(root: string, rel: string, path: string, authored: Map<string, string>): Promise<void> {\n const dest = join(root, ...rel.split(\"/\"));\n await mkdir(dirname(dest), { recursive: true });\n await copyFile(path, dest);\n authored.set(rel, resolve(path));\n}\n\n/** Write one document into the staged tree from bytes loam holds rather than a file. */\nasync function write(root: string, rel: string, content: string): Promise<void> {\n const dest = join(root, ...rel.split(\"/\"));\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, content, \"utf8\");\n}\n"]}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attested.js","sourceRoot":"","sources":["../../../../src/core/c4/resolve/attested.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * The calls a service's OWN `model.likec4` attests across its boundary.\r\n *\r\n * Two readers derive this list and must derive the same one: the adopt brief\r\n * (core/brief/landscape.ts reads it; core/brief/map/owed.ts names these calls\r\n * as the edges the fleet map still owes a drawn-but-edgeless service), and the fleet check\r\n * `landscape.service-isolated` (commands/validate/fleet/map/isolation.ts),\r\n * whose whole evidence gate is \"this list is non-empty\". A second spelling of\r\n * \"which relationships cross the boundary\" is how the brief would name an edge\r\n * the check then never asks for, or the reverse.\r\n *\r\n * It sits in this package because it is a question about the RESOLVED model —\r\n * which endpoints belong to the service — and `serviceResolver` beside it is\r\n * the one answer every join in loam gives to that. A relationship whose two\r\n * endpoints both resolve to the service (`api -> db`) is the service's own\r\n * internals; one where neither does (two context elements) is somebody else's\r\n * call. Only a relationship with EXACTLY one endpoint inside the boundary is\r\n * attested here, collapsed to the service side.\r\n *\r\n * What is deliberately NOT done: matching the counterpart to a landscape\r\n * element. One word names two different things in two documents often enough\r\n * — the reporting fleet's `orchestrator` was a container in the service model\r\n * and a campaign orchestrator on the map — that the join is the agent's to\r\n * make by hand. The counterpart is handed over exactly as the model spells\r\n * it, plus its title where the model declares the element.\r\n */\r\nimport { type Elem, type Rel } from \"../likec4.js\";\r\nimport { serviceResolver } from \"./service.js\";\r\n\r\nexport interface AttestedCall {\r\n /** `out` when the service is the source; `in` when it is the target. */\r\n direction: \"out\" | \"in\";\r\n /** The foreign endpoint's id as model.likec4 spells it — never matched to a landscape element. */\r\n counterpartId: string;\r\n /** The foreign element's title when the model declares it; the id otherwise. */\r\n counterpart: string;\r\n title?: string;\r\n op?: string;\r\n publishes?: string;\r\n consumes?: string;\r\n}\r\n\r\n/** The slice of a loaded document this derivation reads. */\r\nexport interface AttestingModel {\r\n elements: Elem[];\r\n relationships: Rel[];\r\n}\r\n\r\n/**\r\n * Every call the model declares with exactly one endpoint resolving to\r\n * `service`, de-duplicated and sorted.\r\n *\r\n * `known` is the enumerated fleet, for the reason every resolver call carries\r\n * it: a container of this service resolves to the service, and a stand-in\r\n * element titled like another REAL service resolves to that service rather\r\n * than to itself. The dedupe key is (direction, counterpart, op, publishes,\r\n * consumes) so two containers calling one system's one operation are one\r\n * attested call — the map draws edges between services, and that is one edge.\r\n */\r\nexport function attestedCalls(model: AttestingModel, service: string, known: ReadonlySet<string>): AttestedCall[] {\r\n const resolve = serviceResolver(model.elements, known);\r\n const byId = new Map(model.elements.map((e) => [e.id, e]));\r\n const out = new Map<string, AttestedCall>();\r\n for (const r of model.relationships) {\r\n const from = resolve(r.source) === service;\r\n const to = resolve(r.target) === service;\r\n if (from === to) continue;\r\n const foreign = from ? r.target : r.source;\r\n const call: AttestedCall = {\r\n direction: from ? \"out\" : \"in\",\r\n counterpartId: foreign,\r\n counterpart: byId.get(foreign)?.title ?? foreign,\r\n ...(r.title === undefined ? {} : { title: r.title }),\r\n ...(r.op === undefined ? {} : { op: r.op }),\r\n ...(r.publishes === undefined ? {} : { publishes: r.publishes }),\r\n ...(r.consumes === undefined ? {} : { consumes: r.consumes }),\r\n };\r\n const key = [call.direction, foreign, r.op ?? \"\", r.publishes ?? \"\", r.consumes ?? \"\"].join(\"\u0000\");\r\n if (!out.has(key)) out.set(key, call);\r\n }\r\n // Keyed sort, so the order is a function of the calls and never of the\r\n // order the author wrote them in — the brief's text and the finding's\r\n // message are compared byte for byte by tests and by readers.\r\n return [...out.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([, call]) => call);\r\n}\r\n\r\n/**\r\n * One call as prose: `-> stripe (op 'authorize')`, `<- kafka (consumes\r\n * 'order.placed')`, `-> ledger` when the edge carries no join. Shared by the\r\n * brief's instruction and the fleet finding's message so the two surfaces\r\n * spell the same call the same way — a reader who meets it in `adopt` and\r\n * again in `validate --all` must recognise it as one thing.\r\n */\r\nexport function spellCall(call: AttestedCall): string {\r\n const arrow = call.direction === \"out\" ? \"->\" : \"<-\";\r\n const join =\r\n call.op !== undefined\r\n ? ` (op '${call.op}')`\r\n : call.publishes !== undefined\r\n ? ` (publishes '${call.publishes}')`\r\n : call.consumes !== undefined\r\n ? ` (consumes '${call.consumes}')`\r\n : \"\";\r\n return `${arrow} ${call.counterpart}${join}`;\r\n}\r\n\r\n/**\r\n * The edge form(s) the map owes for these calls, on the element that already\r\n * resolves to the service: `<id> -> <callee> …` when the service is the\r\n * caller, `<caller> -> <id> …` when it is the callee, both when the calls run\r\n * both ways. Direction is the part a template gets wrong silently: the repair\r\n * sentence used to spell the outbound form alone, so an agent following it\r\n * for an inbound call drew the dependency backwards — and because `op` must\r\n * be an operationId the TARGET's openapi.yaml defines, the reversed edge then\r\n * earned `spine.op-undefined` against the wrong service. Shared by the\r\n * brief's instruction and the fleet finding's message for the reason\r\n * `spellCall` is. With no call at all the outbound form stands in, so a\r\n * caller never receives an empty list.\r\n */\r\nexport function edgeTemplates(id: string, calls: readonly AttestedCall[]): string[] {\r\n const outbound = calls.some((c) => c.direction === \"out\");\r\n const inbound = calls.some((c) => c.direction === \"in\");\r\n const forms: string[] = [];\r\n if (outbound || !inbound) forms.push(`${id} -> <callee> 'Calls <op>' { metadata { op '<op>' } }`);\r\n if (inbound) forms.push(`<caller> -> ${id} 'Calls <op>' { metadata { op '<op>' } }`);\r\n return forms;\r\n}\r\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The producer OUTSIDE the fleet: the `#external` element the map says sends a
|
|
3
|
+
* message no `services/<id>/` directory stands for.
|
|
4
|
+
*
|
|
5
|
+
* It sits in this package for `./attested.ts`'s reason: it is a question about
|
|
6
|
+
* the RESOLVED model — which endpoint of a `publishes` edge belongs to no
|
|
7
|
+
* service — and `serviceResolver` beside it is the one answer every join gives
|
|
8
|
+
* to that. It moved here from `commands/validate/checks/fleet-shape.ts`, where it
|
|
9
|
+
* sat beside the other checks that read tags off the drawn map, when a second
|
|
10
|
+
* reader needed the same answer: the consumer index (`core/consumers/`), which a
|
|
11
|
+
* `commands/` module can never be imported by.
|
|
12
|
+
*/
|
|
13
|
+
import {} from "../likec4.js";
|
|
14
|
+
import {} from "../../kernel/ids/service.js";
|
|
15
|
+
import { EXTERNAL_TAG } from "../../vocabulary/maturity.js";
|
|
16
|
+
/**
|
|
17
|
+
* The #external element that declares it PUBLISHES this message, or null —
|
|
18
|
+
* the landscape-side half of the event spine's fleet question, whose fleet
|
|
19
|
+
* half is `../../asyncapi/producers.ts` (`spine.message-unproduced`, in
|
|
20
|
+
* `commands/validate/service/events/message-producers.ts`).
|
|
21
|
+
*
|
|
22
|
+
* Positive evidence only: an edge carrying `metadata { publishes '<msg>' }`
|
|
23
|
+
* whose source element — itself or a declared ancestor, since the edge may
|
|
24
|
+
* point out of a topic nested inside the broker — is tagged #external and
|
|
25
|
+
* resolves to no known service directory. The resolution guard is what keeps
|
|
26
|
+
* a mis-tagged internal service reading as unproduced: a tag is cheap to
|
|
27
|
+
* write, and a services/<id>/ directory outranks it.
|
|
28
|
+
*/
|
|
29
|
+
export function externalProducerOf(message, land, landSvcOf, known) {
|
|
30
|
+
if (land === null || land.errors.length > 0 || landSvcOf === null)
|
|
31
|
+
return null;
|
|
32
|
+
const knownIds = known;
|
|
33
|
+
const byId = new Map(land.elements.map((e) => [e.id, e]));
|
|
34
|
+
for (const r of land.relationships) {
|
|
35
|
+
if (r.publishes !== message)
|
|
36
|
+
continue;
|
|
37
|
+
if (knownIds.has(landSvcOf(r.source)))
|
|
38
|
+
continue;
|
|
39
|
+
for (let id = r.source;;) {
|
|
40
|
+
const e = byId.get(id);
|
|
41
|
+
if (e !== undefined && e.tags.includes(EXTERNAL_TAG))
|
|
42
|
+
return e.title;
|
|
43
|
+
const dot = id.lastIndexOf(".");
|
|
44
|
+
if (dot === -1)
|
|
45
|
+
break;
|
|
46
|
+
id = id.slice(0, dot);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=external.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external.js","sourceRoot":"","sources":["../../../../src/core/c4/resolve/external.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAkB,MAAM,cAAc,CAAC;AAC9C,OAAO,EAA2C,MAAM,6BAA6B,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,IAAsB,EACtB,SAAmD,EACnD,KAAgC;IAEhC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/E,MAAM,QAAQ,GAAwB,KAAK,CAAC;IAC5C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,SAAS,KAAK,OAAO;YAAE,SAAS;QACtC,IAAI,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAAE,SAAS;QAChD,KAAK,IAAI,EAAE,GAAG,CAAC,CAAC,MAAM,IAAM,CAAC;YAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvB,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;gBAAE,OAAO,CAAC,CAAC,KAAK,CAAC;YACrE,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,MAAM;YACtB,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/**\n * The producer OUTSIDE the fleet: the `#external` element the map says sends a\n * message no `services/<id>/` directory stands for.\n *\n * It sits in this package for `./attested.ts`'s reason: it is a question about\n * the RESOLVED model — which endpoint of a `publishes` edge belongs to no\n * service — and `serviceResolver` beside it is the one answer every join gives\n * to that. It moved here from `commands/validate/checks/fleet-shape.ts`, where it\n * sat beside the other checks that read tags off the drawn map, when a second\n * reader needed the same answer: the consumer index (`core/consumers/`), which a\n * `commands/` module can never be imported by.\n */\nimport { type LoadedDoc } from \"../likec4.js\";\nimport { type DeclaredService, type RawServiceId } from \"../../kernel/ids/service.js\";\nimport { EXTERNAL_TAG } from \"../../vocabulary/maturity.js\";\n\n/**\n * The #external element that declares it PUBLISHES this message, or null —\n * the landscape-side half of the event spine's fleet question, whose fleet\n * half is `../../asyncapi/producers.ts` (`spine.message-unproduced`, in\n * `commands/validate/service/events/message-producers.ts`).\n *\n * Positive evidence only: an edge carrying `metadata { publishes '<msg>' }`\n * whose source element — itself or a declared ancestor, since the edge may\n * point out of a topic nested inside the broker — is tagged #external and\n * resolves to no known service directory. The resolution guard is what keeps\n * a mis-tagged internal service reading as unproduced: a tag is cheap to\n * write, and a services/<id>/ directory outranks it.\n */\nexport function externalProducerOf(\n message: string,\n land: LoadedDoc | null,\n landSvcOf: ((id: string) => DeclaredService) | null,\n known: ReadonlySet<RawServiceId>,\n): string | null {\n if (land === null || land.errors.length > 0 || landSvcOf === null) return null;\n const knownIds: ReadonlySet<string> = known;\n const byId = new Map(land.elements.map((e) => [e.id, e]));\n for (const r of land.relationships) {\n if (r.publishes !== message) continue;\n if (knownIds.has(landSvcOf(r.source))) continue;\n for (let id = r.source; ; ) {\n const e = byId.get(id);\n if (e !== undefined && e.tags.includes(EXTERNAL_TAG)) return e.title;\n const dot = id.lastIndexOf(\".\");\n if (dot === -1) break;\n id = id.slice(0, dot);\n }\n }\n return null;\n}\n"]}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* The resolution order below is the whole of it, and its last rung is the one
|
|
15
15
|
* that lies: read that comment before adding a caller.
|
|
16
16
|
*/
|
|
17
|
+
import { ancestorIds, properAncestorIds } from "../../kernel/ids/fqn/ancestors.js";
|
|
17
18
|
import { declaredService } from "../../kernel/ids/service.js";
|
|
18
19
|
import {} from "../likec4.js";
|
|
19
20
|
/**
|
|
@@ -28,24 +29,14 @@ import {} from "../likec4.js";
|
|
|
28
29
|
export function elementService(e) {
|
|
29
30
|
return e.service ?? declaredService(e.title);
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* what made those edges invisible: the spine check silently skipped them, and
|
|
39
|
-
* the no-openapi grace treated a service with a dozen inbound container calls as
|
|
40
|
-
* one nobody calls at all.
|
|
32
|
+
/*
|
|
33
|
+
* `ancestorIds` — every id an endpoint may be filed under, nearest first:
|
|
34
|
+
* `a.b.c`, `a.b`, `a` — now lives in `core/kernel/ids/fqn/ancestors.ts`, with
|
|
35
|
+
* the reason it exists at all (a landscape may draw a service's containers, and
|
|
36
|
+
* an edge into one is still an edge into the service). It moved because five
|
|
37
|
+
* modules were each walking that chain privately; the kernel is where a leaf
|
|
38
|
+
* helper goes when the modules that want it are heavier than it is.
|
|
41
39
|
*/
|
|
42
|
-
function ancestorIds(id) {
|
|
43
|
-
const out = [id];
|
|
44
|
-
for (let dot = id.lastIndexOf("."); dot !== -1; dot = id.lastIndexOf(".", dot - 1)) {
|
|
45
|
-
out.push(id.slice(0, dot));
|
|
46
|
-
}
|
|
47
|
-
return out;
|
|
48
|
-
}
|
|
49
40
|
/**
|
|
50
41
|
* A memoized `id -> service` resolver over one document.
|
|
51
42
|
*
|
|
@@ -110,4 +101,74 @@ export function serviceResolver(elements, known) {
|
|
|
110
101
|
export function serviceOf(elements, id, known) {
|
|
111
102
|
return serviceResolver(elements, known)(id);
|
|
112
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* One `id -> service` resolver per document, not one per lookup.
|
|
106
|
+
*
|
|
107
|
+
* `serviceOf` builds a fresh id map on every call, and this axis calls it twice
|
|
108
|
+
* for every relationship in scope — inside loops that already run over every
|
|
109
|
+
* relationship a service can see. `serviceResolver`'s own doc says it is "built
|
|
110
|
+
* once per document and shared"; this is where both axes that need it keep
|
|
111
|
+
* that promise — `../arch.ts`'s `coversEdge` and `./steps.ts`'s `resolvedTier`,
|
|
112
|
+
* each exported and each handed only the element array.
|
|
113
|
+
*
|
|
114
|
+
* A module-level cache is normally the wrong shape here, because a command runs
|
|
115
|
+
* against whatever directory it was invoked in. This one is safe: the key is the
|
|
116
|
+
* per-invocation `Elem[]` a parse produced, its value is a pure function of that
|
|
117
|
+
* key alone, and nothing in it is derived from the working directory — so two
|
|
118
|
+
* runs can never see each other's answer, and the entry dies with the document.
|
|
119
|
+
* Nothing mutates an `Elem[]` after parse.
|
|
120
|
+
*
|
|
121
|
+
* Two tiers because the fleet set is part of the answer: the same document
|
|
122
|
+
* resolves differently with and without `known`, so a cache keyed on the
|
|
123
|
+
* elements alone handed a with-fleet caller the without-fleet resolver.
|
|
124
|
+
* Callers build ONE set per run and pass the same instance through, which is
|
|
125
|
+
* what lets the inner WeakMap key on the set's identity.
|
|
126
|
+
*/
|
|
127
|
+
const RESOLVERS = new WeakMap();
|
|
128
|
+
const FLEET_RESOLVERS = new WeakMap();
|
|
129
|
+
export function resolverFor(elements, known) {
|
|
130
|
+
if (known === undefined) {
|
|
131
|
+
const cached = RESOLVERS.get(elements);
|
|
132
|
+
if (cached !== undefined)
|
|
133
|
+
return cached;
|
|
134
|
+
const resolver = serviceResolver(elements);
|
|
135
|
+
RESOLVERS.set(elements, resolver);
|
|
136
|
+
return resolver;
|
|
137
|
+
}
|
|
138
|
+
let perSet = FLEET_RESOLVERS.get(elements);
|
|
139
|
+
if (perSet === undefined) {
|
|
140
|
+
perSet = new WeakMap();
|
|
141
|
+
FLEET_RESOLVERS.set(elements, perSet);
|
|
142
|
+
}
|
|
143
|
+
const cached = perSet.get(known);
|
|
144
|
+
if (cached !== undefined)
|
|
145
|
+
return cached;
|
|
146
|
+
const resolver = serviceResolver(elements, known);
|
|
147
|
+
perSet.set(known, resolver);
|
|
148
|
+
return resolver;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The element each service is DRAWN as: for every service the map resolves an
|
|
152
|
+
* element to, the first service-level one in document order — an element that
|
|
153
|
+
* resolves to the service while an ancestor does too is a container of it,
|
|
154
|
+
* never a second box. The same filter and the same "first one" the adopt brief
|
|
155
|
+
* names (`core/brief/landscape.ts`), so `loam new` and `loam adopt` can never
|
|
156
|
+
* hand an author two different ids for one box.
|
|
157
|
+
*
|
|
158
|
+
* Exists so a scaffold reuses the id the map already spells instead of
|
|
159
|
+
* guessing one: a feature delta whose context ids differ from the landscape's
|
|
160
|
+
* does not line up at archive, and a guessed camelCase id on a seeded map
|
|
161
|
+
* (`svc_<id>`) never matched once.
|
|
162
|
+
*/
|
|
163
|
+
export function drawnServiceElements(elements, known) {
|
|
164
|
+
const svcOf = serviceResolver(elements, known);
|
|
165
|
+
const drawn = new Map();
|
|
166
|
+
for (const e of elements) {
|
|
167
|
+
const service = svcOf(e.id);
|
|
168
|
+
if (drawn.has(service) || properAncestorIds(e.id).some((a) => svcOf(a) === service))
|
|
169
|
+
continue;
|
|
170
|
+
drawn.set(service, e.id);
|
|
171
|
+
}
|
|
172
|
+
return drawn;
|
|
173
|
+
}
|
|
113
174
|
//# sourceMappingURL=service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../src/core/c4/resolve/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,eAAe,EAAwB,MAAM,6BAA6B,CAAC;AACpF,OAAO,EAAa,MAAM,cAAc,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,CAAO;IACpC,OAAO,CAAC,CAAC,OAAO,IAAI,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,WAAW,CAAC,EAAU;IAC7B,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;IACjB,KAAK,IAAI,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;QACnF,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,KAA2B;IAE3B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,IAAI,GAAG,EAA2B,CAAC;IAChD,OAAO,CAAC,EAAU,EAAmB,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,GAAG,CAAC;QAClC,IAAI,MAAmC,CAAC;QACxC,KAAK,MAAM,SAAS,IAAI,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC9B,IAAI,CAAC,EAAE,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC7B,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC;gBACnB,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAChD,KAAK,MAAM,SAAS,IAAI,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;gBACxC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAC9B,IAAI,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC1C,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;oBAClC,MAAM;gBACR,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC1B,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACrB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,EAAU,EAAE,KAA2B;IACjF,OAAO,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;AAC9C,CAAC","sourcesContent":["/**\n * Which `services/<id>/` directory a modelled element belongs to.\n *\n * A distinct subject from loading a document, and a distinct phase: the\n * loader beside this package (`../likec4.ts`) turns LikeC4's parse output into\n * loam's neutral `Elem`/`Rel` records, and everything here runs AFTER that, over\n * those records, answering one question the model itself never states outright.\n * It is the join every fleet-wide check rests on — the landscape↔services\n * reconciliation, the spine's edge grouping, `Covers:` resolution, health.yaml's\n * dependency matching — which is why `docs/DESIGN.md`'s Open-decisions table\n * audits its call sites as a subject of their own rather than as a detail of the\n * loader.\n *\n * The resolution order below is the whole of it, and its last rung is the one\n * that lies: read that comment before adding a caller.\n */\nimport { declaredService, type DeclaredService } from \"../../kernel/ids/service.js\";\nimport { type Elem } from \"../likec4.js\";\n\n/**\n * The service directory an element stands for: an explicit\n * `metadata { service '<id>' }` binding wins, the title is the fallback.\n *\n * The fallback is what every docs repo written before the binding existed relies\n * on, and it is also the trap the binding exists to close: matching on the title\n * means renaming a box in a diagram silently unlinks it from its service, and\n * every check that joined the two just stops finding anything.\n */\nexport function elementService(e: Elem): DeclaredService {\n return e.service ?? declaredService(e.title);\n}\n\n/**\n * Every id an endpoint may be filed under, nearest first: `a.b.c`, `a.b`, `a`.\n *\n * A landscape does not have to draw a service as one opaque box. The moment\n * somebody models its containers — `paymentService.api`, `paymentService.worker`\n * — an edge drawn INTO a container is still an edge into the service, and every\n * join that groups by service has to know that. Resolving only the exact id is\n * what made those edges invisible: the spine check silently skipped them, and\n * the no-openapi grace treated a service with a dozen inbound container calls as\n * one nobody calls at all.\n */\nfunction ancestorIds(id: string): string[] {\n const out = [id];\n for (let dot = id.lastIndexOf(\".\"); dot !== -1; dot = id.lastIndexOf(\".\", dot - 1)) {\n out.push(id.slice(0, dot));\n }\n return out;\n}\n\n/**\n * A memoized `id -> service` resolver over one document.\n *\n * Resolution order, and why it is this order:\n *\n * 1. the nearest ancestor (the id itself first) carrying an explicit\n * `metadata { service '<id>' }` — a binding is a claim somebody wrote down,\n * so it outranks every guess;\n * 2. the nearest ancestor whose title names a REAL `services/<id>/` directory —\n * positive evidence from the filesystem, available only to callers that\n * hand in `known`;\n * 3. the element's own title, and finally the raw id — today's fallback, kept\n * LAST because it is the one that lies: it happily resolves\n * `paymentService.api` to \"api\", a service that has never existed.\n *\n * Built once per document and shared, because it is called inside loops over\n * every relationship and the walk up the ancestor chain is not free.\n */\nexport function serviceResolver(\n elements: Elem[],\n known?: ReadonlySet<string>,\n): (id: string) => DeclaredService {\n const byId = new Map(elements.map((e) => [e.id, e]));\n const memo = new Map<string, DeclaredService>();\n return (id: string): DeclaredService => {\n const hit = memo.get(id);\n if (hit !== undefined) return hit;\n let answer: DeclaredService | undefined;\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e?.service !== undefined) {\n answer = e.service;\n break;\n }\n }\n if (answer === undefined && known !== undefined) {\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e !== undefined && known.has(e.title)) {\n answer = declaredService(e.title);\n break;\n }\n }\n }\n if (answer === undefined) {\n const self = byId.get(id);\n answer = self ? elementService(self) : declaredService(id);\n }\n memo.set(id, answer);\n return answer;\n };\n}\n\n/**\n * The service a relationship endpoint belongs to. An id that names no element\n * resolves to itself, so a partial document degrades to the id rather than\n * throwing.\n *\n * `known` is the set of service directories that actually exist; pass it\n * wherever the caller has enumerated `services/`, so an edge into a modelled\n * container resolves to the service that owns it instead of to the container's\n * own title. Callers with a single document and no repository in hand omit it\n * and keep the pre-existing behaviour.\n */\nexport function serviceOf(elements: Elem[], id: string, known?: ReadonlySet<string>): DeclaredService {\n return serviceResolver(elements, known)(id);\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../src/core/c4/resolve/service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which `services/<id>/` directory a modelled element belongs to.\n *\n * A distinct subject from loading a document, and a distinct phase: the\n * loader beside this package (`../likec4.ts`) turns LikeC4's parse output into\n * loam's neutral `Elem`/`Rel` records, and everything here runs AFTER that, over\n * those records, answering one question the model itself never states outright.\n * It is the join every fleet-wide check rests on — the landscape↔services\n * reconciliation, the spine's edge grouping, `Covers:` resolution, health.yaml's\n * dependency matching — which is why `docs/DESIGN.md`'s Open-decisions table\n * audits its call sites as a subject of their own rather than as a detail of the\n * loader.\n *\n * The resolution order below is the whole of it, and its last rung is the one\n * that lies: read that comment before adding a caller.\n */\nimport { ancestorIds, properAncestorIds } from \"../../kernel/ids/fqn/ancestors.js\";\nimport { declaredService, type DeclaredService } from \"../../kernel/ids/service.js\";\nimport { type Elem } from \"../likec4.js\";\n\n/**\n * The service directory an element stands for: an explicit\n * `metadata { service '<id>' }` binding wins, the title is the fallback.\n *\n * The fallback is what every docs repo written before the binding existed relies\n * on, and it is also the trap the binding exists to close: matching on the title\n * means renaming a box in a diagram silently unlinks it from its service, and\n * every check that joined the two just stops finding anything.\n */\nexport function elementService(e: Elem): DeclaredService {\n return e.service ?? declaredService(e.title);\n}\n\n/*\n * `ancestorIds` — every id an endpoint may be filed under, nearest first:\n * `a.b.c`, `a.b`, `a` — now lives in `core/kernel/ids/fqn/ancestors.ts`, with\n * the reason it exists at all (a landscape may draw a service's containers, and\n * an edge into one is still an edge into the service). It moved because five\n * modules were each walking that chain privately; the kernel is where a leaf\n * helper goes when the modules that want it are heavier than it is.\n */\n\n/**\n * A memoized `id -> service` resolver over one document.\n *\n * Resolution order, and why it is this order:\n *\n * 1. the nearest ancestor (the id itself first) carrying an explicit\n * `metadata { service '<id>' }` — a binding is a claim somebody wrote down,\n * so it outranks every guess;\n * 2. the nearest ancestor whose title names a REAL `services/<id>/` directory —\n * positive evidence from the filesystem, available only to callers that\n * hand in `known`;\n * 3. the element's own title, and finally the raw id — today's fallback, kept\n * LAST because it is the one that lies: it happily resolves\n * `paymentService.api` to \"api\", a service that has never existed.\n *\n * Built once per document and shared, because it is called inside loops over\n * every relationship and the walk up the ancestor chain is not free.\n */\nexport function serviceResolver(\n elements: Elem[],\n known?: ReadonlySet<string>,\n): (id: string) => DeclaredService {\n const byId = new Map(elements.map((e) => [e.id, e]));\n const memo = new Map<string, DeclaredService>();\n return (id: string): DeclaredService => {\n const hit = memo.get(id);\n if (hit !== undefined) return hit;\n let answer: DeclaredService | undefined;\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e?.service !== undefined) {\n answer = e.service;\n break;\n }\n }\n if (answer === undefined && known !== undefined) {\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e !== undefined && known.has(e.title)) {\n answer = declaredService(e.title);\n break;\n }\n }\n }\n if (answer === undefined) {\n const self = byId.get(id);\n answer = self ? elementService(self) : declaredService(id);\n }\n memo.set(id, answer);\n return answer;\n };\n}\n\n/**\n * The service a relationship endpoint belongs to. An id that names no element\n * resolves to itself, so a partial document degrades to the id rather than\n * throwing.\n *\n * `known` is the set of service directories that actually exist; pass it\n * wherever the caller has enumerated `services/`, so an edge into a modelled\n * container resolves to the service that owns it instead of to the container's\n * own title. Callers with a single document and no repository in hand omit it\n * and keep the pre-existing behaviour.\n */\nexport function serviceOf(elements: Elem[], id: string, known?: ReadonlySet<string>): DeclaredService {\n return serviceResolver(elements, known)(id);\n}\n\n/**\n * One `id -> service` resolver per document, not one per lookup.\n *\n * `serviceOf` builds a fresh id map on every call, and this axis calls it twice\n * for every relationship in scope — inside loops that already run over every\n * relationship a service can see. `serviceResolver`'s own doc says it is \"built\n * once per document and shared\"; this is where both axes that need it keep\n * that promise — `../arch.ts`'s `coversEdge` and `./steps.ts`'s `resolvedTier`,\n * each exported and each handed only the element array.\n *\n * A module-level cache is normally the wrong shape here, because a command runs\n * against whatever directory it was invoked in. This one is safe: the key is the\n * per-invocation `Elem[]` a parse produced, its value is a pure function of that\n * key alone, and nothing in it is derived from the working directory — so two\n * runs can never see each other's answer, and the entry dies with the document.\n * Nothing mutates an `Elem[]` after parse.\n *\n * Two tiers because the fleet set is part of the answer: the same document\n * resolves differently with and without `known`, so a cache keyed on the\n * elements alone handed a with-fleet caller the without-fleet resolver.\n * Callers build ONE set per run and pass the same instance through, which is\n * what lets the inner WeakMap key on the set's identity.\n */\nconst RESOLVERS = new WeakMap<Elem[], (id: string) => string>();\nconst FLEET_RESOLVERS = new WeakMap<Elem[], WeakMap<ReadonlySet<string>, (id: string) => string>>();\n\nexport function resolverFor(elements: Elem[], known?: ReadonlySet<string>): (id: string) => string {\n if (known === undefined) {\n const cached = RESOLVERS.get(elements);\n if (cached !== undefined) return cached;\n const resolver = serviceResolver(elements);\n RESOLVERS.set(elements, resolver);\n return resolver;\n }\n let perSet = FLEET_RESOLVERS.get(elements);\n if (perSet === undefined) {\n perSet = new WeakMap();\n FLEET_RESOLVERS.set(elements, perSet);\n }\n const cached = perSet.get(known);\n if (cached !== undefined) return cached;\n const resolver = serviceResolver(elements, known);\n perSet.set(known, resolver);\n return resolver;\n}\n\n/**\n * The element each service is DRAWN as: for every service the map resolves an\n * element to, the first service-level one in document order — an element that\n * resolves to the service while an ancestor does too is a container of it,\n * never a second box. The same filter and the same \"first one\" the adopt brief\n * names (`core/brief/landscape.ts`), so `loam new` and `loam adopt` can never\n * hand an author two different ids for one box.\n *\n * Exists so a scaffold reuses the id the map already spells instead of\n * guessing one: a feature delta whose context ids differ from the landscape's\n * does not line up at archive, and a guessed camelCase id on a seeded map\n * (`svc_<id>`) never matched once.\n */\nexport function drawnServiceElements(elements: Elem[], known?: ReadonlySet<string>): Map<string, string> {\n const svcOf = serviceResolver(elements, known);\n const drawn = new Map<string, string>();\n for (const e of elements) {\n const service = svcOf(e.id);\n if (drawn.has(service) || properAncestorIds(e.id).some((a) => svcOf(a) === service)) continue;\n drawn.set(service, e.id);\n }\n return drawn;\n}\n"]}
|