@ybotok/loam 0.2.0-alpha.7 → 0.2.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1315 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +83 -7
- package/SCHEMA.md +569 -98
- package/WORKFLOW.md +272 -62
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +33 -11
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/explore.js +4 -8
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +28 -33
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +21 -25
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +3 -5
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +5 -8
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +14 -41
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +17 -38
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +32 -5
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +23 -23
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +6 -10
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +1 -3
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +8 -28
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/report.js +5 -4
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +16 -104
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +1 -1
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +54 -51
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +56 -101
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +4 -3
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +32 -18
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +9 -3
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +20 -16
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +50 -18
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +6 -18
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +15 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/documents.js +31 -6
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +6 -17
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/findings.js +15 -20
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/coherence.js +30 -16
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +119 -8
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +3 -6
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +28 -16
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +5 -2
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +4 -2
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +5 -36
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +22 -8
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +56 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +2 -16
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/repo.js +24 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/walk.js +8 -30
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/staging/commit.js +10 -12
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +174 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +3 -0
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +12 -2
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +21 -6
- package/examples/docs/architecture/landscape.likec4 +8 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleet.js","sourceRoot":"","sources":["../../../../src/core/c4/prefetch/fleet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Every service model ONE command invocation has read, memoised — the fleet\n * map, each model's shape, each per-service project, and each assembled\n * `ServiceModel`.\n *\n * A package of its own rather than four more maps on `core/fleet-context.ts`,\n * and the seam is the one that module's banner already states for its leaves:\n * the rules being memoised here are `../service-model/`'s (which shape a file\n * has, which documents its project holds, how a failed batch degrades), and a\n * memo whose rules live an import away is a memo somebody fills under the wrong\n * key. The read index keeps what it owns — the document parses, the\n * enumerations — and hands them in. It also keeps that file under its line\n * limit, which is the honest second reason: this axis is four memos and a batch\n * plan, and it arrived at a module that was already 336 lines of other axes.\n *\n * The direction is one-way and load-bearing: `fleet-context.ts` imports this,\n * never the reverse. Everything this class needs from the read index arrives as\n * a function on the request (`ModelRead`), so nothing here knows that a\n * FleetContext exists — which is what keeps `core/` → `core/c4/` acyclic.\n *\n * REQUEST-SCOPED exactly as `FleetContext` is: one instance per invocation,\n * dropped when the command finishes. No module-level state, so a later run\n * cannot observe an earlier one's answer, and a `chdir` between two tests\n * cannot leak one docs repo into the next.\n */\nimport { resolve } from \"node:path\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\nimport type { ServicePaths } from \"../../repo/paths.js\";\nimport type { LoadedDoc } from \"../likec4.js\";\nimport { loadArchitecture } from \"../project/architecture.js\";\nimport { asLoadedDoc, loadProject } from \"../project/load.js\";\nimport { modelProjectDocuments } from \"../service-model/documents.js\";\nimport {\n batchServiceProjects,\n serviceModelAt,\n type ServiceModel,\n} from \"../service-model/load.js\";\nimport { readModelShape, readModelShapes, type ModelShape } from \"../service-model/shape.js\";\n\n/**\n * The three things this memo cannot know for itself, supplied once by whoever\n * owns it. Functions rather than values because each is itself memoised on the\n * caller's side: asking again is what makes an answer shared rather than\n * copied.\n */\nexport interface ModelDeps {\n /** Fires once per per-service project actually parsed, batched or not — the caller owns its counters. */\n onProjectLoad: () => void;\n /** The service directories that exist — the caller has enumerated them; this package must not enumerate them again. */\n known: (docsDir: DocsDir) => Promise<ReadonlySet<string>>;\n /** Parse one `.likec4` document ALONE, through the caller's own document memo, so a prefetched parse is a hit. */\n standalone: (path: string) => Promise<LoadedDoc>;\n}\n\n/** One in-flight promise per key, created on first ask — the memo idiom, once. */\nfunction memo<T>(store: Map<string, Promise<T>>, k: string, make: () => Promise<T>): Promise<T> {\n let pending = store.get(k);\n if (pending === undefined) {\n pending = make();\n store.set(k, pending);\n }\n return pending;\n}\n\nexport class ServiceModels {\n private readonly architectures = new Map<string, Promise<LoadedDoc>>();\n private readonly shapeOf = new Map<string, Promise<ModelShape>>();\n private readonly projects = new Map<string, Promise<LoadedDoc>>();\n private readonly models = new Map<string, Promise<ServiceModel>>();\n\n constructor(private readonly deps: ModelDeps) {}\n\n /**\n * The fleet map as the PROJECT it is (`../project/architecture.ts`). Read\n * twice over for every service now rather than once for the run — it is the\n * gate on whether an extending model can be read at all, and the other half\n * of the diff that decides what such a model actually declares — so the memo\n * is what keeps a fleet run to one parse of it.\n *\n * Filled ONLY when somebody asks. `model` below hands `loadServiceModel` a\n * thunk rather than an awaited value, so a fleet whose models all stand alone\n * never parses this project at all — a standalone model is read as one file,\n * and paying for the map (and inheriting its failures) for `loam show` or the\n * adopt brief was cost and risk for an answer nobody used.\n */\n architecture(docsDir: DocsDir): Promise<LoadedDoc> {\n return memo(this.architectures, resolve(docsDir), () => loadArchitecture(docsDir));\n }\n\n /**\n * Which shape each model has, keyed by resolved path — ONE read of each\n * file's bytes per invocation, however many questions are asked of it.\n *\n * The bulk form is the primary one because every caller asks in bulk: the\n * fleet target needs the shapes of the WHOLE enumeration before it can grade\n * a single renderer-file finding, and the prefetch needs them before it can\n * decide which workspace each model belongs in.\n */\n async shapes(paths: readonly string[]): Promise<Map<string, ModelShape>> {\n const keys = [...new Set(paths.map((path) => resolve(path)))];\n const missing = keys.filter((k) => !this.shapeOf.has(k));\n for (const [k, shape] of await readModelShapes(missing)) {\n // A concurrent read's promise stays authoritative — it is the one another\n // caller is already holding — so a second scan of the same bytes can\n // never replace it with a duplicate.\n if (!this.shapeOf.has(k)) this.shapeOf.set(k, Promise.resolve(shape));\n }\n const answered = await Promise.all(keys.map(async (k) => [k, await this.shape(k)] as const));\n return new Map(answered);\n }\n\n /** One model's shape, out of the same memo the bulk form fills. */\n shape(modelPath: string): Promise<ModelShape> {\n return memo(this.shapeOf, resolve(modelPath), () => readModelShape(modelPath));\n }\n\n /** A service's model, read the way its own shape demands. Memoised per model PATH, which is what identifies the file. */\n model(docsDir: DocsDir, paths: ServicePaths): Promise<ServiceModel> {\n return memo(this.models, resolve(paths.model), async () =>\n serviceModelAt({\n docsDir,\n paths,\n shape: await this.shape(paths.model),\n known: await this.deps.known(docsDir),\n // A thunk, so a standalone model never spins the map's workspace: the\n // memo makes the extending case one parse for the run either way.\n architecture: () => this.architecture(docsDir),\n standalone: () => this.deps.standalone(paths.model),\n project: () => this.project(docsDir, paths.model),\n }),\n );\n }\n\n /**\n * Prepare every EXTENDING model's project in one workspace, and answer with\n * the STANDALONE models' paths for the caller to prefetch through its own\n * document batch — the two shapes need different workspaces, and only the\n * caller has the memo the second one seeds.\n *\n * Purely an accelerator: a batch that could not be created seeds nothing and\n * every reader falls back to its own load, so findings can never change\n * because of a tmpdir — only the speed can.\n */\n async prefetch(docsDir: DocsDir, paths: readonly ServicePaths[]): Promise<string[]> {\n const shapes = await this.shapes(paths.map((p) => p.model));\n const models = (shape: ModelShape): string[] =>\n [...shapes].filter(([, found]) => found === shape).map(([model]) => model);\n const extending = models(\"extending\").filter((model) => !this.projects.has(model));\n for (const [k, doc] of await batchServiceProjects(docsDir, extending)) {\n // A concurrent load's promise stays authoritative: it is the one every\n // reader is already holding, and replacing it would count the parse twice.\n if (this.projects.has(k)) continue;\n this.deps.onProjectLoad();\n this.projects.set(k, Promise.resolve(doc));\n }\n return models(\"standalone\");\n }\n\n /**\n * One service's project — the architecture documents and its model, parsed\n * together. Keyed on the model, which is what `prefetch` seeds above, so a\n * batched fleet run and a single-service run answer out of the same memo.\n */\n private project(docsDir: DocsDir, modelPath: string): Promise<LoadedDoc> {\n return memo(this.projects, resolve(modelPath), async () => {\n this.deps.onProjectLoad();\n return asLoadedDoc(await loadProject(docsDir, await modelProjectDocuments(docsDir, modelPath)));\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fleet.js","sourceRoot":"","sources":["../../../../src/core/c4/prefetch/fleet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Every service model ONE command invocation has read, memoised — the fleet\n * map, each model's shape, each per-service project, and each assembled\n * `ServiceModel`.\n *\n * A package of its own rather than four more maps on `core/fleet-context.ts`,\n * and the seam is the one that module's banner already states for its leaves:\n * the rules being memoised here are `../service-model/`'s (which shape a file\n * has, which documents its project holds, how a failed batch degrades), and a\n * memo whose rules live an import away is a memo somebody fills under the wrong\n * key. The read index keeps what it owns — the document parses, the\n * enumerations — and hands them in. It also keeps that file under its line\n * limit, which is the honest second reason: this axis is four memos and a batch\n * plan, and it arrived at a module that was already 336 lines of other axes.\n *\n * The direction is one-way and load-bearing: `fleet-context.ts` imports this,\n * never the reverse. Everything this class needs from the read index arrives as\n * a function on the request (`ModelRead`), so nothing here knows that a\n * FleetContext exists — which is what keeps `core/` → `core/c4/` acyclic.\n *\n * REQUEST-SCOPED exactly as `FleetContext` is: one instance per invocation,\n * dropped when the command finishes. No module-level state, so a later run\n * cannot observe an earlier one's answer, and a `chdir` between two tests\n * cannot leak one docs repo into the next.\n */\nimport { resolve } from \"node:path\";\nimport { memo } from \"../../kernel/concurrency.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\nimport type { ServicePaths } from \"../../repo/paths.js\";\nimport type { LoadedDoc } from \"../likec4.js\";\nimport { loadArchitecture } from \"../project/architecture.js\";\nimport { asLoadedDoc, loadProject } from \"../project/load.js\";\nimport { modelProjectDocuments } from \"../service-model/documents.js\";\nimport {\n batchServiceProjects,\n serviceModelAt,\n type ServiceModel,\n} from \"../service-model/load.js\";\nimport { readModelShape, readModelShapes, type ModelShape } from \"../service-model/shape.js\";\n\n/**\n * The three things this memo cannot know for itself, supplied once by whoever\n * owns it. Functions rather than values because each is itself memoised on the\n * caller's side: asking again is what makes an answer shared rather than\n * copied.\n */\nexport interface ModelDeps {\n /** Fires once per per-service project actually parsed, batched or not — the caller owns its counters. */\n onProjectLoad: () => void;\n /** The service directories that exist — the caller has enumerated them; this package must not enumerate them again. */\n known: (docsDir: DocsDir) => Promise<ReadonlySet<string>>;\n /** Parse one `.likec4` document ALONE, through the caller's own document memo, so a prefetched parse is a hit. */\n standalone: (path: string) => Promise<LoadedDoc>;\n}\n\nexport class ServiceModels {\n private readonly architectures = new Map<string, Promise<LoadedDoc>>();\n private readonly shapeOf = new Map<string, Promise<ModelShape>>();\n private readonly projects = new Map<string, Promise<LoadedDoc>>();\n private readonly models = new Map<string, Promise<ServiceModel>>();\n\n constructor(private readonly deps: ModelDeps) {}\n\n /**\n * The fleet map as the PROJECT it is (`../project/architecture.ts`). Read\n * twice over for every service now rather than once for the run — it is the\n * gate on whether an extending model can be read at all, and the other half\n * of the diff that decides what such a model actually declares — so the memo\n * is what keeps a fleet run to one parse of it.\n *\n * Filled ONLY when somebody asks. `model` below hands `loadServiceModel` a\n * thunk rather than an awaited value, so a fleet whose models all stand alone\n * never parses this project at all — a standalone model is read as one file,\n * and paying for the map (and inheriting its failures) for `loam show` or the\n * adopt brief was cost and risk for an answer nobody used.\n */\n architecture(docsDir: DocsDir): Promise<LoadedDoc> {\n return memo(this.architectures, resolve(docsDir), () => loadArchitecture(docsDir));\n }\n\n /**\n * Which shape each model has, keyed by resolved path — ONE read of each\n * file's bytes per invocation, however many questions are asked of it.\n *\n * The bulk form is the primary one because every caller asks in bulk: the\n * fleet target needs the shapes of the WHOLE enumeration before it can grade\n * a single renderer-file finding, and the prefetch needs them before it can\n * decide which workspace each model belongs in.\n */\n async shapes(paths: readonly string[]): Promise<Map<string, ModelShape>> {\n const keys = [...new Set(paths.map((path) => resolve(path)))];\n const missing = keys.filter((k) => !this.shapeOf.has(k));\n for (const [k, shape] of await readModelShapes(missing)) {\n // A concurrent read's promise stays authoritative — it is the one another\n // caller is already holding — so a second scan of the same bytes can\n // never replace it with a duplicate.\n if (!this.shapeOf.has(k)) this.shapeOf.set(k, Promise.resolve(shape));\n }\n const answered = await Promise.all(keys.map(async (k) => [k, await this.shape(k)] as const));\n return new Map(answered);\n }\n\n /** One model's shape, out of the same memo the bulk form fills. */\n shape(modelPath: string): Promise<ModelShape> {\n return memo(this.shapeOf, resolve(modelPath), () => readModelShape(modelPath));\n }\n\n /** A service's model, read the way its own shape demands. Memoised per model PATH, which is what identifies the file. */\n model(docsDir: DocsDir, paths: ServicePaths): Promise<ServiceModel> {\n return memo(this.models, resolve(paths.model), async () =>\n serviceModelAt({\n docsDir,\n paths,\n shape: await this.shape(paths.model),\n known: await this.deps.known(docsDir),\n // A thunk, so a standalone model never spins the map's workspace: the\n // memo makes the extending case one parse for the run either way.\n architecture: () => this.architecture(docsDir),\n standalone: () => this.deps.standalone(paths.model),\n project: () => this.project(docsDir, paths.model),\n }),\n );\n }\n\n /**\n * Prepare every EXTENDING model's project in one workspace, and answer with\n * the STANDALONE models' paths for the caller to prefetch through its own\n * document batch — the two shapes need different workspaces, and only the\n * caller has the memo the second one seeds.\n *\n * Purely an accelerator: a batch that could not be created seeds nothing and\n * every reader falls back to its own load, so findings can never change\n * because of a tmpdir — only the speed can.\n */\n async prefetch(docsDir: DocsDir, paths: readonly ServicePaths[]): Promise<string[]> {\n const shapes = await this.shapes(paths.map((p) => p.model));\n const models = (shape: ModelShape): string[] =>\n [...shapes].filter(([, found]) => found === shape).map(([model]) => model);\n const extending = models(\"extending\").filter((model) => !this.projects.has(model));\n for (const [k, doc] of await batchServiceProjects(docsDir, extending)) {\n // A concurrent load's promise stays authoritative: it is the one every\n // reader is already holding, and replacing it would count the parse twice.\n if (this.projects.has(k)) continue;\n this.deps.onProjectLoad();\n this.projects.set(k, Promise.resolve(doc));\n }\n return models(\"standalone\");\n }\n\n /**\n * One service's project — the architecture documents and its model, parsed\n * together. Keyed on the model, which is what `prefetch` seeds above, so a\n * batched fleet run and a single-service run answer out of the same memo.\n */\n private project(docsDir: DocsDir, modelPath: string): Promise<LoadedDoc> {\n return memo(this.projects, resolve(modelPath), async () => {\n this.deps.onProjectLoad();\n return asLoadedDoc(await loadProject(docsDir, await modelProjectDocuments(docsDir, modelPath)));\n });\n }\n}\n"]}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* (`./architecture.ts` holds the one join).
|
|
28
28
|
*/
|
|
29
29
|
import { readdir } from "node:fs/promises";
|
|
30
|
-
import { join, resolve } from "node:path";
|
|
30
|
+
import { join, relative, resolve } from "node:path";
|
|
31
31
|
/**
|
|
32
32
|
* Every `.likec4` file under `dir`, recursively, minus `skip`, in a stable
|
|
33
33
|
* order.
|
|
@@ -41,24 +41,49 @@ import { join, resolve } from "node:path";
|
|
|
41
41
|
*/
|
|
42
42
|
export async function architectureDocuments(dir, skip = []) {
|
|
43
43
|
const excluded = new Set(skip.map((path) => resolve(path)));
|
|
44
|
+
return likec4DocumentsUnder(dir, { keeps: (path) => !excluded.has(resolve(path)) });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `architectureDocuments(dir)`, each beside its path inside `dir`,
|
|
48
|
+
* `/`-separated — the id a feature-local document axis keys on (a feature's
|
|
49
|
+
* `usecases/` flows, its `deployment/` topology), spelled once for both.
|
|
50
|
+
*/
|
|
51
|
+
export async function relativeDocuments(dir) {
|
|
52
|
+
const base = resolve(dir);
|
|
53
|
+
return (await architectureDocuments(dir)).map((path) => ({ rel: relative(base, path).split(/[\\/]/).join("/"), path }));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The walk under `architectureDocuments` and the renderer root project's
|
|
57
|
+
* enumeration (`commands/validate/fleet/views/root-documents.ts`): every
|
|
58
|
+
* `.likec4` under `dir`, recursively, absolute and sorted for the reason
|
|
59
|
+
* above. A directory that cannot be read contributes nothing, never a throw —
|
|
60
|
+
* for the missing-`architecture/` reason above, and because the root walk
|
|
61
|
+
* runs inside `validate --all` over a whole fleet, where the one behaviour it
|
|
62
|
+
* may never have is failing the run it was added to.
|
|
63
|
+
*/
|
|
64
|
+
export async function likec4DocumentsUnder(dir, walk) {
|
|
44
65
|
const found = [];
|
|
45
|
-
const
|
|
66
|
+
const visit = async (at) => {
|
|
46
67
|
let entries;
|
|
47
68
|
try {
|
|
48
69
|
entries = await readdir(at, { withFileTypes: true });
|
|
49
70
|
}
|
|
50
71
|
catch {
|
|
72
|
+
// Absent or unreadable: nothing to load from here (see above).
|
|
51
73
|
return;
|
|
52
74
|
}
|
|
53
75
|
for (const entry of entries) {
|
|
54
76
|
const path = join(at, entry.name);
|
|
55
|
-
if (entry.isDirectory())
|
|
56
|
-
|
|
57
|
-
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
if (walk.enters?.(entry.name) ?? true)
|
|
79
|
+
await visit(path);
|
|
80
|
+
}
|
|
81
|
+
else if (entry.name.endsWith(".likec4") && (walk.keeps?.(path) ?? true)) {
|
|
58
82
|
found.push(path);
|
|
83
|
+
}
|
|
59
84
|
}
|
|
60
85
|
};
|
|
61
|
-
await
|
|
86
|
+
await visit(resolve(dir));
|
|
62
87
|
return found.sort();
|
|
63
88
|
}
|
|
64
89
|
//# sourceMappingURL=documents.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.js","sourceRoot":"","sources":["../../../../src/core/c4/project/documents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"documents.js","sourceRoot":"","sources":["../../../../src/core/c4/project/documents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAW,EAAE,IAAI,GAAa,EAAE;IAC1E,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5D,OAAO,oBAAoB,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;AACtF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW;IACjD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,OAAO,CAAC,MAAM,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC1H,CAAC;AAQD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAW,EAAE,IAAkB;IACxE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,KAAK,EAAE,EAAU,EAAiB,EAAE;QAChD,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;YAC/D,OAAO;QACT,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI;oBAAE,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3D,CAAC;iBAAM,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC;gBAC1E,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC","sourcesContent":["/**\n * Which `.likec4` documents in a docs repo's `architecture/` loam loads as one\n * project.\n *\n * The renderer's answer is \"all of them\". The root `likec4.config.json` no\n * longer excludes `services/**`: the root project holds the map AND every\n * service model that EXTENDS it, because such a model is only readable beside\n * the map, and `loam subsystem sync` excludes only the directory of each model\n * that STANDS ALONE — one `services/<tree>/**` entry per standalone model,\n * never the whole subtree (`c4/service-model/renderer.ts` owns that list, and\n * is the one place loam reads that file). loam's own answer here is all of them\n * EXCEPT the documents loam GENERATES there (`architecture/subsystems.likec4`\n * and `architecture/services.likec4`), and that exception is not an optimisation:\n *\n * - docs/DESIGN.md rule 26 excludes the generated file from every read loam\n * performs. Its staleness is a byte compare (`subsystem.views-stale`) and\n * nothing anywhere reads its contents.\n * - Including it would invent a failure mode. Its `include` lines name\n * landscape element ids, so a file that has drifted — a service removed\n * since the last `loam subsystem sync` — would be a parse error, and a parse\n * error in one project document blanks the model for all of them. A fleet\n * whose only problem is a stale generated file would lose its whole map\n * instead of getting the one finding that names the repair.\n *\n * The skip is passed in by the caller rather than spelled here, so the filenames\n * stay written in `repo/paths.ts` alone, and every reader takes the same SET out\n * (`./architecture.ts` holds the one join).\n */\nimport { readdir } from \"node:fs/promises\";\nimport { join, relative, resolve } from \"node:path\";\n\n/**\n * Every `.likec4` file under `dir`, recursively, minus `skip`, in a stable\n * order.\n *\n * Sorted because it decides the staging order and therefore nothing at all —\n * LikeC4 merges a project regardless — but a stable list makes a failure\n * reproducible and a diff of one readable. A directory that does not exist is\n * an empty list, never a throw: a fleet with no `architecture/` is a fleet with\n * nothing to load, which is already `landscape.missing`'s question, not this\n * function's.\n */\nexport async function architectureDocuments(dir: string, skip: string[] = []): Promise<string[]> {\n const excluded = new Set(skip.map((path) => resolve(path)));\n return likec4DocumentsUnder(dir, { keeps: (path) => !excluded.has(resolve(path)) });\n}\n\n/**\n * `architectureDocuments(dir)`, each beside its path inside `dir`,\n * `/`-separated — the id a feature-local document axis keys on (a feature's\n * `usecases/` flows, its `deployment/` topology), spelled once for both.\n */\nexport async function relativeDocuments(dir: string): Promise<{ rel: string; path: string }[]> {\n const base = resolve(dir);\n return (await architectureDocuments(dir)).map((path) => ({ rel: relative(base, path).split(/[\\\\/]/).join(\"/\"), path }));\n}\n\n/** What one walk leaves out: directories it never enters (by name), documents it never collects (by path). */\nexport interface DocumentWalk {\n enters?: (name: string) => boolean;\n keeps?: (path: string) => boolean;\n}\n\n/**\n * The walk under `architectureDocuments` and the renderer root project's\n * enumeration (`commands/validate/fleet/views/root-documents.ts`): every\n * `.likec4` under `dir`, recursively, absolute and sorted for the reason\n * above. A directory that cannot be read contributes nothing, never a throw —\n * for the missing-`architecture/` reason above, and because the root walk\n * runs inside `validate --all` over a whole fleet, where the one behaviour it\n * may never have is failing the run it was added to.\n */\nexport async function likec4DocumentsUnder(dir: string, walk: DocumentWalk): Promise<string[]> {\n const found: string[] = [];\n const visit = async (at: string): Promise<void> => {\n let entries;\n try {\n entries = await readdir(at, { withFileTypes: true });\n } catch {\n // Absent or unreadable: nothing to load from here (see above).\n return;\n }\n for (const entry of entries) {\n const path = join(at, entry.name);\n if (entry.isDirectory()) {\n if (walk.enters?.(entry.name) ?? true) await visit(path);\n } else if (entry.name.endsWith(\".likec4\") && (walk.keeps?.(path) ?? true)) {\n found.push(path);\n }\n }\n };\n await visit(resolve(dir));\n return found.sort();\n}\n"]}
|
|
@@ -16,11 +16,8 @@ import { tmpdir } from "node:os";
|
|
|
16
16
|
import { dirname, join, relative, resolve } from "node:path";
|
|
17
17
|
import { LikeC4 } from "likec4";
|
|
18
18
|
import { inOrder } from "../../kernel/concurrency.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { readSpecification } from "../parsed/specification.js";
|
|
22
|
-
import { readGlobalStyleIds, readViewIds } from "../parsed/view-ids.js";
|
|
23
|
-
import { readDeployment, NO_DEPLOYMENT } from "../parsed/deployment.js";
|
|
19
|
+
import { distinctErrors, readParsedModel } from "../likec4.js";
|
|
20
|
+
import { NO_DEPLOYMENT } from "../parsed/deployment.js";
|
|
24
21
|
/**
|
|
25
22
|
* Stage a set of documents as ONE project named `project`, mirroring their
|
|
26
23
|
* paths relative to `base`.
|
|
@@ -153,16 +150,7 @@ export function emptyProject(errors, clean) {
|
|
|
153
150
|
* test/likec4-batch-parity.test.ts was written to catch.
|
|
154
151
|
*/
|
|
155
152
|
export function cleanProjectDoc(model) {
|
|
156
|
-
return {
|
|
157
|
-
errors: new Map(),
|
|
158
|
-
clean: true,
|
|
159
|
-
specification: readSpecification(model.specification),
|
|
160
|
-
views: readDynamicViews(model),
|
|
161
|
-
viewIds: readViewIds(model),
|
|
162
|
-
globalStyles: readGlobalStyleIds(model),
|
|
163
|
-
deployment: readDeployment(model),
|
|
164
|
-
...flattenModel(model),
|
|
165
|
-
};
|
|
153
|
+
return { errors: new Map(), clean: true, ...readParsedModel(model) };
|
|
166
154
|
}
|
|
167
155
|
/**
|
|
168
156
|
* Diagnostics split per DOCUMENT inside one project, each error's
|
|
@@ -178,12 +166,13 @@ export function cleanProjectDoc(model) {
|
|
|
178
166
|
* It takes the diagnostics rather than the workspace so `./batch.ts` can hand
|
|
179
167
|
* it ONE project's share of a multi-project `getErrors()` — the two questions
|
|
180
168
|
* compose (which project, then which document) exactly because neither is
|
|
181
|
-
* baked in here.
|
|
169
|
+
* baked in here. Both callers' diagnostics pass through `distinctErrors` here,
|
|
170
|
+
* once, as every loader's do (`../likec4.ts` says why).
|
|
182
171
|
*/
|
|
183
172
|
export function groupErrorsByRel(errors, byRel) {
|
|
184
173
|
const rels = [...byRel.keys()].sort((a, b) => b.length - a.length);
|
|
185
174
|
const grouped = new Map();
|
|
186
|
-
for (const err of errors) {
|
|
175
|
+
for (const err of distinctErrors(errors)) {
|
|
187
176
|
const spelled = err.sourceFsPath.split(/[\\/]/).join("/");
|
|
188
177
|
const rel = rels.find((candidate) => spelled.endsWith(`/${candidate}`) || spelled === candidate);
|
|
189
178
|
if (rel === undefined) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../../../src/core/c4/project/load.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One directory of `.likec4` documents, loaded as ONE LikeC4 project — the way\n * the renderer already reads `architecture/`.\n *\n * A package of its own rather than more of `../workspace.ts`, and the seam is\n * the isolation rule, not the line count. That module stages every document as\n * its own single-file project ON PURPOSE, so an author-written `import` can\n * never resolve against a sibling; this one does the opposite deliberately, for\n * the set of documents that are meant to see each other. Two loaders with\n * opposite contracts should not share a file: the next reader has to be able to\n * tell at a glance which one they are looking at.\n */\nimport { randomBytes } from \"node:crypto\";\nimport { copyFile, mkdir, mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport { LikeC4 } from \"likec4\";\nimport { inOrder } from \"../../kernel/concurrency.js\";\nimport { flattenModel, type Elem, type LikeC4Error, type LoadedDoc, type ReadableModel, type Rel } from \"../likec4.js\";\nimport { readDynamicViews, type ParsedView } from \"../parsed/dynamic-views.js\";\nimport { readSpecification, type DocSpecification } from \"../parsed/specification.js\";\nimport { readGlobalStyleIds, readViewIds, type ViewIdClaim } from \"../parsed/view-ids.js\";\nimport { readDeployment, NO_DEPLOYMENT, type DeploymentModel } from \"../parsed/deployment.js\";\n\n/**\n * A diagnostic exactly as a WORKSPACE hands it back, where `sourceFsPath` is\n * always present — unlike loam's own `LikeC4Error`, whose field is optional\n * because the several literals standing in for an unread file have no path to\n * put there. Named so the attribution below can take diagnostics rather than\n * the workspace object, without a non-null assertion per error.\n */\ntype WorkspaceError = LikeC4Error & { sourceFsPath: string };\n\n/** A directory of `.likec4` documents loaded as one LikeC4 project. */\nexport interface ProjectDoc {\n /** Errors by the REAL absolute path of the document that raised them. */\n errors: Map<string, LikeC4Error[]>;\n /** True when no document in the project raised one — loam's \"errors mean no model\" gate. */\n clean: boolean;\n elements: Elem[];\n relationships: Rel[];\n specification?: DocSpecification;\n /** Every dynamic view the project declares, each carrying the file it is written in. */\n views: ParsedView[];\n /** Every authored view id the project claims, each with its file. */\n viewIds: ViewIdClaim[];\n /**\n * Every global style id declared anywhere in the project, sorted — the\n * renderer's table, so a palette kept in `usecases/style.likec4` counts\n * exactly as one in the landscape does. Empty when none, and when it did\n * not parse.\n */\n globalStyles: string[];\n /** The project's deployment model — empty when it declares none, and when it did not parse. */\n deployment: DeploymentModel;\n}\n\n/**\n * Stage a set of documents as ONE project named `project`, mirroring their\n * paths relative to `base`.\n *\n * The relative structure is preserved rather than flattened to basenames, and\n * that is load-bearing twice over: two use cases named `checkout.likec4` in\n * different subdirectories would overwrite each other on copy, and LikeC4\n * reports a view's `sourcePath` relative to the project root — so flattening\n * would make every finding name a file the author cannot find.\n *\n * The NAME arrives from the caller rather than being minted in here, and that\n * is what lets `./batch.ts` reuse this staging verbatim: many projects in one\n * workspace need the folder name twice over — `parsedModel(<name>)` asks for\n * that project's model, and error attribution keys on the folder SEGMENT.\n * `loadProject` below has one project and passes a name it never looks at\n * again; both spellings carry a crypto-random per-invocation token for\n * `../workspace.ts`'s reason (an author-written `import` must not be able to\n * name a sibling project).\n */\nexport async function stageProject(\n root: string,\n project: string,\n base: string,\n targets: string[],\n): Promise<Map<string, string>> {\n const folder = join(root, project);\n await mkdir(folder, { recursive: true });\n await writeFile(join(folder, \"likec4.config.json\"), JSON.stringify({ name: project }), \"utf8\");\n // rel -> real path, so an error carrying the staged path can be handed back\n // spelled the way the author wrote it.\n const byRel = new Map<string, string>();\n await inOrder(targets, async (path) => {\n const rel = relative(base, path).split(/[\\\\/]/).join(\"/\");\n const dest = join(folder, ...rel.split(\"/\"));\n try {\n await mkdir(dirname(dest), { recursive: true });\n await copyFile(path, dest);\n byRel.set(rel, path);\n } catch {\n // Dropped, exactly as `stage` drops one: the caller's ordinary per-path\n // load owns the error, and a project missing one document still answers\n // about the rest.\n }\n return null;\n });\n return byRel;\n}\n\n/**\n * Load a directory of `.likec4` documents as ONE LikeC4 project — the way the\n * renderer already reads `architecture/`, and the only way a document that\n * declares views over ANOTHER file's elements can be read at all.\n *\n * Measured at the 1.59.2 pin: a views-only `usecases/checkout.likec4` parsed\n * alone is five errors (unresolved tag, unresolved elements); the same file\n * beside its landscape in one project is zero, and each view comes back with\n * its own `sourcePath`. That is the whole reason this function exists — a use\n * case lives in a file of its own, and `loadBatch`'s one-project-per-document\n * isolation makes such a file unreadable by construction.\n *\n * Two rules the caller must know, because both invert what holds elsewhere:\n *\n * - **Exactly one document in the set declares the `specification` block.**\n * Every `.likec4` file loam parses alone must declare its own; inside one\n * project a second declaration is a duplicate error blamed on BOTH files\n * (measured).\n * - **Errors are per document, but the MODEL is all-or-nothing.** A typo in\n * one file is attributed to that file — so the finding names it, which is\n * better than today, where a use case in the landscape would blame the\n * landscape — but loam's standing rule is that errors mean no model, and\n * here the model is the project's. `clean` says which case the caller is in.\n *\n * The documents loam GENERATES under `architecture/` must NOT be in `targets`\n * — there are two of them now (`subsystems.likec4` and `services.likec4`, both\n * written by `loam subsystem sync`), and `repo/paths.ts`'s\n * `GENERATED_ARCHITECTURE_FILES` is the one table that names them. docs/DESIGN.md\n * rule 26 keeps each a byte compare, and a stale one whose `include` names a\n * removed element would otherwise be a parse error that blanks the whole fleet\n * map. `./architecture.ts` holds the join from that table to a docsDir, so no\n * caller has to remember the list.\n */\nexport async function loadProject(base: string, paths: string[]): Promise<ProjectDoc> {\n const targets = [...new Set(paths.map((path) => resolve(path)))];\n if (targets.length === 0) return emptyProject(new Map(), true);\n const root = await mkdtemp(join(tmpdir(), \"loam-c4-\"));\n try {\n const byRel = await stageProject(root, `arch_${randomBytes(8).toString(\"hex\")}`, resolve(base), targets);\n if (byRel.size === 0) return emptyProject(new Map(), true);\n const likec4 = await LikeC4.fromWorkspace(root, { logger: false });\n try {\n const errors = groupErrorsByRel(likec4.getErrors(), byRel);\n if (errors.size > 0) return emptyProject(errors, false);\n return cleanProjectDoc((await likec4.parsedModel()) as ReadableModel);\n } finally {\n await likec4.dispose();\n }\n } finally {\n await rm(root, { recursive: true, force: true });\n }\n}\n\n/**\n * A `ProjectDoc` carrying NO model — the answer for a project that did not\n * parse (`clean: false`) and for one there was nothing to load (`clean: true`,\n * empty errors).\n *\n * Exported for `./batch.ts`, which answers about many projects at once and\n * must answer each of them in exactly this shape: a second literal there would\n * be a second chance to spell `deployment` as `undefined` and turn \"this\n * project declares no topology\" into a crash in every reader.\n */\nexport function emptyProject(errors: Map<string, LikeC4Error[]>, clean: boolean): ProjectDoc {\n return {\n errors,\n clean,\n elements: [],\n relationships: [],\n views: [],\n viewIds: [],\n globalStyles: [],\n deployment: NO_DEPLOYMENT,\n };\n}\n\n/**\n * Everything loam reads out of a project that parsed, from LikeC4's parsed\n * stage — parsed, never computed, for the reason `../likec4.ts` records.\n *\n * Exported beside `emptyProject` and for the same reason: `./batch.ts` builds\n * one of these per clean project, and an adapter wired into one loader and not\n * the other reads as an absence rather than an error — the failure shape\n * test/likec4-batch-parity.test.ts was written to catch.\n */\nexport function cleanProjectDoc(model: ReadableModel): ProjectDoc {\n return {\n errors: new Map(),\n clean: true,\n specification: readSpecification(model.specification),\n views: readDynamicViews(model),\n viewIds: readViewIds(model),\n globalStyles: readGlobalStyleIds(model),\n deployment: readDeployment(model),\n ...flattenModel(model),\n };\n}\n\n/**\n * Diagnostics split per DOCUMENT inside one project, each error's\n * `sourceFsPath` rewritten to the document's real absolute path.\n *\n * `../workspace.ts`'s `groupErrors` keys on the project folder segment, which\n * cannot work here — every document shares one project. This keys on the staged\n * relative path instead, longest first so `usecases/checkout.likec4` is matched\n * before a hypothetical `checkout.likec4` at the root. An error nobody can\n * attribute throws for the same reason it does there: a dropped error grades\n * its document clean, which is failing open in the fleet gate.\n *\n * It takes the diagnostics rather than the workspace so `./batch.ts` can hand\n * it ONE project's share of a multi-project `getErrors()` — the two questions\n * compose (which project, then which document) exactly because neither is\n * baked in here.\n */\nexport function groupErrorsByRel(\n errors: readonly WorkspaceError[],\n byRel: Map<string, string>,\n): Map<string, LikeC4Error[]> {\n const rels = [...byRel.keys()].sort((a, b) => b.length - a.length);\n const grouped = new Map<string, LikeC4Error[]>();\n for (const err of errors) {\n const spelled = err.sourceFsPath.split(/[\\\\/]/).join(\"/\");\n const rel = rels.find((candidate) => spelled.endsWith(`/${candidate}`) || spelled === candidate);\n if (rel === undefined) {\n throw new Error(`unattributable LikeC4 error in project workspace: ${err.message}`);\n }\n const real = byRel.get(rel)!;\n const list = grouped.get(real) ?? [];\n list.push({ ...err, sourceFsPath: real });\n grouped.set(real, list);\n }\n return grouped;\n}\n\n/**\n * A `ProjectDoc` in the shape every existing landscape reader already takes.\n *\n * The per-file error map flattens into one list, which loses nothing: each\n * error still carries the real path of the document that raised it, so a\n * finding can name the file even though the list is flat. Sorted by that path\n * so a project with two broken documents reports them in a stable order —\n * `getErrors()` returns whatever order the workspace scan produced.\n *\n * The model travels only when the project is clean, which is loam's standing\n * rule (`errors mean no model`) applied at project altitude: a use-case file\n * with an unresolved element leaves the landscape unusable, and pretending\n * otherwise would grade a fleet against half a map.\n */\nexport function asLoadedDoc(doc: ProjectDoc): LoadedDoc {\n const errors = [...doc.errors]\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .flatMap(([, list]) => list);\n if (!doc.clean) return { errors, elements: [], relationships: [] };\n return {\n errors,\n ...(doc.specification === undefined ? {} : { specification: doc.specification }),\n views: doc.views,\n viewIds: doc.viewIds,\n globalStyles: doc.globalStyles,\n deployment: doc.deployment,\n elements: doc.elements,\n relationships: doc.relationships,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../../../src/core/c4/project/load.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One directory of `.likec4` documents, loaded as ONE LikeC4 project — the way\n * the renderer already reads `architecture/`.\n *\n * A package of its own rather than more of `../workspace.ts`, and the seam is\n * the isolation rule, not the line count. That module stages every document as\n * its own single-file project ON PURPOSE, so an author-written `import` can\n * never resolve against a sibling; this one does the opposite deliberately, for\n * the set of documents that are meant to see each other. Two loaders with\n * opposite contracts should not share a file: the next reader has to be able to\n * tell at a glance which one they are looking at.\n */\nimport { randomBytes } from \"node:crypto\";\nimport { copyFile, mkdir, mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport { LikeC4 } from \"likec4\";\nimport { inOrder } from \"../../kernel/concurrency.js\";\nimport { distinctErrors, readParsedModel, type Elem, type LikeC4Error, type LoadedDoc, type ReadableModel, type Rel } from \"../likec4.js\";\nimport type { ParsedView } from \"../parsed/dynamic-views.js\";\nimport type { DocSpecification } from \"../parsed/specification.js\";\nimport type { ViewIdClaim } from \"../parsed/view-ids.js\";\nimport { NO_DEPLOYMENT, type DeploymentModel } from \"../parsed/deployment.js\";\n\n/**\n * A diagnostic exactly as a WORKSPACE hands it back, where `sourceFsPath` is\n * always present — unlike loam's own `LikeC4Error`, whose field is optional\n * because the several literals standing in for an unread file have no path to\n * put there. Named so the attribution below can take diagnostics rather than\n * the workspace object, without a non-null assertion per error.\n */\ntype WorkspaceError = LikeC4Error & { sourceFsPath: string };\n\n/** A directory of `.likec4` documents loaded as one LikeC4 project. */\nexport interface ProjectDoc {\n /** Errors by the REAL absolute path of the document that raised them. */\n errors: Map<string, LikeC4Error[]>;\n /** True when no document in the project raised one — loam's \"errors mean no model\" gate. */\n clean: boolean;\n elements: Elem[];\n relationships: Rel[];\n specification?: DocSpecification;\n /** Every dynamic view the project declares, each carrying the file it is written in. */\n views: ParsedView[];\n /** Every authored view id the project claims, each with its file. */\n viewIds: ViewIdClaim[];\n /**\n * Every global style id declared anywhere in the project, sorted — the\n * renderer's table, so a palette kept in `usecases/style.likec4` counts\n * exactly as one in the landscape does. Empty when none, and when it did\n * not parse.\n */\n globalStyles: string[];\n /** The project's deployment model — empty when it declares none, and when it did not parse. */\n deployment: DeploymentModel;\n}\n\n/**\n * Stage a set of documents as ONE project named `project`, mirroring their\n * paths relative to `base`.\n *\n * The relative structure is preserved rather than flattened to basenames, and\n * that is load-bearing twice over: two use cases named `checkout.likec4` in\n * different subdirectories would overwrite each other on copy, and LikeC4\n * reports a view's `sourcePath` relative to the project root — so flattening\n * would make every finding name a file the author cannot find.\n *\n * The NAME arrives from the caller rather than being minted in here, and that\n * is what lets `./batch.ts` reuse this staging verbatim: many projects in one\n * workspace need the folder name twice over — `parsedModel(<name>)` asks for\n * that project's model, and error attribution keys on the folder SEGMENT.\n * `loadProject` below has one project and passes a name it never looks at\n * again; both spellings carry a crypto-random per-invocation token for\n * `../workspace.ts`'s reason (an author-written `import` must not be able to\n * name a sibling project).\n */\nexport async function stageProject(\n root: string,\n project: string,\n base: string,\n targets: string[],\n): Promise<Map<string, string>> {\n const folder = join(root, project);\n await mkdir(folder, { recursive: true });\n await writeFile(join(folder, \"likec4.config.json\"), JSON.stringify({ name: project }), \"utf8\");\n // rel -> real path, so an error carrying the staged path can be handed back\n // spelled the way the author wrote it.\n const byRel = new Map<string, string>();\n await inOrder(targets, async (path) => {\n const rel = relative(base, path).split(/[\\\\/]/).join(\"/\");\n const dest = join(folder, ...rel.split(\"/\"));\n try {\n await mkdir(dirname(dest), { recursive: true });\n await copyFile(path, dest);\n byRel.set(rel, path);\n } catch {\n // Dropped, exactly as `stage` drops one: the caller's ordinary per-path\n // load owns the error, and a project missing one document still answers\n // about the rest.\n }\n return null;\n });\n return byRel;\n}\n\n/**\n * Load a directory of `.likec4` documents as ONE LikeC4 project — the way the\n * renderer already reads `architecture/`, and the only way a document that\n * declares views over ANOTHER file's elements can be read at all.\n *\n * Measured at the 1.59.2 pin: a views-only `usecases/checkout.likec4` parsed\n * alone is five errors (unresolved tag, unresolved elements); the same file\n * beside its landscape in one project is zero, and each view comes back with\n * its own `sourcePath`. That is the whole reason this function exists — a use\n * case lives in a file of its own, and `loadBatch`'s one-project-per-document\n * isolation makes such a file unreadable by construction.\n *\n * Two rules the caller must know, because both invert what holds elsewhere:\n *\n * - **Exactly one document in the set declares the `specification` block.**\n * Every `.likec4` file loam parses alone must declare its own; inside one\n * project a second declaration is a duplicate error blamed on BOTH files\n * (measured).\n * - **Errors are per document, but the MODEL is all-or-nothing.** A typo in\n * one file is attributed to that file — so the finding names it, which is\n * better than today, where a use case in the landscape would blame the\n * landscape — but loam's standing rule is that errors mean no model, and\n * here the model is the project's. `clean` says which case the caller is in.\n *\n * The documents loam GENERATES under `architecture/` must NOT be in `targets`\n * — there are two of them now (`subsystems.likec4` and `services.likec4`, both\n * written by `loam subsystem sync`), and `repo/paths.ts`'s\n * `GENERATED_ARCHITECTURE_FILES` is the one table that names them. docs/DESIGN.md\n * rule 26 keeps each a byte compare, and a stale one whose `include` names a\n * removed element would otherwise be a parse error that blanks the whole fleet\n * map. `./architecture.ts` holds the join from that table to a docsDir, so no\n * caller has to remember the list.\n */\nexport async function loadProject(base: string, paths: string[]): Promise<ProjectDoc> {\n const targets = [...new Set(paths.map((path) => resolve(path)))];\n if (targets.length === 0) return emptyProject(new Map(), true);\n const root = await mkdtemp(join(tmpdir(), \"loam-c4-\"));\n try {\n const byRel = await stageProject(root, `arch_${randomBytes(8).toString(\"hex\")}`, resolve(base), targets);\n if (byRel.size === 0) return emptyProject(new Map(), true);\n const likec4 = await LikeC4.fromWorkspace(root, { logger: false });\n try {\n const errors = groupErrorsByRel(likec4.getErrors(), byRel);\n if (errors.size > 0) return emptyProject(errors, false);\n return cleanProjectDoc((await likec4.parsedModel()) as ReadableModel);\n } finally {\n await likec4.dispose();\n }\n } finally {\n await rm(root, { recursive: true, force: true });\n }\n}\n\n/**\n * A `ProjectDoc` carrying NO model — the answer for a project that did not\n * parse (`clean: false`) and for one there was nothing to load (`clean: true`,\n * empty errors).\n *\n * Exported for `./batch.ts`, which answers about many projects at once and\n * must answer each of them in exactly this shape: a second literal there would\n * be a second chance to spell `deployment` as `undefined` and turn \"this\n * project declares no topology\" into a crash in every reader.\n */\nexport function emptyProject(errors: Map<string, LikeC4Error[]>, clean: boolean): ProjectDoc {\n return {\n errors,\n clean,\n elements: [],\n relationships: [],\n views: [],\n viewIds: [],\n globalStyles: [],\n deployment: NO_DEPLOYMENT,\n };\n}\n\n/**\n * Everything loam reads out of a project that parsed, from LikeC4's parsed\n * stage — parsed, never computed, for the reason `../likec4.ts` records.\n *\n * Exported beside `emptyProject` and for the same reason: `./batch.ts` builds\n * one of these per clean project, and an adapter wired into one loader and not\n * the other reads as an absence rather than an error — the failure shape\n * test/likec4-batch-parity.test.ts was written to catch.\n */\nexport function cleanProjectDoc(model: ReadableModel): ProjectDoc {\n return { errors: new Map(), clean: true, ...readParsedModel(model) };\n}\n\n/**\n * Diagnostics split per DOCUMENT inside one project, each error's\n * `sourceFsPath` rewritten to the document's real absolute path.\n *\n * `../workspace.ts`'s `groupErrors` keys on the project folder segment, which\n * cannot work here — every document shares one project. This keys on the staged\n * relative path instead, longest first so `usecases/checkout.likec4` is matched\n * before a hypothetical `checkout.likec4` at the root. An error nobody can\n * attribute throws for the same reason it does there: a dropped error grades\n * its document clean, which is failing open in the fleet gate.\n *\n * It takes the diagnostics rather than the workspace so `./batch.ts` can hand\n * it ONE project's share of a multi-project `getErrors()` — the two questions\n * compose (which project, then which document) exactly because neither is\n * baked in here. Both callers' diagnostics pass through `distinctErrors` here,\n * once, as every loader's do (`../likec4.ts` says why).\n */\nexport function groupErrorsByRel(\n errors: readonly WorkspaceError[],\n byRel: Map<string, string>,\n): Map<string, LikeC4Error[]> {\n const rels = [...byRel.keys()].sort((a, b) => b.length - a.length);\n const grouped = new Map<string, LikeC4Error[]>();\n for (const err of distinctErrors(errors)) {\n const spelled = err.sourceFsPath.split(/[\\\\/]/).join(\"/\");\n const rel = rels.find((candidate) => spelled.endsWith(`/${candidate}`) || spelled === candidate);\n if (rel === undefined) {\n throw new Error(`unattributable LikeC4 error in project workspace: ${err.message}`);\n }\n const real = byRel.get(rel)!;\n const list = grouped.get(real) ?? [];\n list.push({ ...err, sourceFsPath: real });\n grouped.set(real, list);\n }\n return grouped;\n}\n\n/**\n * A `ProjectDoc` in the shape every existing landscape reader already takes.\n *\n * The per-file error map flattens into one list, which loses nothing: each\n * error still carries the real path of the document that raised it, so a\n * finding can name the file even though the list is flat. Sorted by that path\n * so a project with two broken documents reports them in a stable order —\n * `getErrors()` returns whatever order the workspace scan produced.\n *\n * The model travels only when the project is clean, which is loam's standing\n * rule (`errors mean no model`) applied at project altitude: a use-case file\n * with an unresolved element leaves the landscape unusable, and pretending\n * otherwise would grade a fleet against half a map.\n */\nexport function asLoadedDoc(doc: ProjectDoc): LoadedDoc {\n const errors = [...doc.errors]\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .flatMap(([, list]) => list);\n if (!doc.clean) return { errors, elements: [], relationships: [] };\n return {\n errors,\n ...(doc.specification === undefined ? {} : { specification: doc.specification }),\n views: doc.views,\n viewIds: doc.viewIds,\n globalStyles: doc.globalStyles,\n deployment: doc.deployment,\n elements: doc.elements,\n relationships: doc.relationships,\n };\n}\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,7 +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 } from "../../kernel/ids/fqn/ancestors.js";
|
|
17
|
+
import { ancestorIds, properAncestorIds } from "../../kernel/ids/fqn/ancestors.js";
|
|
18
18
|
import { declaredService } from "../../kernel/ids/service.js";
|
|
19
19
|
import {} from "../likec4.js";
|
|
20
20
|
/**
|
|
@@ -147,4 +147,28 @@ export function resolverFor(elements, known) {
|
|
|
147
147
|
perSet.set(known, resolver);
|
|
148
148
|
return resolver;
|
|
149
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
|
+
}
|
|
150
174
|
//# sourceMappingURL=service.js.map
|
|
@@ -1 +1 @@
|
|
|
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 } 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"]}
|
|
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"]}
|
|
@@ -22,6 +22,20 @@
|
|
|
22
22
|
function foldName(name, kind) {
|
|
23
23
|
return `${kind}_${name.replace(/[.-]/g, "_")}`;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The element id loam SUGGESTS for a service no map draws yet — this seed's
|
|
27
|
+
* own convention, exported so it is the only one. The adopt brief and the
|
|
28
|
+
* `loam new` delta used to camel-case the directory name instead, so a seeded
|
|
29
|
+
* fleet received three spellings of one box (`svc_payment_service` on the
|
|
30
|
+
* map, `paymentService` in every brief and scaffold), and a delta that reused
|
|
31
|
+
* the scaffold's guess never lined up with the map it was told to match. The
|
|
32
|
+
* id stays cosmetic — `metadata { service '<id>' }` is the join — but a
|
|
33
|
+
* suggestion gets pasted, so it has to be one spelling, and the keyword-proof
|
|
34
|
+
* one above is the one already on disk in every seeded repo.
|
|
35
|
+
*/
|
|
36
|
+
export function serviceElementId(service) {
|
|
37
|
+
return foldName(service, "svc");
|
|
38
|
+
}
|
|
25
39
|
/** Element ids for every declared name (services ∪ externals), collision-suffixed deterministically. */
|
|
26
40
|
function elementIds(seed) {
|
|
27
41
|
const ids = new Map();
|
|
@@ -57,7 +71,7 @@ export function renderLandscape(seed) {
|
|
|
57
71
|
"// As contracts arrive, give each call the operationId it uses, so",
|
|
58
72
|
"// requirements, C4 and OpenAPI can be cross-checked:",
|
|
59
73
|
"//",
|
|
60
|
-
"//
|
|
74
|
+
"// svc_checkout -> svc_payment_service 'Calls authorizePayment' {",
|
|
61
75
|
"// metadata { op 'authorizePayment' }",
|
|
62
76
|
"// }",
|
|
63
77
|
"",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../../../src/core/c4/seed/template.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * FleetSeed → landscape DSL body: a pure, deterministic function — identical\n * fleet.yaml, identical bytes, on any machine — because the stamp\n * (`./stamp.ts`) digests the output and a nondeterministic templater would\n * read as hand-edited on the next run.\n *\n * The DSL shape is deliberately minimal: one bound `softwareSystem` per\n * service (the `metadata { service '<id>' }` binding is what every loam join\n * reads — element ids are cosmetic), `#external` systems for the externals,\n * and PLAIN edges. No operationIds: those arrive with contracts, and a\n * templated guess would be the extractor this command exists not to be.\n */\nimport type { FleetSeed } from \"./fleet-file.js\";\n\n/** Which kind of declared name an element id is being built for. */\ntype ElementKind = \"svc\" | \"ext\";\n\n/**\n * A LikeC4 identifier for a declared name, mechanically: `.` and `-` become\n * `_`, and the result takes its kind's prefix — `svc_` for a service, `ext_`\n * for an external.\n *\n * The prefix is not decoration, and not only about the digit-initial case\n * (LikeC4 identifiers are letter/underscore-initial). LikeC4's KEYWORDS are\n * bare lowercase words — `notes`, `tag`, `view`, `metadata`, `style`, `link`,\n * `import`, `description`, `size`, `line`, `this` and some thirty more — and a\n * service legally named `notes` emitted as a bare `notes = softwareSystem`\n * does not parse. The self-check would catch that, but only to refuse a fleet\n * whose fault it is not, under `internal`, with no fix to offer. No keyword\n * contains `_`, so a prefixed identifier is PROVABLY not one — at this LikeC4\n * version and at every later one. A reserved-word list here would be the same\n * bug again, rotting the first time the language grew a word.\n *\n * The fold is not injective — `a-b` and `a.b` both give `a_b` — so residual\n * collisions take a deterministic numeric suffix in sorted processing order.\n * Aesthetics do not matter here: the binding metadata, not the element id, is\n * the element's identity to loam.\n */\nfunction foldName(name: string, kind: ElementKind): string {\n return `${kind}_${name.replace(/[.-]/g, \"_\")}`;\n}\n\n/** Element ids for every declared name (services ∪ externals), collision-suffixed deterministically. */\nfunction elementIds(seed: FleetSeed): Map<string, string> {\n const ids = new Map<string, string>();\n const used = new Set<string>();\n const named: Array<readonly [string, ElementKind]> = [\n ...[...seed.services]\n .map((s) => s.id as string)\n .sort()\n .map((n) => [n, \"svc\"] as const),\n ...[...seed.externals].sort().map((n) => [n, \"ext\"] as const),\n ];\n for (const [name, kind] of named) {\n let candidate = foldName(name, kind);\n for (let n = 2; used.has(candidate); n += 1) candidate = `${foldName(name, kind)}_${n}`;\n used.add(candidate);\n ids.set(name, candidate);\n }\n return ids;\n}\n\n/** Render the landscape body (unstamped — `sealLandscape` adds line 1). */\nexport function renderLandscape(seed: FleetSeed): string {\n const ids = elementIds(seed);\n // Every name reaching `el` was declared (calls are validated against\n // services ∪ externals before rendering), so the fallback is unreachable —\n // it exists so a future caller cannot get a silent `undefined` into the DSL.\n const el = (name: string): string => ids.get(name) ?? foldName(name, \"svc\");\n\n const lines: string[] = [\n \"// Generated from fleet.yaml by `loam seed` — edit fleet.yaml and re-run to\",\n \"// regenerate. Hand edits make this file yours: the stamp on line 1 is how\",\n \"// seed tells, and it will then refuse to touch the file again.\",\n \"//\",\n \"// As contracts arrive, give each call the operationId it uses, so\",\n \"// requirements, C4 and OpenAPI can be cross-checked:\",\n \"//\",\n \"// checkout -> paymentService 'Authorizes' {\",\n \"// metadata { op 'authorizePayment' }\",\n \"// }\",\n \"\",\n \"specification {\",\n \" element person\",\n \" element softwareSystem\",\n \" element container\",\n \" element database\",\n \"\",\n \" // #platform keeps ubiquitous infrastructure out of the fleet view;\",\n \" // #external marks somebody else's system, so validate does not ask for a\",\n \" // services/ directory nobody owes. A tag must be declared before it is\",\n \" // used, so both are declared here whether or not this fleet uses them.\",\n \" tag platform\",\n \" tag external\",\n \"}\",\n \"\",\n \"model {\",\n ];\n\n const services = [...seed.services].sort((a, b) => (a.id < b.id ? -1 : 1));\n for (const s of services) {\n lines.push(` ${el(s.id)} = softwareSystem '${s.id}' {`);\n lines.push(` metadata { service '${s.id}' }`);\n lines.push(\" }\");\n }\n const externals = [...seed.externals].sort();\n for (const x of externals) {\n lines.push(` ${el(x)} = softwareSystem '${x}' {`);\n lines.push(\" #external\");\n lines.push(\" }\");\n }\n const calls = [...seed.calls].sort((a, b) =>\n a.from === b.from ? (a.to < b.to ? -1 : 1) : a.from < b.from ? -1 : 1,\n );\n if (calls.length > 0) lines.push(\"\");\n for (const c of calls) lines.push(` ${el(c.from)} -> ${el(c.to)}`);\n lines.push(\"}\");\n\n // The views block below is DUPLICATED from core/docs.ts's LANDSCAPE_STUB\n // (two copies — extract on a third, per the house rule). The tripwire in\n // test/seed.test.ts compares the two blocks BYTE FOR BYTE, comments and\n // wrapping included, so whichever copy drifts fails the suite — a\n // view-names-only compare let a corrected predicate land in one copy and\n // leave every seeded fleet map rendering the old one.\n // The emitted comment speaks to the docs repo's reader, not to loam's:\n // internal paths mean nothing where this file lands.\n lines.push(\n \"\",\n \"// The views are for LikeC4's own tooling — loam reads the model and never\",\n \"// a view. Render with `npx likec4 start` from the docs repo root. Tag\",\n \"// ubiquitous infrastructure #platform to keep the fleet view readable.\",\n \"views {\",\n \" view fleet {\",\n \" include *\",\n \" exclude element.tag = #platform\",\n \" }\",\n \" // The obvious spelling — `include element.tag = #platform` — draws the\",\n \" // platform boxes with NO edges and no consumers. The predicate that works is\",\n \" // the relationship form below.\",\n \" view platform {\",\n \" include * -> element.tag = #platform\",\n \" }\",\n \"}\",\n );\n return lines.join(\"\\n\") + \"\\n\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../../../src/core/c4/seed/template.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * FleetSeed → landscape DSL body: a pure, deterministic function — identical\n * fleet.yaml, identical bytes, on any machine — because the stamp\n * (`./stamp.ts`) digests the output and a nondeterministic templater would\n * read as hand-edited on the next run.\n *\n * The DSL shape is deliberately minimal: one bound `softwareSystem` per\n * service (the `metadata { service '<id>' }` binding is what every loam join\n * reads — element ids are cosmetic), `#external` systems for the externals,\n * and PLAIN edges. No operationIds: those arrive with contracts, and a\n * templated guess would be the extractor this command exists not to be.\n */\nimport type { FleetSeed } from \"./fleet-file.js\";\n\n/** Which kind of declared name an element id is being built for. */\ntype ElementKind = \"svc\" | \"ext\";\n\n/**\n * A LikeC4 identifier for a declared name, mechanically: `.` and `-` become\n * `_`, and the result takes its kind's prefix — `svc_` for a service, `ext_`\n * for an external.\n *\n * The prefix is not decoration, and not only about the digit-initial case\n * (LikeC4 identifiers are letter/underscore-initial). LikeC4's KEYWORDS are\n * bare lowercase words — `notes`, `tag`, `view`, `metadata`, `style`, `link`,\n * `import`, `description`, `size`, `line`, `this` and some thirty more — and a\n * service legally named `notes` emitted as a bare `notes = softwareSystem`\n * does not parse. The self-check would catch that, but only to refuse a fleet\n * whose fault it is not, under `internal`, with no fix to offer. No keyword\n * contains `_`, so a prefixed identifier is PROVABLY not one — at this LikeC4\n * version and at every later one. A reserved-word list here would be the same\n * bug again, rotting the first time the language grew a word.\n *\n * The fold is not injective — `a-b` and `a.b` both give `a_b` — so residual\n * collisions take a deterministic numeric suffix in sorted processing order.\n * Aesthetics do not matter here: the binding metadata, not the element id, is\n * the element's identity to loam.\n */\nfunction foldName(name: string, kind: ElementKind): string {\n return `${kind}_${name.replace(/[.-]/g, \"_\")}`;\n}\n\n/**\n * The element id loam SUGGESTS for a service no map draws yet — this seed's\n * own convention, exported so it is the only one. The adopt brief and the\n * `loam new` delta used to camel-case the directory name instead, so a seeded\n * fleet received three spellings of one box (`svc_payment_service` on the\n * map, `paymentService` in every brief and scaffold), and a delta that reused\n * the scaffold's guess never lined up with the map it was told to match. The\n * id stays cosmetic — `metadata { service '<id>' }` is the join — but a\n * suggestion gets pasted, so it has to be one spelling, and the keyword-proof\n * one above is the one already on disk in every seeded repo.\n */\nexport function serviceElementId(service: string): string {\n return foldName(service, \"svc\");\n}\n\n/** Element ids for every declared name (services ∪ externals), collision-suffixed deterministically. */\nfunction elementIds(seed: FleetSeed): Map<string, string> {\n const ids = new Map<string, string>();\n const used = new Set<string>();\n const named: Array<readonly [string, ElementKind]> = [\n ...[...seed.services]\n .map((s) => s.id as string)\n .sort()\n .map((n) => [n, \"svc\"] as const),\n ...[...seed.externals].sort().map((n) => [n, \"ext\"] as const),\n ];\n for (const [name, kind] of named) {\n let candidate = foldName(name, kind);\n for (let n = 2; used.has(candidate); n += 1) candidate = `${foldName(name, kind)}_${n}`;\n used.add(candidate);\n ids.set(name, candidate);\n }\n return ids;\n}\n\n/** Render the landscape body (unstamped — `sealLandscape` adds line 1). */\nexport function renderLandscape(seed: FleetSeed): string {\n const ids = elementIds(seed);\n // Every name reaching `el` was declared (calls are validated against\n // services ∪ externals before rendering), so the fallback is unreachable —\n // it exists so a future caller cannot get a silent `undefined` into the DSL.\n const el = (name: string): string => ids.get(name) ?? foldName(name, \"svc\");\n\n const lines: string[] = [\n \"// Generated from fleet.yaml by `loam seed` — edit fleet.yaml and re-run to\",\n \"// regenerate. Hand edits make this file yours: the stamp on line 1 is how\",\n \"// seed tells, and it will then refuse to touch the file again.\",\n \"//\",\n \"// As contracts arrive, give each call the operationId it uses, so\",\n \"// requirements, C4 and OpenAPI can be cross-checked:\",\n \"//\",\n \"// svc_checkout -> svc_payment_service 'Calls authorizePayment' {\",\n \"// metadata { op 'authorizePayment' }\",\n \"// }\",\n \"\",\n \"specification {\",\n \" element person\",\n \" element softwareSystem\",\n \" element container\",\n \" element database\",\n \"\",\n \" // #platform keeps ubiquitous infrastructure out of the fleet view;\",\n \" // #external marks somebody else's system, so validate does not ask for a\",\n \" // services/ directory nobody owes. A tag must be declared before it is\",\n \" // used, so both are declared here whether or not this fleet uses them.\",\n \" tag platform\",\n \" tag external\",\n \"}\",\n \"\",\n \"model {\",\n ];\n\n const services = [...seed.services].sort((a, b) => (a.id < b.id ? -1 : 1));\n for (const s of services) {\n lines.push(` ${el(s.id)} = softwareSystem '${s.id}' {`);\n lines.push(` metadata { service '${s.id}' }`);\n lines.push(\" }\");\n }\n const externals = [...seed.externals].sort();\n for (const x of externals) {\n lines.push(` ${el(x)} = softwareSystem '${x}' {`);\n lines.push(\" #external\");\n lines.push(\" }\");\n }\n const calls = [...seed.calls].sort((a, b) =>\n a.from === b.from ? (a.to < b.to ? -1 : 1) : a.from < b.from ? -1 : 1,\n );\n if (calls.length > 0) lines.push(\"\");\n for (const c of calls) lines.push(` ${el(c.from)} -> ${el(c.to)}`);\n lines.push(\"}\");\n\n // The views block below is DUPLICATED from core/docs.ts's LANDSCAPE_STUB\n // (two copies — extract on a third, per the house rule). The tripwire in\n // test/seed.test.ts compares the two blocks BYTE FOR BYTE, comments and\n // wrapping included, so whichever copy drifts fails the suite — a\n // view-names-only compare let a corrected predicate land in one copy and\n // leave every seeded fleet map rendering the old one.\n // The emitted comment speaks to the docs repo's reader, not to loam's:\n // internal paths mean nothing where this file lands.\n lines.push(\n \"\",\n \"// The views are for LikeC4's own tooling — loam reads the model and never\",\n \"// a view. Render with `npx likec4 start` from the docs repo root. Tag\",\n \"// ubiquitous infrastructure #platform to keep the fleet view readable.\",\n \"views {\",\n \" view fleet {\",\n \" include *\",\n \" exclude element.tag = #platform\",\n \" }\",\n \" // The obvious spelling — `include element.tag = #platform` — draws the\",\n \" // platform boxes with NO edges and no consumers. The predicate that works is\",\n \" // the relationship form below.\",\n \" view platform {\",\n \" include * -> element.tag = #platform\",\n \" }\",\n \"}\",\n );\n return lines.join(\"\\n\") + \"\\n\";\n}\n"]}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
import { readFile } from "node:fs/promises";
|
|
33
33
|
import { resolve } from "node:path";
|
|
34
34
|
import { inOrder } from "../../kernel/concurrency.js";
|
|
35
|
-
import { maskSource,
|
|
35
|
+
import { maskSource, topLevelBlocks } from "../source-mask.js";
|
|
36
36
|
/**
|
|
37
37
|
* A kind declaration inside a specification block — `element service`,
|
|
38
38
|
* `element softwareSystem`. Deliberately only `element`: `tag`,
|
|
@@ -68,12 +68,7 @@ export function modelShape(text) {
|
|
|
68
68
|
// module-level-state hazard AGENTS.md names, arriving through a constant that
|
|
69
69
|
// looks immutable.
|
|
70
70
|
const specification = /\bspecification\s*\{/g;
|
|
71
|
-
for (
|
|
72
|
-
const open = match.index + match[0].length - 1;
|
|
73
|
-
if (depthBefore(code, match.index) !== 0)
|
|
74
|
-
continue;
|
|
75
|
-
const close = matchBrace(code, open);
|
|
76
|
-
const body = close === -1 ? code.slice(open + 1) : code.slice(open + 1, close);
|
|
71
|
+
for (const { body } of topLevelBlocks(code, specification)) {
|
|
77
72
|
if (ELEMENT_KIND.test(body))
|
|
78
73
|
return "standalone";
|
|
79
74
|
}
|
|
@@ -108,19 +103,4 @@ export async function readModelShapes(paths) {
|
|
|
108
103
|
const entries = await inOrder(paths, async (path) => [resolve(path), await readModelShape(path)]);
|
|
109
104
|
return new Map(entries);
|
|
110
105
|
}
|
|
111
|
-
/**
|
|
112
|
-
* How many blocks are open at `index` in masked code. A plain count, because
|
|
113
|
-
* the mask has already blanked every brace inside a string or a comment — the
|
|
114
|
-
* one thing that would make counting lie.
|
|
115
|
-
*/
|
|
116
|
-
function depthBefore(code, index) {
|
|
117
|
-
let depth = 0;
|
|
118
|
-
for (let i = 0; i < index; i += 1) {
|
|
119
|
-
if (code[i] === "{")
|
|
120
|
-
depth += 1;
|
|
121
|
-
else if (code[i] === "}")
|
|
122
|
-
depth -= 1;
|
|
123
|
-
}
|
|
124
|
-
return depth;
|
|
125
|
-
}
|
|
126
106
|
//# sourceMappingURL=shape.js.map
|