@ybotok/loam 0.1.0-beta.2 → 0.2.0-alpha.1
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 +2570 -61
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2362 -245
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +82 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/agent/agents-stamp.js +155 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/asyncapi/read.js +239 -0
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +59 -55
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +132 -77
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -291
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -254
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1591
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -343
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2065
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js +0 -68
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/brief.js +0 -733
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -559
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -479
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -720
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -436
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -481
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1104
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The architecture spec axis — the grammar and resolution of `Covers:` — and
|
|
3
|
+
* the same endpoint join, reused, for the steps of a use case.
|
|
4
|
+
*
|
|
5
|
+
* A business spec will never mention the transactional outbox; that is
|
|
6
|
+
* architecture, and it lives in `arch.spec.md` — same requirement/scenario
|
|
7
|
+
* grammar as spec.md, same delta algebra, same merge. What is new is the
|
|
8
|
+
* `Covers:` line: where a business requirement declares the OPERATIONS it
|
|
9
|
+
* governs, an architecture requirement declares the MODEL OBJECTS its
|
|
10
|
+
* scenarios exercise, so coverage can be derived mechanically instead of
|
|
11
|
+
* trusted to an author who was never going to write the outbox down.
|
|
12
|
+
*
|
|
13
|
+
* Three entry forms, resolved against the documents loam already reads:
|
|
14
|
+
*
|
|
15
|
+
* paymentService.db a C4 element id — resolved against the
|
|
16
|
+
* in-scope elements the way every check joins
|
|
17
|
+
* them: the id itself, or the service the
|
|
18
|
+
* element stands for (binding, then title);
|
|
19
|
+
* paymentService -> kafka an edge — each side resolved the same way,
|
|
20
|
+
* against the declared relationships;
|
|
21
|
+
* alert:<id> / sli:<id> a health signal — ids `health.yaml`
|
|
22
|
+
* declares (core/vocabulary/health.ts).
|
|
23
|
+
*
|
|
24
|
+
* Resolution never reads code. An entry that resolves to nothing is the typo
|
|
25
|
+
* guard (`covers.unknown`, warn); the emitters live with the other validate
|
|
26
|
+
* checks, this module owns only the grammar and the matching.
|
|
27
|
+
*
|
|
28
|
+
* `attributeStep` at the foot of the file answers the same shape of question for
|
|
29
|
+
* a different axis. A `dynamic view`'s hop says "web talks to orders"; which
|
|
30
|
+
* declared relationship backs that hop, and what that relationship says the
|
|
31
|
+
* operation is, is the join `coversEdge` already performs — same two tiers, same
|
|
32
|
+
* cached `serviceResolver`, same "an endpoint names an element or the service it
|
|
33
|
+
* stands for" rule. It lives here rather than beside the view reader in
|
|
34
|
+
* `parsed/` because a second copy of that join is the copy that drifts, and
|
|
35
|
+
* because `parsed/dynamic-views.ts` may read `$data` and nothing else
|
|
36
|
+
* (docs/DESIGN.md rule 26) — it has no business holding the model join.
|
|
37
|
+
*/
|
|
38
|
+
import {} from "./likec4.js";
|
|
39
|
+
import { elementService, serviceResolver } from "./resolve/service.js";
|
|
40
|
+
/** Parse one comma-separated `Covers:` entry into its form. Never fails: an
|
|
41
|
+
* unclassifiable string is an element entry that will not resolve, and the
|
|
42
|
+
* covers.unknown message is a better diagnosis than a parse refusal. */
|
|
43
|
+
export function parseCoversEntry(raw) {
|
|
44
|
+
const alert = /^alert:\s*(.+)$/.exec(raw);
|
|
45
|
+
if (alert)
|
|
46
|
+
return { form: "alert", id: alert[1].trim(), raw };
|
|
47
|
+
const sli = /^sli:\s*(.+)$/.exec(raw);
|
|
48
|
+
if (sli)
|
|
49
|
+
return { form: "sli", id: sli[1].trim(), raw };
|
|
50
|
+
const edge = /^(.+?)\s*->\s*(.+)$/.exec(raw);
|
|
51
|
+
if (edge)
|
|
52
|
+
return { form: "edge", source: edge[1].trim(), target: edge[2].trim(), raw };
|
|
53
|
+
return { form: "element", id: raw, raw };
|
|
54
|
+
}
|
|
55
|
+
/** Does an element entry name this element? Its id, or the service it stands for. */
|
|
56
|
+
function namesElement(name, e) {
|
|
57
|
+
return e.id === name || elementService(e) === name;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* One `id -> service` resolver per document, not one per lookup.
|
|
61
|
+
*
|
|
62
|
+
* `serviceOf` builds a fresh id map on every call, and this axis calls it twice
|
|
63
|
+
* for every relationship in scope — inside loops that already run over every
|
|
64
|
+
* relationship a service can see. `serviceResolver`'s own doc says it is "built
|
|
65
|
+
* once per document and shared"; this is where the Covers axis keeps that
|
|
66
|
+
* promise, since `coversEdge` is exported and its callers hold only the element
|
|
67
|
+
* array.
|
|
68
|
+
*
|
|
69
|
+
* A module-level cache is normally the wrong shape here, because a command runs
|
|
70
|
+
* against whatever directory it was invoked in. This one is safe: the key is the
|
|
71
|
+
* per-invocation `Elem[]` a parse produced, its value is a pure function of that
|
|
72
|
+
* key alone, and nothing in it is derived from the working directory — so two
|
|
73
|
+
* runs can never see each other's answer, and the entry dies with the document.
|
|
74
|
+
* Nothing mutates an `Elem[]` after parse.
|
|
75
|
+
*
|
|
76
|
+
* Two tiers because the fleet set is part of the answer: the same document
|
|
77
|
+
* resolves differently with and without `known`, so a cache keyed on the
|
|
78
|
+
* elements alone handed a with-fleet caller the without-fleet resolver.
|
|
79
|
+
* Callers build ONE set per run and pass the same instance through, which is
|
|
80
|
+
* what lets the inner WeakMap key on the set's identity.
|
|
81
|
+
*/
|
|
82
|
+
const RESOLVERS = new WeakMap();
|
|
83
|
+
const FLEET_RESOLVERS = new WeakMap();
|
|
84
|
+
function resolverFor(elements, known) {
|
|
85
|
+
if (known === undefined) {
|
|
86
|
+
const cached = RESOLVERS.get(elements);
|
|
87
|
+
if (cached !== undefined)
|
|
88
|
+
return cached;
|
|
89
|
+
const resolver = serviceResolver(elements);
|
|
90
|
+
RESOLVERS.set(elements, resolver);
|
|
91
|
+
return resolver;
|
|
92
|
+
}
|
|
93
|
+
let perSet = FLEET_RESOLVERS.get(elements);
|
|
94
|
+
if (perSet === undefined) {
|
|
95
|
+
perSet = new WeakMap();
|
|
96
|
+
FLEET_RESOLVERS.set(elements, perSet);
|
|
97
|
+
}
|
|
98
|
+
const cached = perSet.get(known);
|
|
99
|
+
if (cached !== undefined)
|
|
100
|
+
return cached;
|
|
101
|
+
const resolver = serviceResolver(elements, known);
|
|
102
|
+
perSet.set(known, resolver);
|
|
103
|
+
return resolver;
|
|
104
|
+
}
|
|
105
|
+
/** Does an edge entry's side name this relationship endpoint? */
|
|
106
|
+
function namesEndpoint(name, endpointId, elements, known) {
|
|
107
|
+
return endpointId === name || resolverFor(elements, known)(endpointId) === name;
|
|
108
|
+
}
|
|
109
|
+
/** Does a Covers entry match this element? (Only element entries can.) */
|
|
110
|
+
export function coversElement(entry, e) {
|
|
111
|
+
return entry.form === "element" && namesElement(entry.id, e);
|
|
112
|
+
}
|
|
113
|
+
/** Does a Covers entry match this relationship? Endpoints resolved within `elements`, through `known` where the caller has the fleet. */
|
|
114
|
+
export function coversEdge(entry, r, elements, known) {
|
|
115
|
+
return (entry.form === "edge" &&
|
|
116
|
+
namesEndpoint(entry.source, r.source, elements, known) &&
|
|
117
|
+
namesEndpoint(entry.target, r.target, elements, known));
|
|
118
|
+
}
|
|
119
|
+
/** Does the entry resolve to ANYTHING in scope? False is `covers.unknown`. */
|
|
120
|
+
export function entryResolves(entry, scope) {
|
|
121
|
+
switch (entry.form) {
|
|
122
|
+
case "alert":
|
|
123
|
+
return scope.health.alerts.includes(entry.id);
|
|
124
|
+
case "sli":
|
|
125
|
+
return scope.health.slis.includes(entry.id);
|
|
126
|
+
case "edge":
|
|
127
|
+
return scope.relationships.some((r) => coversEdge(entry, r, scope.elements, scope.known));
|
|
128
|
+
case "element":
|
|
129
|
+
return scope.elements.some((e) => coversElement(entry, e));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** Ids a covers.unknown hint may offer for a mistyped entry — always real ones. */
|
|
133
|
+
export function coversCandidates(entry, scope) {
|
|
134
|
+
switch (entry.form) {
|
|
135
|
+
case "alert":
|
|
136
|
+
return closeIds(entry.id, scope.health.alerts).map((id) => `alert:${id}`);
|
|
137
|
+
case "sli":
|
|
138
|
+
return closeIds(entry.id, scope.health.slis).map((id) => `sli:${id}`);
|
|
139
|
+
case "edge":
|
|
140
|
+
// Cheap on purpose: no pairwise edge fuzzing — the sides are element
|
|
141
|
+
// names, so the element hint is the useful one.
|
|
142
|
+
return [];
|
|
143
|
+
case "element":
|
|
144
|
+
return closeIds(entry.id, [...new Set(scope.elements.map((e) => e.id))]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Existing ids near a misspelling — substring containment either way, else a
|
|
149
|
+
* shared 3-character prefix. Deliberately dumb: no fuzzy library for one hint,
|
|
150
|
+
* and every id offered is real, so the hint can never point at the typo itself.
|
|
151
|
+
* (Moved here from validate.ts once `covers.unknown` became its second user.)
|
|
152
|
+
*/
|
|
153
|
+
export function closeIds(typo, ids) {
|
|
154
|
+
const t = typo.toLowerCase();
|
|
155
|
+
return ids
|
|
156
|
+
.filter((id) => {
|
|
157
|
+
const i = id.toLowerCase();
|
|
158
|
+
return i.includes(t) || t.includes(i) || (t.length >= 3 && i.startsWith(t.slice(0, 3)));
|
|
159
|
+
})
|
|
160
|
+
.slice(0, 5);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The pair a step's backing relationship is looked up under. This is the half
|
|
164
|
+
* that mis-grades the whole fleet if it is wrong, because a reply arrow is the
|
|
165
|
+
* commonest step in any sequence diagram.
|
|
166
|
+
*
|
|
167
|
+
* MEASURED at the `likec4@1.59.2` pin, and `test/likec4-view-shape.test.ts` holds
|
|
168
|
+
* the raw form: a reply written `a <- b 'reply'` parses as
|
|
169
|
+
* `{source: "b", target: "a", isBackward: true}` — LikeC4 has ALREADY reversed
|
|
170
|
+
* the endpoints and set the flag — while the forward `b -> a` yields that same
|
|
171
|
+
* pair with no `isBackward` key at all (ABSENT, not false). So the endpoints as
|
|
172
|
+
* parsed describe the direction the MESSAGE travels, and the flag records which
|
|
173
|
+
* arrow the author typed.
|
|
174
|
+
*
|
|
175
|
+
* loam attributes a reply to the CALL it answers, so the reversal is undone
|
|
176
|
+
* here. A landscape declares `web -> orders` because that edge is the
|
|
177
|
+
* dependency; a mirror `orders -> web` for each response is not something a
|
|
178
|
+
* fleet writes down. Reading a reply hop under the message's own direction would
|
|
179
|
+
* report every return hop against such a landscape as `unbacked`, and where a
|
|
180
|
+
* fleet does draw the mirror edge it would file the two halves of one call under
|
|
181
|
+
* two operations — the call under `op 'createOrder'`, its reply under none.
|
|
182
|
+
*
|
|
183
|
+
* That is deliberately NOT what LikeC4's own computed stage does, and the
|
|
184
|
+
* difference is measured rather than assumed: that stage resolves a backward
|
|
185
|
+
* step's `relations` from the UNFLIPPED pair, so at this pin a `web <- orders`
|
|
186
|
+
* step computes to `relations: []` when only `web -> orders` exists, and to the
|
|
187
|
+
* mirror edge when only `orders -> web` does. It is answering which arrow to
|
|
188
|
+
* draw; loam is answering which operation the hop exercises, and reads what a
|
|
189
|
+
* view DECLARES rather than what it shows — which is why loam never reaches that
|
|
190
|
+
* stage at all (docs/DESIGN.md rule 26, enforced by `scripts/arch-check.mjs`).
|
|
191
|
+
* The consequence to know before changing this: where
|
|
192
|
+
* a fleet declares BOTH directions, the mirror edge is not what backs the reply
|
|
193
|
+
* here — the call is, and the mirror edge backs only a hop drawn forward along
|
|
194
|
+
* it.
|
|
195
|
+
*/
|
|
196
|
+
function callPair(step) {
|
|
197
|
+
return step.isBackward ? { from: step.target, to: step.source } : { from: step.source, to: step.target };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The distinct `op` values across candidates, first-seen order preserved.
|
|
201
|
+
*
|
|
202
|
+
* Absent is a VALUE here, not a gap to skip: a relationship carrying no `op` and
|
|
203
|
+
* one carrying `createOrder` disagree about the operation just as surely as two
|
|
204
|
+
* different ops do, and skipping the absent one would silently promote that
|
|
205
|
+
* disagreement to a confident answer. `includes` rather than a `Set` because a
|
|
206
|
+
* candidate list is the handful of relationships between one pair of endpoints,
|
|
207
|
+
* and it keeps `undefined` comparing the way the rest of this file expects.
|
|
208
|
+
*/
|
|
209
|
+
function distinctOps(rels) {
|
|
210
|
+
const out = [];
|
|
211
|
+
for (const r of rels)
|
|
212
|
+
if (!out.includes(r.op))
|
|
213
|
+
out.push(r.op);
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The verdict one tier's candidates imply, or `undefined` when that tier found
|
|
218
|
+
* nothing and is therefore not the tier that answers.
|
|
219
|
+
*
|
|
220
|
+
* The count that decides is the DISTINCT-OP count, never the candidate count.
|
|
221
|
+
* That distinction is what makes the service tier safe: once a service is drawn
|
|
222
|
+
* as containers, `a.api -> b` and `a.worker -> b` both carrying
|
|
223
|
+
* `op 'createOrder'` are two relationships and one operation, and a verdict read
|
|
224
|
+
* off `rels.length` would convict a perfectly consistent model of contesting
|
|
225
|
+
* itself.
|
|
226
|
+
*/
|
|
227
|
+
function verdictOn(pair, tier, rels) {
|
|
228
|
+
if (rels.length === 0)
|
|
229
|
+
return undefined;
|
|
230
|
+
const ops = distinctOps(rels);
|
|
231
|
+
if (ops.length > 1)
|
|
232
|
+
return { ...pair, verdict: "contested", tier, rels, ops };
|
|
233
|
+
// Length is exactly one here, so the single distinct value is the answer —
|
|
234
|
+
// and `undefined` is a legitimate answer: every candidate agreed that this
|
|
235
|
+
// hop names no operation.
|
|
236
|
+
const [only] = ops;
|
|
237
|
+
return { ...pair, verdict: "attributed", tier, rels, ...(only === undefined ? {} : { op: only }) };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Relationships whose ENDPOINTS resolve to the same two services as the pair —
|
|
241
|
+
* the fallback tier, and the reason a step drawn between two services matches an
|
|
242
|
+
* edge drawn between two containers.
|
|
243
|
+
*
|
|
244
|
+
* Through the module's cached `resolverFor`, not a fresh `serviceResolver`: this
|
|
245
|
+
* runs once per step of every use case in the fleet, and building the id map per
|
|
246
|
+
* step is the cost that comment above `RESOLVERS` exists to prevent.
|
|
247
|
+
*/
|
|
248
|
+
function resolvedTier(pair, scope) {
|
|
249
|
+
const resolve = resolverFor(scope.elements, scope.known);
|
|
250
|
+
const from = resolve(pair.from);
|
|
251
|
+
const to = resolve(pair.to);
|
|
252
|
+
return scope.relationships.filter((r) => resolve(r.source) === from && resolve(r.target) === to);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Which relationship in the model backs one hop of a use case, and what it says
|
|
256
|
+
* the operation is.
|
|
257
|
+
*
|
|
258
|
+
* Two tiers, and the second runs ONLY when the first is empty. That ordering is
|
|
259
|
+
* load-bearing rather than an optimisation: the service tier necessarily
|
|
260
|
+
* re-finds every exact match — an endpoint that matches literally resolves to
|
|
261
|
+
* the same service trivially — so running it unconditionally would let a
|
|
262
|
+
* container-level edge with a different `op` contest a step the model already
|
|
263
|
+
* answers exactly, turning an `attributed` verdict into a `contested` one on a
|
|
264
|
+
* document nobody changed.
|
|
265
|
+
*
|
|
266
|
+
* Relationship `kind` is deliberately not part of the match, and that is
|
|
267
|
+
* measured too: at this pin `a -[http]-> b` without a `relationship http` in the
|
|
268
|
+
* `specification` block is "Could not resolve reference to RelationshipKind
|
|
269
|
+
* named 'http'" — a parse error, which under loam's standing rule means no model
|
|
270
|
+
* at all — and `test/helpers/harness.ts`'s own `LANDSCAPE` declares no
|
|
271
|
+
* relationship kinds. So narrowing by kind would mean a fix message asking an
|
|
272
|
+
* author to write a line their document does not parse.
|
|
273
|
+
*/
|
|
274
|
+
export function attributeStep(step, scope) {
|
|
275
|
+
const pair = callPair(step);
|
|
276
|
+
const exact = scope.relationships.filter((r) => r.source === pair.from && r.target === pair.to);
|
|
277
|
+
const declared = verdictOn(pair, 1, exact);
|
|
278
|
+
if (declared !== undefined)
|
|
279
|
+
return declared;
|
|
280
|
+
return verdictOn(pair, 2, resolvedTier(pair, scope)) ?? { ...pair, verdict: "unbacked" };
|
|
281
|
+
}
|
|
282
|
+
//# sourceMappingURL=arch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arch.js","sourceRoot":"","sources":["../../../src/core/c4/arch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architecture spec axis — the grammar and resolution of `Covers:` — and\n * the same endpoint join, reused, for the steps of a use case.\n *\n * A business spec will never mention the transactional outbox; that is\n * architecture, and it lives in `arch.spec.md` — same requirement/scenario\n * grammar as spec.md, same delta algebra, same merge. What is new is the\n * `Covers:` line: where a business requirement declares the OPERATIONS it\n * governs, an architecture requirement declares the MODEL OBJECTS its\n * scenarios exercise, so coverage can be derived mechanically instead of\n * trusted to an author who was never going to write the outbox down.\n *\n * Three entry forms, resolved against the documents loam already reads:\n *\n * paymentService.db a C4 element id — resolved against the\n * in-scope elements the way every check joins\n * them: the id itself, or the service the\n * element stands for (binding, then title);\n * paymentService -> kafka an edge — each side resolved the same way,\n * against the declared relationships;\n * alert:<id> / sli:<id> a health signal — ids `health.yaml`\n * declares (core/vocabulary/health.ts).\n *\n * Resolution never reads code. An entry that resolves to nothing is the typo\n * guard (`covers.unknown`, warn); the emitters live with the other validate\n * checks, this module owns only the grammar and the matching.\n *\n * `attributeStep` at the foot of the file answers the same shape of question for\n * a different axis. A `dynamic view`'s hop says \"web talks to orders\"; which\n * declared relationship backs that hop, and what that relationship says the\n * operation is, is the join `coversEdge` already performs — same two tiers, same\n * cached `serviceResolver`, same \"an endpoint names an element or the service it\n * stands for\" rule. It lives here rather than beside the view reader in\n * `parsed/` because a second copy of that join is the copy that drifts, and\n * because `parsed/dynamic-views.ts` may read `$data` and nothing else\n * (docs/DESIGN.md rule 26) — it has no business holding the model join.\n */\nimport { type Elem, type Rel } from \"./likec4.js\";\nimport { elementService, serviceResolver } from \"./resolve/service.js\";\nimport type { ParsedStep } from \"./parsed/dynamic-views.js\";\nimport type { HealthIds } from \"../vocabulary/health.js\";\n\nexport type CoversEntry =\n | { form: \"element\"; id: string; raw: string }\n | { form: \"edge\"; source: string; target: string; raw: string }\n | { form: \"alert\" | \"sli\"; id: string; raw: string };\n\n/** Parse one comma-separated `Covers:` entry into its form. Never fails: an\n * unclassifiable string is an element entry that will not resolve, and the\n * covers.unknown message is a better diagnosis than a parse refusal. */\nexport function parseCoversEntry(raw: string): CoversEntry {\n const alert = /^alert:\\s*(.+)$/.exec(raw);\n if (alert) return { form: \"alert\", id: alert[1]!.trim(), raw };\n const sli = /^sli:\\s*(.+)$/.exec(raw);\n if (sli) return { form: \"sli\", id: sli[1]!.trim(), raw };\n const edge = /^(.+?)\\s*->\\s*(.+)$/.exec(raw);\n if (edge) return { form: \"edge\", source: edge[1]!.trim(), target: edge[2]!.trim(), raw };\n return { form: \"element\", id: raw, raw };\n}\n\n/**\n * What `Covers:` entries resolve against: every element and relationship in\n * view (a service's own model plus the landscape; for a feature, the delta\n * plus both), and the health ids of the service whose spec is being read.\n */\nexport interface CoverageScope {\n elements: Elem[];\n relationships: Rel[];\n health: HealthIds;\n /**\n * The enumerated fleet (plus the feature's own `specs/` names, where the\n * caller has them). The Covers matcher resolves edge ENDPOINTS with it, and\n * it must be the SAME set the caller's own findings resolve with: the finding\n * says \"write `Covers: checkout-web -> payment-service`\" with the fleet in\n * hand, and a matcher resolving without it answered that exact line with\n * `covers.unknown` whenever the edge landed on a modelled container.\n */\n known?: ReadonlySet<string>;\n}\n\n/** Does an element entry name this element? Its id, or the service it stands for. */\nfunction namesElement(name: string, e: Elem): boolean {\n return e.id === name || elementService(e) === name;\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 the Covers axis keeps that\n * promise, since `coversEdge` is exported and its callers hold only the element\n * 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\nfunction 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/** Does an edge entry's side name this relationship endpoint? */\nfunction namesEndpoint(\n name: string,\n endpointId: string,\n elements: Elem[],\n known?: ReadonlySet<string>,\n): boolean {\n return endpointId === name || resolverFor(elements, known)(endpointId) === name;\n}\n\n/** Does a Covers entry match this element? (Only element entries can.) */\nexport function coversElement(entry: CoversEntry, e: Elem): boolean {\n return entry.form === \"element\" && namesElement(entry.id, e);\n}\n\n/** Does a Covers entry match this relationship? Endpoints resolved within `elements`, through `known` where the caller has the fleet. */\nexport function coversEdge(entry: CoversEntry, r: Rel, elements: Elem[], known?: ReadonlySet<string>): boolean {\n return (\n entry.form === \"edge\" &&\n namesEndpoint(entry.source, r.source, elements, known) &&\n namesEndpoint(entry.target, r.target, elements, known)\n );\n}\n\n/** Does the entry resolve to ANYTHING in scope? False is `covers.unknown`. */\nexport function entryResolves(entry: CoversEntry, scope: CoverageScope): boolean {\n switch (entry.form) {\n case \"alert\":\n return scope.health.alerts.includes(entry.id);\n case \"sli\":\n return scope.health.slis.includes(entry.id);\n case \"edge\":\n return scope.relationships.some((r) => coversEdge(entry, r, scope.elements, scope.known));\n case \"element\":\n return scope.elements.some((e) => coversElement(entry, e));\n }\n}\n\n/** Ids a covers.unknown hint may offer for a mistyped entry — always real ones. */\nexport function coversCandidates(entry: CoversEntry, scope: CoverageScope): string[] {\n switch (entry.form) {\n case \"alert\":\n return closeIds(entry.id, scope.health.alerts).map((id) => `alert:${id}`);\n case \"sli\":\n return closeIds(entry.id, scope.health.slis).map((id) => `sli:${id}`);\n case \"edge\":\n // Cheap on purpose: no pairwise edge fuzzing — the sides are element\n // names, so the element hint is the useful one.\n return [];\n case \"element\":\n return closeIds(entry.id, [...new Set(scope.elements.map((e) => e.id))]);\n }\n}\n\n/**\n * Existing ids near a misspelling — substring containment either way, else a\n * shared 3-character prefix. Deliberately dumb: no fuzzy library for one hint,\n * and every id offered is real, so the hint can never point at the typo itself.\n * (Moved here from validate.ts once `covers.unknown` became its second user.)\n */\nexport function closeIds(typo: string, ids: readonly string[]): string[] {\n const t = typo.toLowerCase();\n return ids\n .filter((id) => {\n const i = id.toLowerCase();\n return i.includes(t) || t.includes(i) || (t.length >= 3 && i.startsWith(t.slice(0, 3)));\n })\n .slice(0, 5);\n}\n\n/**\n * The three fields an attribution reads off a step, and no more.\n *\n * Narrower than `ParsedStep` on purpose. Attribution must not depend on the\n * ordinal, the title or the notes — a hop is graded by where it points, never by\n * what the author called it — and a signature that says so is also a signature a\n * test can satisfy with a literal instead of a parsed document. Derived from\n * `ParsedStep` rather than restated, so a rename upstream is a compile error\n * here instead of a second definition that quietly disagrees.\n */\nexport type StepEndpoints = Pick<ParsedStep, \"source\" | \"target\" | \"isBackward\">;\n\n/**\n * What a step is attributed against: the model in view, plus the enumerated\n * fleet where the caller has it.\n *\n * A deliberate subset of `CoverageScope` rather than a reuse of it — an\n * attribution never reads `health`, and demanding one would make every caller\n * fabricate an empty `HealthIds` to ask a question that has nothing to do with\n * alerts. A `CoverageScope` is still structurally assignable here, so a caller\n * already holding the fuller scope passes it through unchanged.\n */\nexport interface StepScope {\n elements: Elem[];\n relationships: Rel[];\n /** As `CoverageScope.known`, for the same reason: one set per run, shared. */\n known?: ReadonlySet<string>;\n}\n\n/** The oriented pair a verdict is about — see `callPair` for which way round. */\ninterface CallPair {\n from: string;\n to: string;\n}\n\n/**\n * What the model says about one hop of a use case.\n *\n * Three variants rather than one record with optional fields, because the fields\n * are not independent: `op` is meaningful only when exactly one operation was\n * implied, `ops` only when more than one was, and an unbacked step has neither\n * and no tier either. Tagged, those states stop being constructible.\n *\n * - `attributed` — the candidates on the matched tier agree, so the hop\n * exercises exactly one operation. `op` is absent when that one agreed\n * operation is *no* operation: the relationships back the hop but none of\n * them carries `metadata { op '…' }`.\n * - `contested` — the candidates disagree, so loam cannot say which operation\n * the hop exercises. `ops` lists the distinct values in candidate order,\n * `undefined` standing for the candidate that declared none.\n * - `unbacked` — nothing in the model backs the hop on either tier. The pair is\n * still carried, because a message about it has to be able to name the edge\n * the author would have to draw.\n *\n * `rels` carries every candidate on the matched tier, not just the first: an\n * `attributed` verdict over two relationships is the normal shape once a service\n * is drawn as containers, and a caller pinning evidence needs all of them.\n */\nexport type StepAttribution =\n | (CallPair & { verdict: \"attributed\"; tier: 1 | 2; rels: readonly Rel[]; op?: string })\n | (CallPair & { verdict: \"contested\"; tier: 1 | 2; rels: readonly Rel[]; ops: readonly (string | undefined)[] })\n | (CallPair & { verdict: \"unbacked\" });\n\n/**\n * The pair a step's backing relationship is looked up under. This is the half\n * that mis-grades the whole fleet if it is wrong, because a reply arrow is the\n * commonest step in any sequence diagram.\n *\n * MEASURED at the `likec4@1.59.2` pin, and `test/likec4-view-shape.test.ts` holds\n * the raw form: a reply written `a <- b 'reply'` parses as\n * `{source: \"b\", target: \"a\", isBackward: true}` — LikeC4 has ALREADY reversed\n * the endpoints and set the flag — while the forward `b -> a` yields that same\n * pair with no `isBackward` key at all (ABSENT, not false). So the endpoints as\n * parsed describe the direction the MESSAGE travels, and the flag records which\n * arrow the author typed.\n *\n * loam attributes a reply to the CALL it answers, so the reversal is undone\n * here. A landscape declares `web -> orders` because that edge is the\n * dependency; a mirror `orders -> web` for each response is not something a\n * fleet writes down. Reading a reply hop under the message's own direction would\n * report every return hop against such a landscape as `unbacked`, and where a\n * fleet does draw the mirror edge it would file the two halves of one call under\n * two operations — the call under `op 'createOrder'`, its reply under none.\n *\n * That is deliberately NOT what LikeC4's own computed stage does, and the\n * difference is measured rather than assumed: that stage resolves a backward\n * step's `relations` from the UNFLIPPED pair, so at this pin a `web <- orders`\n * step computes to `relations: []` when only `web -> orders` exists, and to the\n * mirror edge when only `orders -> web` does. It is answering which arrow to\n * draw; loam is answering which operation the hop exercises, and reads what a\n * view DECLARES rather than what it shows — which is why loam never reaches that\n * stage at all (docs/DESIGN.md rule 26, enforced by `scripts/arch-check.mjs`).\n * The consequence to know before changing this: where\n * a fleet declares BOTH directions, the mirror edge is not what backs the reply\n * here — the call is, and the mirror edge backs only a hop drawn forward along\n * it.\n */\nfunction callPair(step: StepEndpoints): CallPair {\n return step.isBackward ? { from: step.target, to: step.source } : { from: step.source, to: step.target };\n}\n\n/**\n * The distinct `op` values across candidates, first-seen order preserved.\n *\n * Absent is a VALUE here, not a gap to skip: a relationship carrying no `op` and\n * one carrying `createOrder` disagree about the operation just as surely as two\n * different ops do, and skipping the absent one would silently promote that\n * disagreement to a confident answer. `includes` rather than a `Set` because a\n * candidate list is the handful of relationships between one pair of endpoints,\n * and it keeps `undefined` comparing the way the rest of this file expects.\n */\nfunction distinctOps(rels: readonly Rel[]): (string | undefined)[] {\n const out: (string | undefined)[] = [];\n for (const r of rels) if (!out.includes(r.op)) out.push(r.op);\n return out;\n}\n\n/**\n * The verdict one tier's candidates imply, or `undefined` when that tier found\n * nothing and is therefore not the tier that answers.\n *\n * The count that decides is the DISTINCT-OP count, never the candidate count.\n * That distinction is what makes the service tier safe: once a service is drawn\n * as containers, `a.api -> b` and `a.worker -> b` both carrying\n * `op 'createOrder'` are two relationships and one operation, and a verdict read\n * off `rels.length` would convict a perfectly consistent model of contesting\n * itself.\n */\nfunction verdictOn(pair: CallPair, tier: 1 | 2, rels: readonly Rel[]): StepAttribution | undefined {\n if (rels.length === 0) return undefined;\n const ops = distinctOps(rels);\n if (ops.length > 1) return { ...pair, verdict: \"contested\", tier, rels, ops };\n // Length is exactly one here, so the single distinct value is the answer —\n // and `undefined` is a legitimate answer: every candidate agreed that this\n // hop names no operation.\n const [only] = ops;\n return { ...pair, verdict: \"attributed\", tier, rels, ...(only === undefined ? {} : { op: only }) };\n}\n\n/**\n * Relationships whose ENDPOINTS resolve to the same two services as the pair —\n * the fallback tier, and the reason a step drawn between two services matches an\n * edge drawn between two containers.\n *\n * Through the module's cached `resolverFor`, not a fresh `serviceResolver`: this\n * runs once per step of every use case in the fleet, and building the id map per\n * step is the cost that comment above `RESOLVERS` exists to prevent.\n */\nfunction resolvedTier(pair: CallPair, scope: StepScope): Rel[] {\n const resolve = resolverFor(scope.elements, scope.known);\n const from = resolve(pair.from);\n const to = resolve(pair.to);\n return scope.relationships.filter((r) => resolve(r.source) === from && resolve(r.target) === to);\n}\n\n/**\n * Which relationship in the model backs one hop of a use case, and what it says\n * the operation is.\n *\n * Two tiers, and the second runs ONLY when the first is empty. That ordering is\n * load-bearing rather than an optimisation: the service tier necessarily\n * re-finds every exact match — an endpoint that matches literally resolves to\n * the same service trivially — so running it unconditionally would let a\n * container-level edge with a different `op` contest a step the model already\n * answers exactly, turning an `attributed` verdict into a `contested` one on a\n * document nobody changed.\n *\n * Relationship `kind` is deliberately not part of the match, and that is\n * measured too: at this pin `a -[http]-> b` without a `relationship http` in the\n * `specification` block is \"Could not resolve reference to RelationshipKind\n * named 'http'\" — a parse error, which under loam's standing rule means no model\n * at all — and `test/helpers/harness.ts`'s own `LANDSCAPE` declares no\n * relationship kinds. So narrowing by kind would mean a fix message asking an\n * author to write a line their document does not parse.\n */\nexport function attributeStep(step: StepEndpoints, scope: StepScope): StepAttribution {\n const pair = callPair(step);\n const exact = scope.relationships.filter((r) => r.source === pair.from && r.target === pair.to);\n const declared = verdictOn(pair, 1, exact);\n if (declared !== undefined) return declared;\n return verdictOn(pair, 2, resolvedTier(pair, scope)) ?? { ...pair, verdict: \"unbacked\" };\n}\n"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { LikeC4 } from "likec4";
|
|
3
|
+
import { declaredService } from "../kernel/ids/service.js";
|
|
4
|
+
import { readDynamicViews } from "./parsed/dynamic-views.js";
|
|
5
|
+
import { readViewIds } from "./parsed/view-ids.js";
|
|
6
|
+
import { readSpecification } from "./parsed/specification.js";
|
|
7
|
+
import { descText, metaKey } from "./parsed/values.js";
|
|
8
|
+
/** Flatten a LikeC4 model into loam's neutral `Elem`/`Rel` view. */
|
|
9
|
+
export function flattenModel(model) {
|
|
10
|
+
const elements = [...model.elements()].map((e) => {
|
|
11
|
+
// The one crossing between LikeC4's parse output and loam's type system:
|
|
12
|
+
// a `metadata { service '...' }` binding is text somebody wrote, and this
|
|
13
|
+
// is where it acquires a type that says so.
|
|
14
|
+
const bound = metaKey(e.metadata, "service");
|
|
15
|
+
return {
|
|
16
|
+
id: e.id,
|
|
17
|
+
kind: e.kind,
|
|
18
|
+
title: e.title,
|
|
19
|
+
description: descText(e.description),
|
|
20
|
+
...(bound === undefined ? {} : { service: declaredService(bound) }),
|
|
21
|
+
tags: [...(e.tags ?? [])],
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
const relationships = [...model.relationships()].map((r) => ({
|
|
25
|
+
source: r.source.id,
|
|
26
|
+
target: r.target.id,
|
|
27
|
+
// LikeC4 reports an untitled edge as title: null — normalize to the declared `title?: string`.
|
|
28
|
+
title: r.title ?? undefined,
|
|
29
|
+
op: metaKey(r.metadata, "op"),
|
|
30
|
+
publishes: metaKey(r.metadata, "publishes"),
|
|
31
|
+
consumes: metaKey(r.metadata, "consumes"),
|
|
32
|
+
tags: [...(r.tags ?? [])],
|
|
33
|
+
}));
|
|
34
|
+
return { elements, relationships };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Load and validate a single self-contained `.likec4` document, in-process
|
|
38
|
+
* (no external tool, no JVM). Returns validation errors and, if clean, the
|
|
39
|
+
* flattened elements + relationships.
|
|
40
|
+
*/
|
|
41
|
+
export async function loadFile(path) {
|
|
42
|
+
return loadSource(await readFile(path, "utf8"));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* `loadFile` for text that is not (yet) on disk — what the archive merge uses
|
|
46
|
+
* to prove a computed landscape parses BEFORE anything is written.
|
|
47
|
+
*/
|
|
48
|
+
export async function loadSource(src) {
|
|
49
|
+
// LikeC4 owns a Langium workspace and registers a process-exit listener for
|
|
50
|
+
// it. Validation creates many short-lived instances, so every acquired
|
|
51
|
+
// instance must be disposed even on an invalid document or a failed model
|
|
52
|
+
// computation. Its logger is disabled because diagnostics are returned in
|
|
53
|
+
// LoadedDoc instead of being written out-of-band to stderr.
|
|
54
|
+
const likec4 = await LikeC4.fromSource(src, { logger: false });
|
|
55
|
+
try {
|
|
56
|
+
// Assigned, not cast: LikeC4 declares `getErrors()` as a non-nullable array
|
|
57
|
+
// whose element type is structurally wider than ours, so the compiler can
|
|
58
|
+
// check the shape here. The cast was the only thing standing between a
|
|
59
|
+
// dependency renaming `message` and a build that still succeeded — and the
|
|
60
|
+
// `?? []` it carried could never fire.
|
|
61
|
+
const errors = likec4.getErrors();
|
|
62
|
+
if (errors.length > 0) {
|
|
63
|
+
return { errors, elements: [], relationships: [] };
|
|
64
|
+
}
|
|
65
|
+
// `parsedModel`, not `computedModel`: the computed stage RESOLVES every
|
|
66
|
+
// view — expanding its predicates against the model and deriving the
|
|
67
|
+
// ancestor edges a diagram needs (and computing a default view when the
|
|
68
|
+
// document declares none). loam reads what a view DECLARES and never what a
|
|
69
|
+
// view SHOWS (docs/DESIGN.md rule 26), so none of that resolution is ever
|
|
70
|
+
// wanted; it renders nothing. That work is superlinear in the number of
|
|
71
|
+
// RELATIONSHIPS, so a landscape at fleet shape turned `loam list` from under
|
|
72
|
+
// a second into minutes. The elements and relationships below are identical
|
|
73
|
+
// either way. The `await` is load-bearing for anything reading `$data`: it is
|
|
74
|
+
// undefined on the unresolved promise.
|
|
75
|
+
const model = (await likec4.parsedModel());
|
|
76
|
+
return {
|
|
77
|
+
errors,
|
|
78
|
+
specification: readSpecification(model.specification),
|
|
79
|
+
views: readDynamicViews(model),
|
|
80
|
+
viewIds: readViewIds(model),
|
|
81
|
+
...flattenModel(model),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await likec4.dispose();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=likec4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"likec4.js","sourceRoot":"","sources":["../../../src/core/c4/likec4.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { LikeC4 } from \"likec4\";\nimport { declaredService, type DeclaredService } from \"../kernel/ids/service.js\";\nimport { readDynamicViews, type ParsedView } from \"./parsed/dynamic-views.js\";\nimport { readViewIds, type ViewIdClaim } from \"./parsed/view-ids.js\";\nimport { readSpecification, type DocSpecification } from \"./parsed/specification.js\";\nimport { descText, metaKey } from \"./parsed/values.js\";\n\n/** A parse/validation issue reported by LikeC4. */\nexport interface LikeC4Error {\n message: string;\n line?: number;\n sourceFsPath?: string;\n}\n\n/** loam-neutral element view (flattened from the LikeC4 parsed model). */\nexport interface Elem {\n id: string;\n kind: string;\n title: string;\n description?: string;\n /**\n * The `services/<id>` directory this element stands for, from the element's\n * `metadata { service '...' }`. Absent on the elements nobody has bound —\n * see `elementService` for the fallback.\n */\n service?: DeclaredService;\n tags: string[];\n}\n\n/** loam-neutral relationship view. */\nexport interface Rel {\n source: string;\n target: string;\n title?: string;\n /** OpenAPI operationId this call uses, from the relationship's `metadata { op '...' }`. */\n op?: string;\n /**\n * AsyncAPI message this edge PRODUCES, from `metadata { publishes '...' }`.\n * Resolved against the edge SOURCE's own contract: a service publishes what it\n * declares an `action: send` for.\n */\n publishes?: string;\n /**\n * AsyncAPI message this edge CONSUMES, from `metadata { consumes '...' }`.\n * Two keys rather than one directional `message`, because the async spine is\n * not symmetric with the HTTP one: there the PROVIDER owns the contract and\n * every consumer is checked against it, while here the PRODUCER owns the\n * message and the consumer lives in another repository entirely. Which side of\n * an edge owes the declaration has to be readable from the edge itself — the\n * arrow cannot say it, because a Kafka edge points at the broker from both\n * ends.\n */\n consumes?: string;\n tags: string[];\n}\n\nexport interface LoadedDoc {\n errors: LikeC4Error[];\n elements: Elem[];\n relationships: Rel[];\n /**\n * The document's `specification { }` block — what its KINDS declare, as\n * opposed to what its elements do. Optional because a document that did not\n * parse has none, and because the several `LoadedDoc` literals standing in for\n * an absent file (`show`'s missing model.likec4, `unreadableLandscape`) have\n * nothing to put here: a reader must treat \"absent\" and \"declares nothing\" the\n * same way. See `./parsed/specification.ts` for why loam reads it at all —\n * short version: since LikeC4 1.59.0 a kind can carry tags, and only the\n * specification can tell an inherited tag from an authored one.\n */\n specification?: DocSpecification;\n /**\n * The `dynamic view`s the document DECLARES — the ordered steps of a use\n * case, never a rendering (docs/DESIGN.md rule 26). Optional for exactly the\n * reason `specification` is: a document that did not parse has none, and the\n * several `LoadedDoc` literals standing in for an absent file have nothing to\n * put here. Absent and empty mean the same thing to every reader — no views\n * were declared, or none could be read — and neither is a finding: a fleet\n * that draws no diagrams owes loam no views block.\n */\n views?: ParsedView[];\n /**\n * The ids of every view the document AUTHORS, static ones included — a census,\n * not a read of what any of them shows. Separate from `views` above because\n * the two answer different questions off different filters (see\n * `./parsed/view-ids.ts`), and because a static view contributes an id and\n * nothing else loam may look at.\n */\n viewIds?: ViewIdClaim[];\n}\n\n/**\n * The slice of a LikeC4 model loam reads: elements and relationships, and on\n * each of those only id/kind/title/description/tags/metadata.\n *\n * It is a type of its own, rather than an inline cast at the one call site,\n * because BOTH of LikeC4's model stages expose exactly this slice — which is\n * what makes the cheap stage a safe substitute for the expensive one. The\n * substitution is pinned in test/likec4-model-parity.test.ts, which flattens\n * both stages through `flattenModel` below and compares the results.\n *\n * Views are deliberately NOT in this interface, even though loam does read them.\n * What this interface pins is the two-stage PARITY that makes the cheap stage a\n * safe substitute for the expensive one — and the stages are not claimed to\n * agree about views, nor need to: the parsed stage is the only one rule 26\n * permits. That read has its own adapter, `./parsed/dynamic-views.ts`.\n */\nexport interface ReadableModel {\n elements: () => Iterable<{\n id: string;\n kind: string;\n title: string;\n description?: unknown;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n relationships: () => Iterable<{\n source: { id: string };\n target: { id: string };\n title?: string | null;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n /**\n * The parsed `specification { }` block. Typed `unknown` and normalized by\n * `readSpecification` rather than described here, because it is NOT part of\n * the two-stage parity this interface pins: the shape is LikeC4's internal\n * record (1.59.2 spells element-kind tags and relationship-kind tags\n * differently), and writing it out here would be a structural claim about a\n * dependency's internals that the compiler would then enforce against us.\n */\n specification?: unknown;\n}\n\n/** Flatten a LikeC4 model into loam's neutral `Elem`/`Rel` view. */\nexport function flattenModel(model: ReadableModel): { elements: Elem[]; relationships: Rel[] } {\n const elements: Elem[] = [...model.elements()].map((e) => {\n // The one crossing between LikeC4's parse output and loam's type system:\n // a `metadata { service '...' }` binding is text somebody wrote, and this\n // is where it acquires a type that says so.\n const bound = metaKey(e.metadata, \"service\");\n return {\n id: e.id,\n kind: e.kind,\n title: e.title,\n description: descText(e.description),\n ...(bound === undefined ? {} : { service: declaredService(bound) }),\n tags: [...(e.tags ?? [])],\n };\n });\n const relationships: Rel[] = [...model.relationships()].map((r) => ({\n source: r.source.id,\n target: r.target.id,\n // LikeC4 reports an untitled edge as title: null — normalize to the declared `title?: string`.\n title: r.title ?? undefined,\n op: metaKey(r.metadata, \"op\"),\n publishes: metaKey(r.metadata, \"publishes\"),\n consumes: metaKey(r.metadata, \"consumes\"),\n tags: [...(r.tags ?? [])],\n }));\n return { elements, relationships };\n}\n\n/**\n * Load and validate a single self-contained `.likec4` document, in-process\n * (no external tool, no JVM). Returns validation errors and, if clean, the\n * flattened elements + relationships.\n */\nexport async function loadFile(path: string): Promise<LoadedDoc> {\n return loadSource(await readFile(path, \"utf8\"));\n}\n\n/**\n * `loadFile` for text that is not (yet) on disk — what the archive merge uses\n * to prove a computed landscape parses BEFORE anything is written.\n */\nexport async function loadSource(src: string): Promise<LoadedDoc> {\n // LikeC4 owns a Langium workspace and registers a process-exit listener for\n // it. Validation creates many short-lived instances, so every acquired\n // instance must be disposed even on an invalid document or a failed model\n // computation. Its logger is disabled because diagnostics are returned in\n // LoadedDoc instead of being written out-of-band to stderr.\n const likec4 = await LikeC4.fromSource(src, { logger: false });\n try {\n // Assigned, not cast: LikeC4 declares `getErrors()` as a non-nullable array\n // whose element type is structurally wider than ours, so the compiler can\n // check the shape here. The cast was the only thing standing between a\n // dependency renaming `message` and a build that still succeeded — and the\n // `?? []` it carried could never fire.\n const errors: LikeC4Error[] = likec4.getErrors();\n if (errors.length > 0) {\n return { errors, elements: [], relationships: [] };\n }\n\n // `parsedModel`, not `computedModel`: the computed stage RESOLVES every\n // view — expanding its predicates against the model and deriving the\n // ancestor edges a diagram needs (and computing a default view when the\n // document declares none). loam reads what a view DECLARES and never what a\n // view SHOWS (docs/DESIGN.md rule 26), so none of that resolution is ever\n // wanted; it renders nothing. That work is superlinear in the number of\n // RELATIONSHIPS, so a landscape at fleet shape turned `loam list` from under\n // a second into minutes. The elements and relationships below are identical\n // either way. The `await` is load-bearing for anything reading `$data`: it is\n // undefined on the unresolved promise.\n const model = (await likec4.parsedModel()) as ReadableModel;\n return {\n errors,\n specification: readSpecification(model.specification),\n views: readDynamicViews(model),\n viewIds: readViewIds(model),\n ...flattenModel(model),\n };\n } finally {\n await likec4.dispose();\n }\n}\n"]}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `dynamic view`s a document DECLARES, read off LikeC4's parsed model.
|
|
3
|
+
*
|
|
4
|
+
* This is the only module in loam permitted to touch `$data`, and
|
|
5
|
+
* `scripts/arch-check.mjs` enforces that rather than trusting it (docs/DESIGN.md
|
|
6
|
+
* rule 26). The confinement is the point: `$data` is public and typed but thinly
|
|
7
|
+
* documented for this use, so an upstream shape change has a blast radius of one
|
|
8
|
+
* file, and `test/likec4-view-shape.test.ts` is the tripwire that fires before
|
|
9
|
+
* the change reaches a check.
|
|
10
|
+
*
|
|
11
|
+
* It takes the WHOLE model as `unknown` rather than a `$data` slice handed in by
|
|
12
|
+
* its caller, and that is not squeamishness — naming `$data` in `../likec4.ts`
|
|
13
|
+
* would violate the containment scan this module exists behind. Callers pass the
|
|
14
|
+
* model; every assumption about LikeC4's record shape lives here.
|
|
15
|
+
*
|
|
16
|
+
* Everything is defensive, in the same posture as `./specification.ts`: a shape
|
|
17
|
+
* this reader cannot make sense of degrades to ZERO views, never to a wrong one
|
|
18
|
+
* and never to a throw inside a validate run. That asymmetry is deliberate and
|
|
19
|
+
* is loam's standing rule for a suspended axis — a check with no views to grade
|
|
20
|
+
* reports could-not-look, while a check fed a half-read view would report
|
|
21
|
+
* nothing-wrong about a flow it never saw.
|
|
22
|
+
*/
|
|
23
|
+
import { descText } from "./values.js";
|
|
24
|
+
/** A record, or nothing — `v` is `unknown` off an untyped parse. */
|
|
25
|
+
function record(v) {
|
|
26
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
27
|
+
}
|
|
28
|
+
/** The value at `key`, when it is a non-empty string. */
|
|
29
|
+
function str(rec, key) {
|
|
30
|
+
const v = rec[key];
|
|
31
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Every leaf step under `entries`, depth-first in author order.
|
|
35
|
+
*
|
|
36
|
+
* Steps NEST: a `loop 'until accepted' { ... }` arrives as an entry with
|
|
37
|
+
* `_type: "loop"`, a title, its own `steps[]`, and NO endpoints and NO `astPath`
|
|
38
|
+
* of its own (measured). A walk that treated every entry as a hop would read the
|
|
39
|
+
* bracket as a step and then fail to find a relationship backing it — inventing
|
|
40
|
+
* a finding out of a grouping the author drew for legibility.
|
|
41
|
+
*
|
|
42
|
+
* The test is structural rather than a list of group keywords (`loop`, `par`,
|
|
43
|
+
* ...): an entry with a nested `steps` array is a group whatever LikeC4 calls
|
|
44
|
+
* it, and an entry with two string endpoints is a hop. A future group kind
|
|
45
|
+
* therefore flattens correctly on the day it ships instead of on the day
|
|
46
|
+
* somebody notices.
|
|
47
|
+
*/
|
|
48
|
+
function leaves(entries, out) {
|
|
49
|
+
if (!Array.isArray(entries))
|
|
50
|
+
return;
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const rec = record(entry);
|
|
53
|
+
if (!rec)
|
|
54
|
+
continue;
|
|
55
|
+
if (Array.isArray(rec["steps"])) {
|
|
56
|
+
leaves(rec["steps"], out);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (typeof rec["source"] === "string" && typeof rec["target"] === "string")
|
|
60
|
+
out.push(rec);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** One leaf entry as a `ParsedStep`. Every leaf yields one; nothing is dropped. */
|
|
64
|
+
function step(rec, ordinal) {
|
|
65
|
+
const astPath = str(rec, "astPath");
|
|
66
|
+
const title = str(rec, "title");
|
|
67
|
+
const notes = descText(rec["notes"]);
|
|
68
|
+
return {
|
|
69
|
+
ordinal,
|
|
70
|
+
source: rec["source"],
|
|
71
|
+
target: rec["target"],
|
|
72
|
+
...(title === undefined ? {} : { title }),
|
|
73
|
+
...(notes === undefined ? {} : { notes }),
|
|
74
|
+
// Present-and-true is the only truthy form LikeC4 emits; absent means
|
|
75
|
+
// forward. Read as a boolean rather than carried as `true | undefined` so a
|
|
76
|
+
// caller cannot accidentally test `=== false` against a key that is never
|
|
77
|
+
// written.
|
|
78
|
+
isBackward: rec["isBackward"] === true,
|
|
79
|
+
...(astPath === undefined ? {} : { astPath }),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The dynamic views one parsed document declares, in the record's own order.
|
|
84
|
+
*
|
|
85
|
+
* `model` is the awaited `parsedModel()` result. The `await` matters and is the
|
|
86
|
+
* mistake every draft of this made: `$data` is `undefined` on the unresolved
|
|
87
|
+
* promise, so a reader handed the promise reports a document with views as
|
|
88
|
+
* having none — a silent zero, which is the one wrong answer this module is
|
|
89
|
+
* written to avoid.
|
|
90
|
+
*
|
|
91
|
+
* Two entries are dropped on the way out, for different reasons:
|
|
92
|
+
*
|
|
93
|
+
* - Anything whose `_type` is not `"dynamic"`. LikeC4 synthesises an `index`
|
|
94
|
+
* view into EVERY document, including one with no `views` block at all
|
|
95
|
+
* (measured), so reporting element views would be reporting a fiction the
|
|
96
|
+
* author never wrote. A census that must see authored element views wants the
|
|
97
|
+
* other filter rule 26 states — `sourcePath !== undefined` — and must not
|
|
98
|
+
* reuse this function to get it.
|
|
99
|
+
* - Anything with no `id`. There is nothing to name in a finding, and a view
|
|
100
|
+
* loam cannot name is a view it cannot ask anybody to fix.
|
|
101
|
+
*/
|
|
102
|
+
export function readDynamicViews(model) {
|
|
103
|
+
const data = record(record(model)?.["$data"]);
|
|
104
|
+
const views = record(data?.["views"]);
|
|
105
|
+
if (!views)
|
|
106
|
+
return [];
|
|
107
|
+
const out = [];
|
|
108
|
+
for (const raw of Object.values(views)) {
|
|
109
|
+
const rec = record(raw);
|
|
110
|
+
if (!rec || rec["_type"] !== "dynamic")
|
|
111
|
+
continue;
|
|
112
|
+
const id = str(rec, "id");
|
|
113
|
+
if (id === undefined)
|
|
114
|
+
continue;
|
|
115
|
+
const found = [];
|
|
116
|
+
leaves(rec["steps"], found);
|
|
117
|
+
const steps = found.map((leaf, i) => step(leaf, i + 1));
|
|
118
|
+
const title = str(rec, "title");
|
|
119
|
+
const description = descText(rec["description"]);
|
|
120
|
+
const sourcePath = str(rec, "sourcePath");
|
|
121
|
+
out.push({
|
|
122
|
+
id,
|
|
123
|
+
...(sourcePath === undefined ? {} : { sourcePath }),
|
|
124
|
+
tags: Array.isArray(rec["tags"]) ? rec["tags"].filter((t) => typeof t === "string") : [],
|
|
125
|
+
...(title === undefined ? {} : { title }),
|
|
126
|
+
...(description === undefined ? {} : { description }),
|
|
127
|
+
steps,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=dynamic-views.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-views.js","sourceRoot":"","sources":["../../../../src/core/c4/parsed/dynamic-views.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAsFvC,oEAAoE;AACpE,SAAS,MAAM,CAAC,CAAU;IACxB,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAA6B,CAAC,CAAC,CAAC,SAAS,CAAC;AACtG,CAAC;AAED,yDAAyD;AACzD,SAAS,GAAG,CAAC,GAA4B,EAAE,GAAW;IACpD,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,MAAM,CAAC,OAAgB,EAAE,GAA8B;IAC9D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO;IACpC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC;YAC1B,SAAS;QACX,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,QAAQ;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,SAAS,IAAI,CAAC,GAA4B,EAAE,OAAe;IACzD,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACrC,OAAO;QACL,OAAO;QACP,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAW;QAC/B,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAW;QAC/B,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QACzC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QACzC,sEAAsE;QACtE,4EAA4E;QAC5E,0EAA0E;QAC1E,WAAW;QACX,UAAU,EAAE,GAAG,CAAC,YAAY,CAAC,KAAK,IAAI;QACtC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IAEtB,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,SAAS;YAAE,SAAS;QACjD,MAAM,EAAE,GAAG,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC1B,IAAI,EAAE,KAAK,SAAS;YAAE,SAAS;QAE/B,MAAM,KAAK,GAA8B,EAAE,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAExD,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAChC,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QAC1C,GAAG,CAAC,IAAI,CAAC;YACP,EAAE;YACF,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;YACnD,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;YACrG,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;YACzC,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;YACrD,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * The `dynamic view`s a document DECLARES, read off LikeC4's parsed model.\n *\n * This is the only module in loam permitted to touch `$data`, and\n * `scripts/arch-check.mjs` enforces that rather than trusting it (docs/DESIGN.md\n * rule 26). The confinement is the point: `$data` is public and typed but thinly\n * documented for this use, so an upstream shape change has a blast radius of one\n * file, and `test/likec4-view-shape.test.ts` is the tripwire that fires before\n * the change reaches a check.\n *\n * It takes the WHOLE model as `unknown` rather than a `$data` slice handed in by\n * its caller, and that is not squeamishness — naming `$data` in `../likec4.ts`\n * would violate the containment scan this module exists behind. Callers pass the\n * model; every assumption about LikeC4's record shape lives here.\n *\n * Everything is defensive, in the same posture as `./specification.ts`: a shape\n * this reader cannot make sense of degrades to ZERO views, never to a wrong one\n * and never to a throw inside a validate run. That asymmetry is deliberate and\n * is loam's standing rule for a suspended axis — a check with no views to grade\n * reports could-not-look, while a check fed a half-read view would report\n * nothing-wrong about a flow it never saw.\n */\nimport { descText } from \"./values.js\";\n\n/** One hop of a declared use case: an ordered edge between two elements. */\nexport interface ParsedStep {\n /**\n * 1-based pre-order position among the view's LEAF steps — what lets a finding\n * say \"step 4 of Checkout\". Group wrappers do not consume an ordinal: a\n * `loop` is a bracket around hops, not a hop, and counting it would shift\n * every number after it away from what the author sees on the diagram.\n */\n ordinal: number;\n /**\n * The step's endpoints, ALREADY ORIENTED — see `isBackward`. Dotted FQN\n * strings naming elements, never relationship ids: a step says who talks to\n * whom, and which relationship backs that is a join loam performs later.\n */\n source: string;\n target: string;\n /** The step's label, absent when the author wrote none. */\n title?: string;\n /** A `notes '...'` block on the step, decoded from LikeC4's rich-text shape. */\n notes?: string;\n /**\n * True when the author wrote the hop as a reply — `a <- b` rather than\n * `b -> a`. LOAD-BEARING, and measured: LikeC4 records `a <- b 'reply'` as\n * `{source: b, target: a, isBackward: true}` — already reversed AND flagged —\n * while a forward step carries no such key at all. `source`/`target` above are\n * therefore correct as they stand and need no flipping; the flag is carried so\n * a check can tell a return hop from a call, and so a message naming the\n * failing step can suggest the `<-` spelling to an author who wrote the arrow\n * the long way round.\n */\n isBackward: boolean;\n /**\n * LikeC4's own path to the step inside the view (`/steps@1/steps@0`), carried\n * verbatim. It is the only stable handle on a nested step — two hops between\n * the same pair inside different `loop` blocks are otherwise indistinguishable\n * — and it is message text, never identity: nothing joins on it.\n *\n * OPTIONAL, and that is a deliberate refusal to make it load-bearing. Every\n * leaf carries one at the 1.59.2 pin, so requiring it would look free — and\n * would mean that an upstream release which stopped emitting it turned every\n * use case in the fleet into a view with zero steps. That is the silent wrong\n * answer this module exists to avoid: a hop loam cannot cite is still a hop it\n * must grade.\n */\n astPath?: string;\n}\n\n/** One declared `dynamic view`, flattened to what rule 26 permits reading. */\nexport interface ParsedView {\n id: string;\n /**\n * The name LikeC4 knows the document by, and it is only a PATH under\n * `loadProject`: there it is relative to the project root, so\n * `usecases/checkout.likec4` inside `architecture/`. Under the per-file\n * loaders it is whatever that loader called the document — `source.c4` for\n * `loadSource`, the basename for `loadBatch` — because there is no project\n * for it to be relative to.\n *\n * So: name a file with it only when the views came from `loadProject`. That\n * is the only reader that has one, which is also the only reader that needs\n * one — a use case lives in a file of its own now, and a finding that cannot\n * name that file sends its reader to the landscape to look for something\n * that is not there.\n *\n * Absent for anything LikeC4 synthesized rather than read off a file, which\n * is how the census reader tells the two apart.\n */\n sourcePath?: string;\n /**\n * The view's declared tags, `[]` when it declares none. Normalized here\n * because LikeC4 reads an untagged view's tags as `null`, and a caller\n * testing `.length` on that would throw inside a validate run.\n *\n * Tags live on the VIEW and nowhere else: a step cannot carry tags, and\n * `metadata` on a step is a hard parse error. That is measured, and it is why\n * a use case is opted in as a whole rather than hop by hop.\n */\n tags: string[];\n title?: string;\n description?: string;\n /** The declared hops, in author order, groups flattened away. */\n steps: ParsedStep[];\n}\n\n/** A record, or nothing — `v` is `unknown` off an untyped parse. */\nfunction record(v: unknown): Record<string, unknown> | undefined {\n return v && typeof v === \"object\" && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;\n}\n\n/** The value at `key`, when it is a non-empty string. */\nfunction str(rec: Record<string, unknown>, key: string): string | undefined {\n const v = rec[key];\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n}\n\n/**\n * Every leaf step under `entries`, depth-first in author order.\n *\n * Steps NEST: a `loop 'until accepted' { ... }` arrives as an entry with\n * `_type: \"loop\"`, a title, its own `steps[]`, and NO endpoints and NO `astPath`\n * of its own (measured). A walk that treated every entry as a hop would read the\n * bracket as a step and then fail to find a relationship backing it — inventing\n * a finding out of a grouping the author drew for legibility.\n *\n * The test is structural rather than a list of group keywords (`loop`, `par`,\n * ...): an entry with a nested `steps` array is a group whatever LikeC4 calls\n * it, and an entry with two string endpoints is a hop. A future group kind\n * therefore flattens correctly on the day it ships instead of on the day\n * somebody notices.\n */\nfunction leaves(entries: unknown, out: Record<string, unknown>[]): void {\n if (!Array.isArray(entries)) return;\n for (const entry of entries) {\n const rec = record(entry);\n if (!rec) continue;\n if (Array.isArray(rec[\"steps\"])) {\n leaves(rec[\"steps\"], out);\n continue;\n }\n if (typeof rec[\"source\"] === \"string\" && typeof rec[\"target\"] === \"string\") out.push(rec);\n }\n}\n\n/** One leaf entry as a `ParsedStep`. Every leaf yields one; nothing is dropped. */\nfunction step(rec: Record<string, unknown>, ordinal: number): ParsedStep {\n const astPath = str(rec, \"astPath\");\n const title = str(rec, \"title\");\n const notes = descText(rec[\"notes\"]);\n return {\n ordinal,\n source: rec[\"source\"] as string,\n target: rec[\"target\"] as string,\n ...(title === undefined ? {} : { title }),\n ...(notes === undefined ? {} : { notes }),\n // Present-and-true is the only truthy form LikeC4 emits; absent means\n // forward. Read as a boolean rather than carried as `true | undefined` so a\n // caller cannot accidentally test `=== false` against a key that is never\n // written.\n isBackward: rec[\"isBackward\"] === true,\n ...(astPath === undefined ? {} : { astPath }),\n };\n}\n\n/**\n * The dynamic views one parsed document declares, in the record's own order.\n *\n * `model` is the awaited `parsedModel()` result. The `await` matters and is the\n * mistake every draft of this made: `$data` is `undefined` on the unresolved\n * promise, so a reader handed the promise reports a document with views as\n * having none — a silent zero, which is the one wrong answer this module is\n * written to avoid.\n *\n * Two entries are dropped on the way out, for different reasons:\n *\n * - Anything whose `_type` is not `\"dynamic\"`. LikeC4 synthesises an `index`\n * view into EVERY document, including one with no `views` block at all\n * (measured), so reporting element views would be reporting a fiction the\n * author never wrote. A census that must see authored element views wants the\n * other filter rule 26 states — `sourcePath !== undefined` — and must not\n * reuse this function to get it.\n * - Anything with no `id`. There is nothing to name in a finding, and a view\n * loam cannot name is a view it cannot ask anybody to fix.\n */\nexport function readDynamicViews(model: unknown): ParsedView[] {\n const data = record(record(model)?.[\"$data\"]);\n const views = record(data?.[\"views\"]);\n if (!views) return [];\n\n const out: ParsedView[] = [];\n for (const raw of Object.values(views)) {\n const rec = record(raw);\n if (!rec || rec[\"_type\"] !== \"dynamic\") continue;\n const id = str(rec, \"id\");\n if (id === undefined) continue;\n\n const found: Record<string, unknown>[] = [];\n leaves(rec[\"steps\"], found);\n const steps = found.map((leaf, i) => step(leaf, i + 1));\n\n const title = str(rec, \"title\");\n const description = descText(rec[\"description\"]);\n const sourcePath = str(rec, \"sourcePath\");\n out.push({\n id,\n ...(sourcePath === undefined ? {} : { sourcePath }),\n tags: Array.isArray(rec[\"tags\"]) ? rec[\"tags\"].filter((t): t is string => typeof t === \"string\") : [],\n ...(title === undefined ? {} : { title }),\n ...(description === undefined ? {} : { description }),\n steps,\n });\n }\n return out;\n}\n"]}
|