@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.2
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 +2635 -86
- 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 +599 -0
- package/SCHEMA.md +2355 -256
- 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 +323 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +180 -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 +252 -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 +72 -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 +204 -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 +270 -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 +251 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +251 -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/{agents-stamp.js → agent/agents-stamp.js} +50 -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 +337 -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.js → asyncapi/read.js} +95 -109
- 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 +48 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +174 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +90 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +158 -0
- package/dist/core/c4/parsed/deployment.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 +187 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +220 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/service.js +159 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +144 -0
- package/dist/core/c4/resolve/steps.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 +139 -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 +174 -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 +287 -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/deployment/delta.js +188 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +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 +7 -64
- 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 +120 -78
- 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 +137 -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 +210 -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 +277 -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 +196 -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 +197 -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 +72 -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 +58 -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 -295
- 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 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- 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 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- 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/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- 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 -596
- 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 -489
- 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 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- 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 -1132
- 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,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fleet-shape advisories over the parsed landscape: the two ways a fleet map
|
|
3
|
+
* stops answering questions as it grows, caught while the map is already in
|
|
4
|
+
* memory.
|
|
5
|
+
*
|
|
6
|
+
* Both shapes become visible at service three rather than service two.
|
|
7
|
+
* Ubiquitous infrastructure — logging Kafka, an identity provider, service
|
|
8
|
+
* discovery — takes one inbound edge per modelled service until the map is a
|
|
9
|
+
* hairball around it; deleting the element fixes the picture and loses "who
|
|
10
|
+
* depends on the Identity Provider", which is the question the map exists to
|
|
11
|
+
* answer during an incident. Tagging keeps both: the fleet view excludes
|
|
12
|
+
* `#platform`, and a platform view keeps the dependents. And a datastore drawn as a fleet-level
|
|
13
|
+
* peer makes a claim its consumer count either supports or refutes: one
|
|
14
|
+
* consumer means the drawing is false — the store is that service's
|
|
15
|
+
* internals, not a system in its own right — while two or more mean the
|
|
16
|
+
* strongest coupling two services can have, which deserves to be stated
|
|
17
|
+
* rather than inferred.
|
|
18
|
+
*
|
|
19
|
+
* Everything here is O(relationships) over data the fleet target already
|
|
20
|
+
* holds. loam still reads no code and decides no policy: the team chooses
|
|
21
|
+
* what counts as platform and which data is truly shared — these warnings
|
|
22
|
+
* only name the shapes that are usually wrong.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync } from "node:fs";
|
|
25
|
+
import { readFile } from "node:fs/promises";
|
|
26
|
+
import {} from "../../../core/c4/likec4.js";
|
|
27
|
+
import {} from "../../../core/kernel/ids/service.js";
|
|
28
|
+
import {} from "../../../core/vocabulary/report.js";
|
|
29
|
+
import {} from "../../../core/repo/entries.js";
|
|
30
|
+
import { permissionsPath, servicePathsAt } from "../../../core/repo/paths.js";
|
|
31
|
+
import { readVocabulary } from "../../../core/permissions/permissions.js";
|
|
32
|
+
import { readCapabilityVocabulary } from "../../../core/capabilities/capabilities.js";
|
|
33
|
+
import { capabilityDocFindings, capabilityRequirementIndex, docMissingFindings, gradableCapabilityIds, invalidVocabularyFinding, unrealizedFindings, } from "../../../core/capabilities/findings.js";
|
|
34
|
+
import { useCaseRequirementClaims } from "../../../core/usecases/capability.js";
|
|
35
|
+
import { capabilityRollup, usedCapabilities } from "../../../core/capabilities/rollup.js";
|
|
36
|
+
import { requirementUnrealizedFindings } from "../../../core/capabilities/realizes/findings.js";
|
|
37
|
+
import { parseRequirements } from "../../../core/document/parse.js";
|
|
38
|
+
import {} from "../../../core/document/spec.js";
|
|
39
|
+
import { FleetContext } from "../../../core/fleet-context.js";
|
|
40
|
+
import { EXTERNAL_TAG, PLATFORM_TAG } from "../../../core/vocabulary/maturity.js";
|
|
41
|
+
/**
|
|
42
|
+
* Consumers at which an untagged external hub starts to warn. Three, not two:
|
|
43
|
+
* two services sharing a dependency is a fact about those two services, the
|
|
44
|
+
* same edge from three is a pattern about the element — and three is where
|
|
45
|
+
* the shared hub starts making the map unreadable.
|
|
46
|
+
*/
|
|
47
|
+
export const PLATFORM_CANDIDATE_MIN_CONSUMERS = 3;
|
|
48
|
+
/** The element kind read as a datastore, compared case-insensitively. */
|
|
49
|
+
const DATASTORE_KIND = "database";
|
|
50
|
+
export function fleetShapeFindings(shape) {
|
|
51
|
+
const { drawn, relationships, services, resolve } = shape;
|
|
52
|
+
const findings = [];
|
|
53
|
+
// Distinct consumer SERVICES of an element, its nested children included: an
|
|
54
|
+
// edge into `kafka.paymentEvents` is consumption of `kafka`. Persons, other
|
|
55
|
+
// external systems and unresolved sources never count — a consumer is a
|
|
56
|
+
// source the shared resolver files under a real services/<id>/, so two edges
|
|
57
|
+
// from one service are one consumer.
|
|
58
|
+
const consumersOf = (e) => {
|
|
59
|
+
const out = new Set();
|
|
60
|
+
for (const r of relationships) {
|
|
61
|
+
if (r.target !== e.id && !r.target.startsWith(`${e.id}.`))
|
|
62
|
+
continue;
|
|
63
|
+
const svc = resolve(r.source);
|
|
64
|
+
if (services.has(svc))
|
|
65
|
+
out.add(svc);
|
|
66
|
+
}
|
|
67
|
+
return [...out].sort();
|
|
68
|
+
};
|
|
69
|
+
for (const e of drawn) {
|
|
70
|
+
// An element that stands for a real service is the fleet itself, not its
|
|
71
|
+
// shape: many consumers is what a well-used service looks like, and a
|
|
72
|
+
// datastore bound to a directory is graded by the binding checks instead.
|
|
73
|
+
if (e.service !== undefined || services.has(e.title))
|
|
74
|
+
continue;
|
|
75
|
+
const consumers = consumersOf(e);
|
|
76
|
+
if (e.tags.includes(EXTERNAL_TAG) &&
|
|
77
|
+
!e.tags.includes(PLATFORM_TAG) &&
|
|
78
|
+
consumers.length >= PLATFORM_CANDIDATE_MIN_CONSUMERS) {
|
|
79
|
+
findings.push({
|
|
80
|
+
severity: "warn",
|
|
81
|
+
code: "landscape.platform-candidate",
|
|
82
|
+
subject: e.title,
|
|
83
|
+
message: `landscape: '${e.title}' is consumed by ${consumers.length} services (${consumers.join(", ")}) ` +
|
|
84
|
+
`and is not tagged #platform — a hub like this takes one more edge per modelled service until ` +
|
|
85
|
+
`the fleet view is unreadable. Declare \`tag platform\` in the specification block and tag the ` +
|
|
86
|
+
`element (LikeC4 refuses an undeclared tag, so both steps are needed): the fleet view then ` +
|
|
87
|
+
`excludes it, and a platform view over \`include * -> element.tag = #platform\` keeps ` +
|
|
88
|
+
`"who depends on it" answerable`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (e.kind.toLowerCase() !== DATASTORE_KIND || consumers.length === 0)
|
|
92
|
+
continue;
|
|
93
|
+
if (consumers.length === 1) {
|
|
94
|
+
findings.push({
|
|
95
|
+
severity: "warn",
|
|
96
|
+
code: "landscape.datastore-private",
|
|
97
|
+
subject: e.title,
|
|
98
|
+
message: `landscape: '${e.title}' is a datastore with a single consumer at fleet level ` +
|
|
99
|
+
`('${consumers[0]}') — drawn as a peer it reads as a system in its own right, available to ` +
|
|
100
|
+
`be depended on. Move it into ${shape.pathOf(consumers[0])}/model.likec4 as a nested container ` +
|
|
101
|
+
`and delete it here, or add the second consumer's edge if another service really reaches ` +
|
|
102
|
+
`the same data`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
findings.push({
|
|
107
|
+
severity: "warn",
|
|
108
|
+
code: "landscape.datastore-shared",
|
|
109
|
+
subject: e.title,
|
|
110
|
+
message: `landscape: '${e.title}' is a datastore shared by ${consumers.length} services ` +
|
|
111
|
+
`(${consumers.join(", ")}) — the strongest coupling two services can have, and the hardest ` +
|
|
112
|
+
`to undo. Shared means the same DATA: if they read the same tables or keys, keep it drawn ` +
|
|
113
|
+
`and let this warning state the coupling; if they only share a host or cluster (two schemas, ` +
|
|
114
|
+
`two lock paths), that is operational blast radius — a runbook fact — and the honest model ` +
|
|
115
|
+
`is one private store per service`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return findings;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The #external element that declares it PUBLISHES this message, or null —
|
|
123
|
+
* the landscape-side half of the event spine's fleet question, beside the
|
|
124
|
+
* other checks that read tags off the drawn map.
|
|
125
|
+
*
|
|
126
|
+
* Positive evidence only: an edge carrying `metadata { publishes '<msg>' }`
|
|
127
|
+
* whose source element — itself or a declared ancestor, since the edge may
|
|
128
|
+
* point out of a topic nested inside the broker — is tagged #external and
|
|
129
|
+
* resolves to no known service directory. The resolution guard is what keeps
|
|
130
|
+
* a mis-tagged internal service reading as unproduced: a tag is cheap to
|
|
131
|
+
* write, and a services/<id>/ directory outranks it.
|
|
132
|
+
*/
|
|
133
|
+
/**
|
|
134
|
+
* The authorization vocabulary graded against the fleet that uses it — the half
|
|
135
|
+
* of the axis no single service can answer.
|
|
136
|
+
*
|
|
137
|
+
* `permissions.invalid` is an error and it is the only one reported when it
|
|
138
|
+
* fires: a vocabulary nobody can read resolves nothing, so every `Requires:`
|
|
139
|
+
* line in the fleet would follow it as `permissions.unknown`, and a hundred
|
|
140
|
+
* findings about one broken file is a cascade, not a diagnosis.
|
|
141
|
+
*
|
|
142
|
+
* `permissions.unenforced` is the mirror of `api.ungoverned`, and it earns its
|
|
143
|
+
* place for the same reason: a vocabulary is only worth what cites it. A
|
|
144
|
+
* permission nothing requires is either a rule that was removed and left its
|
|
145
|
+
* word behind, or a word nobody adopted — both are drift, and neither is
|
|
146
|
+
* visible from inside the file. Warn, because the honest answer is sometimes
|
|
147
|
+
* "not modelled yet".
|
|
148
|
+
*/
|
|
149
|
+
export async function permissionFindings(docsDir, services, fleet) {
|
|
150
|
+
const vocabulary = await readVocabulary(permissionsPath(docsDir));
|
|
151
|
+
if (!vocabulary.present)
|
|
152
|
+
return [];
|
|
153
|
+
if (vocabulary.invalid !== undefined) {
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
severity: "error",
|
|
157
|
+
code: "permissions.invalid",
|
|
158
|
+
message: `landscape: architecture/permissions.yaml does not read as a vocabulary — ${vocabulary.invalid}. ` +
|
|
159
|
+
"Every `Requires:` line in the fleet resolves against this file, so none of them can be graded until it parses. " +
|
|
160
|
+
"The shape is `subjects: {<kind>: {description}}` then `permissions: {<kind>: {<name>: {description, owned_by, enforced_by}}}`.",
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
const used = new Set();
|
|
165
|
+
for (const entry of services) {
|
|
166
|
+
const paths = servicePathsAt(entry.dir);
|
|
167
|
+
for (const path of [paths.spec, paths.archSpec]) {
|
|
168
|
+
// Existence first, and for BOTH readers: arch.spec.md is optional (most
|
|
169
|
+
// of a legacy fleet has none) and spec.md is missing on a half-adopted
|
|
170
|
+
// service. `FleetContext.readRequirements` throws ENOENT, which surfaces
|
|
171
|
+
// as `repository-unavailable` and takes the whole `--all` run down — an
|
|
172
|
+
// absent optional artifact must never be able to do that.
|
|
173
|
+
if (!existsSync(path))
|
|
174
|
+
continue;
|
|
175
|
+
const reqs = fleet === undefined ? await readRequirementsAt(path) : await fleet.readRequirements(path);
|
|
176
|
+
for (const r of reqs) {
|
|
177
|
+
if (r.kind !== "REMOVED")
|
|
178
|
+
for (const p of r.requires)
|
|
179
|
+
used.add(p);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const unenforced = [...vocabulary.byId.keys()].filter((id) => !used.has(id));
|
|
184
|
+
if (unenforced.length === 0)
|
|
185
|
+
return [];
|
|
186
|
+
return [
|
|
187
|
+
{
|
|
188
|
+
severity: "warn",
|
|
189
|
+
code: "permissions.unenforced",
|
|
190
|
+
message: `landscape: ${unenforced.length} declared permission(s) that no requirement's \`Requires:\` line names — write the requirement that gates on each, or drop the declaration`,
|
|
191
|
+
details: unenforced,
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
/** The uncached read, for the `--service` path where no fleet context exists. */
|
|
196
|
+
async function readRequirementsAt(path) {
|
|
197
|
+
if (!existsSync(path))
|
|
198
|
+
return [];
|
|
199
|
+
return parseRequirements(await readFile(path, "utf8"));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The capability vocabulary graded against the fleet that realizes it — the
|
|
203
|
+
* axis's fleet half, beside permissionFindings and shaped by the same two
|
|
204
|
+
* verdicts. `capability.invalid` is a `validate --all` run's ONE finding about
|
|
205
|
+
* an unreadable file — fleet scope, so single-target runs stay silent about
|
|
206
|
+
* it — and it suppresses the rest of the family (the unknown
|
|
207
|
+
* grades at every service target return [] for the same vocabulary — one
|
|
208
|
+
* breach, one finding, never a cascade). `capability.unrealized` is one warn
|
|
209
|
+
* PER declared-but-unnamed capability, subject = the id, because the roadmap's
|
|
210
|
+
* criterion is 'one warning per capability, not one per service'. Holding
|
|
211
|
+
* NEITHER `architecture/capabilities.yaml` nor `capabilities/` is total
|
|
212
|
+
* silence: the fleet's own files are this axis's opt-in, either of them.
|
|
213
|
+
*
|
|
214
|
+
* The used set comes through capabilityRollup with the fleet's own cached
|
|
215
|
+
* reader, so under --all the walk re-parses nothing the service targets have
|
|
216
|
+
* not already paid for — and the rollup, the warn and `loam list capabilities`
|
|
217
|
+
* can never disagree about what "realized" means.
|
|
218
|
+
*
|
|
219
|
+
* The AUTHORED documents are graded here too, and they ride this function
|
|
220
|
+
* rather than a target of their own for `viewsStaleFindings`' reason one tree
|
|
221
|
+
* over: a capability belongs to no service, so only the fleet run has the whole
|
|
222
|
+
* vocabulary and the enumerated tree in view at once. Their reads are NOT
|
|
223
|
+
* wrapped, deliberately — a `capabilities/<id>/spec.md` that is not UTF-8 takes
|
|
224
|
+
* the run down exactly as a `services/<id>/spec.md` does, because a document
|
|
225
|
+
* loam cannot decode is a refusal everywhere else in the product and a quiet
|
|
226
|
+
* "zero requirements" here would grade a whole capability green on bytes
|
|
227
|
+
* nobody read.
|
|
228
|
+
*/
|
|
229
|
+
export async function capabilityFleetFindings(docsDir, services, fleet, flows) {
|
|
230
|
+
const vocab = fleet === undefined ? await readCapabilityVocabulary(docsDir) : await fleet.capabilities(docsDir);
|
|
231
|
+
if (!vocab.present)
|
|
232
|
+
return [];
|
|
233
|
+
const read = fleet === undefined ? readRequirementsAt : (p) => fleet.readRequirements(p);
|
|
234
|
+
const authored = docMissingFindings(vocab.tree);
|
|
235
|
+
for (const doc of vocab.tree.docs)
|
|
236
|
+
authored.push(...capabilityDocFindings(await read(doc.spec), doc.id));
|
|
237
|
+
const invalid = invalidVocabularyFinding(vocab);
|
|
238
|
+
if (invalid !== null)
|
|
239
|
+
return [invalid, ...authored];
|
|
240
|
+
// A USE CASE IS THE OTHER WAY TO KEEP A PROMISE, and it is the only carrier a
|
|
241
|
+
// cross-service criterion has: "I enter a login and a password and I am in"
|
|
242
|
+
// belongs to no single service's spec, so a fleet could satisfy it perfectly
|
|
243
|
+
// through a `#cap-`/`#req-` tagged flow and still be told nobody realizes it.
|
|
244
|
+
//
|
|
245
|
+
// The claims go INTO the rollup rather than beside it, so both unrealized
|
|
246
|
+
// grades and `loam list capabilities` read one set of rows. `flows === null`
|
|
247
|
+
// means loam could not READ the flows (no preload, or an `architecture/` that
|
|
248
|
+
// did not parse); the key is then OMITTED rather than empty, and every
|
|
249
|
+
// `keptBy` below is `undefined` — the three-state contract `rollup.ts` states.
|
|
250
|
+
const index = await capabilityRequirementIndex(vocab, read);
|
|
251
|
+
const keptByFlows = flows === null
|
|
252
|
+
? undefined
|
|
253
|
+
: useCaseRequirementClaims(flows, gradableCapabilityIds(vocab), (c) => index.byCapability.get(c));
|
|
254
|
+
const rows = await capabilityRollup({ services, vocab, read, ...(keptByFlows === undefined ? {} : { keptByFlows }) });
|
|
255
|
+
// The two unrealized grades read the SAME rollup, which is what keeps them
|
|
256
|
+
// from disagreeing about the word: a capability is unrealized when nothing
|
|
257
|
+
// names it by any join, and one of its requirements is unrealized when nothing
|
|
258
|
+
// keeps that promise. Both are warnings, and the second is the one that
|
|
259
|
+
// survives a healthy-looking row — a capability with four requirements and
|
|
260
|
+
// three kept reports nothing at all through the first.
|
|
261
|
+
//
|
|
262
|
+
// The suspension falls out of the DATA rather than a branch: `keptBy` absent
|
|
263
|
+
// means nobody looked, and a promise nobody looked for is not a promise
|
|
264
|
+
// reported unkept.
|
|
265
|
+
const unrealizedRequirements = rows.flatMap((row) => (row.requirements ?? [])
|
|
266
|
+
.filter((req) => req.realizedBy.length === 0 && req.keptBy?.length === 0)
|
|
267
|
+
.map((req) => ({ capability: row.id, id: req.id, name: req.name })));
|
|
268
|
+
return [
|
|
269
|
+
...authored,
|
|
270
|
+
...unrealizedFindings(vocab, usedCapabilities(rows)),
|
|
271
|
+
...requirementUnrealizedFindings(unrealizedRequirements),
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
export function externalProducerOf(message, land, landSvcOf, known) {
|
|
275
|
+
if (land === null || land.errors.length > 0 || landSvcOf === null)
|
|
276
|
+
return null;
|
|
277
|
+
const knownIds = known;
|
|
278
|
+
const byId = new Map(land.elements.map((e) => [e.id, e]));
|
|
279
|
+
for (const r of land.relationships) {
|
|
280
|
+
if (r.publishes !== message)
|
|
281
|
+
continue;
|
|
282
|
+
if (knownIds.has(landSvcOf(r.source)))
|
|
283
|
+
continue;
|
|
284
|
+
for (let id = r.source;;) {
|
|
285
|
+
const e = byId.get(id);
|
|
286
|
+
if (e !== undefined && e.tags.includes(EXTERNAL_TAG))
|
|
287
|
+
return e.title;
|
|
288
|
+
const dot = id.lastIndexOf(".");
|
|
289
|
+
if (dot === -1)
|
|
290
|
+
break;
|
|
291
|
+
id = id.slice(0, dot);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=fleet-shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-shape.js","sourceRoot":"","sources":["../../../../src/commands/validate/checks/fleet-shape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fleet-shape advisories over the parsed landscape: the two ways a fleet map\n * stops answering questions as it grows, caught while the map is already in\n * memory.\n *\n * Both shapes become visible at service three rather than service two.\n * Ubiquitous infrastructure — logging Kafka, an identity provider, service\n * discovery — takes one inbound edge per modelled service until the map is a\n * hairball around it; deleting the element fixes the picture and loses \"who\n * depends on the Identity Provider\", which is the question the map exists to\n * answer during an incident. Tagging keeps both: the fleet view excludes\n * `#platform`, and a platform view keeps the dependents. And a datastore drawn as a fleet-level\n * peer makes a claim its consumer count either supports or refutes: one\n * consumer means the drawing is false — the store is that service's\n * internals, not a system in its own right — while two or more mean the\n * strongest coupling two services can have, which deserves to be stated\n * rather than inferred.\n *\n * Everything here is O(relationships) over data the fleet target already\n * holds. loam still reads no code and decides no policy: the team chooses\n * what counts as platform and which data is truly shared — these warnings\n * only name the shapes that are usually wrong.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport { type DeclaredService, type RawServiceId } from \"../../../core/kernel/ids/service.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { type ServiceEntry } from \"../../../core/repo/entries.js\";\nimport { permissionsPath, servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { readVocabulary } from \"../../../core/permissions/permissions.js\";\nimport { readCapabilityVocabulary } from \"../../../core/capabilities/capabilities.js\";\nimport {\n capabilityDocFindings,\n capabilityRequirementIndex,\n docMissingFindings,\n gradableCapabilityIds,\n invalidVocabularyFinding,\n unrealizedFindings,\n} from \"../../../core/capabilities/findings.js\";\nimport { useCaseRequirementClaims } from \"../../../core/usecases/capability.js\";\nimport type { ParsedView } from \"../../../core/c4/parsed/dynamic-views.js\";\nimport { capabilityRollup, usedCapabilities } from \"../../../core/capabilities/rollup.js\";\nimport { requirementUnrealizedFindings } from \"../../../core/capabilities/realizes/findings.js\";\nimport { parseRequirements } from \"../../../core/document/parse.js\";\nimport { type Requirement } from \"../../../core/document/spec.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { EXTERNAL_TAG, PLATFORM_TAG } from \"../../../core/vocabulary/maturity.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\n/**\n * Consumers at which an untagged external hub starts to warn. Three, not two:\n * two services sharing a dependency is a fact about those two services, the\n * same edge from three is a pattern about the element — and three is where\n * the shared hub starts making the map unreadable.\n */\nexport const PLATFORM_CANDIDATE_MIN_CONSUMERS = 3;\n\n/** The element kind read as a datastore, compared case-insensitively. */\nconst DATASTORE_KIND = \"database\";\n\nexport interface FleetShape {\n /** Service-LEVEL elements — the fleet map's own boxes (landscape.ts `drawn`). */\n drawn: Elem[];\n relationships: Rel[];\n /** The services/<id>/ directories that exist. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver every edge join uses. */\n resolve: (id: string) => string;\n /**\n * Where a service id's directory actually sits, repo-relative and without a\n * trailing slash. Injected rather than joined: only the enumeration knows\n * whether a service is filed under a subsystem, and a fix instruction naming\n * the wrong directory is worse than one naming none.\n */\n pathOf: (id: string) => string;\n}\n\nexport function fleetShapeFindings(shape: FleetShape): Finding[] {\n const { drawn, relationships, services, resolve } = shape;\n const findings: Finding[] = [];\n\n // Distinct consumer SERVICES of an element, its nested children included: an\n // edge into `kafka.paymentEvents` is consumption of `kafka`. Persons, other\n // external systems and unresolved sources never count — a consumer is a\n // source the shared resolver files under a real services/<id>/, so two edges\n // from one service are one consumer.\n const consumersOf = (e: Elem): string[] => {\n const out = new Set<string>();\n for (const r of relationships) {\n if (r.target !== e.id && !r.target.startsWith(`${e.id}.`)) continue;\n const svc = resolve(r.source);\n if (services.has(svc)) out.add(svc);\n }\n return [...out].sort();\n };\n\n for (const e of drawn) {\n // An element that stands for a real service is the fleet itself, not its\n // shape: many consumers is what a well-used service looks like, and a\n // datastore bound to a directory is graded by the binding checks instead.\n if (e.service !== undefined || services.has(e.title)) continue;\n const consumers = consumersOf(e);\n\n if (\n e.tags.includes(EXTERNAL_TAG) &&\n !e.tags.includes(PLATFORM_TAG) &&\n consumers.length >= PLATFORM_CANDIDATE_MIN_CONSUMERS\n ) {\n findings.push({\n severity: \"warn\",\n code: \"landscape.platform-candidate\",\n subject: e.title,\n message:\n `landscape: '${e.title}' is consumed by ${consumers.length} services (${consumers.join(\", \")}) ` +\n `and is not tagged #platform — a hub like this takes one more edge per modelled service until ` +\n `the fleet view is unreadable. Declare \\`tag platform\\` in the specification block and tag the ` +\n `element (LikeC4 refuses an undeclared tag, so both steps are needed): the fleet view then ` +\n `excludes it, and a platform view over \\`include * -> element.tag = #platform\\` keeps ` +\n `\"who depends on it\" answerable`,\n });\n }\n\n if (e.kind.toLowerCase() !== DATASTORE_KIND || consumers.length === 0) continue;\n if (consumers.length === 1) {\n findings.push({\n severity: \"warn\",\n code: \"landscape.datastore-private\",\n subject: e.title,\n message:\n `landscape: '${e.title}' is a datastore with a single consumer at fleet level ` +\n `('${consumers[0]!}') — drawn as a peer it reads as a system in its own right, available to ` +\n `be depended on. Move it into ${shape.pathOf(consumers[0]!)}/model.likec4 as a nested container ` +\n `and delete it here, or add the second consumer's edge if another service really reaches ` +\n `the same data`,\n });\n } else {\n findings.push({\n severity: \"warn\",\n code: \"landscape.datastore-shared\",\n subject: e.title,\n message:\n `landscape: '${e.title}' is a datastore shared by ${consumers.length} services ` +\n `(${consumers.join(\", \")}) — the strongest coupling two services can have, and the hardest ` +\n `to undo. Shared means the same DATA: if they read the same tables or keys, keep it drawn ` +\n `and let this warning state the coupling; if they only share a host or cluster (two schemas, ` +\n `two lock paths), that is operational blast radius — a runbook fact — and the honest model ` +\n `is one private store per service`,\n });\n }\n }\n return findings;\n}\n\n/**\n * The #external element that declares it PUBLISHES this message, or null —\n * the landscape-side half of the event spine's fleet question, beside the\n * other checks that read tags off the drawn map.\n *\n * Positive evidence only: an edge carrying `metadata { publishes '<msg>' }`\n * whose source element — itself or a declared ancestor, since the edge may\n * point out of a topic nested inside the broker — is tagged #external and\n * resolves to no known service directory. The resolution guard is what keeps\n * a mis-tagged internal service reading as unproduced: a tag is cheap to\n * write, and a services/<id>/ directory outranks it.\n */\n/**\n * The authorization vocabulary graded against the fleet that uses it — the half\n * of the axis no single service can answer.\n *\n * `permissions.invalid` is an error and it is the only one reported when it\n * fires: a vocabulary nobody can read resolves nothing, so every `Requires:`\n * line in the fleet would follow it as `permissions.unknown`, and a hundred\n * findings about one broken file is a cascade, not a diagnosis.\n *\n * `permissions.unenforced` is the mirror of `api.ungoverned`, and it earns its\n * place for the same reason: a vocabulary is only worth what cites it. A\n * permission nothing requires is either a rule that was removed and left its\n * word behind, or a word nobody adopted — both are drift, and neither is\n * visible from inside the file. Warn, because the honest answer is sometimes\n * \"not modelled yet\".\n */\nexport async function permissionFindings(\n docsDir: DocsDir,\n services: ServiceEntry[],\n fleet: FleetContext | undefined,\n): Promise<Finding[]> {\n const vocabulary = await readVocabulary(permissionsPath(docsDir));\n if (!vocabulary.present) return [];\n if (vocabulary.invalid !== undefined) {\n return [\n {\n severity: \"error\",\n code: \"permissions.invalid\",\n message:\n `landscape: architecture/permissions.yaml does not read as a vocabulary — ${vocabulary.invalid}. ` +\n \"Every `Requires:` line in the fleet resolves against this file, so none of them can be graded until it parses. \" +\n \"The shape is `subjects: {<kind>: {description}}` then `permissions: {<kind>: {<name>: {description, owned_by, enforced_by}}}`.\",\n },\n ];\n }\n const used = new Set<string>();\n for (const entry of services) {\n const paths = servicePathsAt(entry.dir);\n for (const path of [paths.spec, paths.archSpec]) {\n // Existence first, and for BOTH readers: arch.spec.md is optional (most\n // of a legacy fleet has none) and spec.md is missing on a half-adopted\n // service. `FleetContext.readRequirements` throws ENOENT, which surfaces\n // as `repository-unavailable` and takes the whole `--all` run down — an\n // absent optional artifact must never be able to do that.\n if (!existsSync(path)) continue;\n const reqs = fleet === undefined ? await readRequirementsAt(path) : await fleet.readRequirements(path);\n for (const r of reqs) {\n if (r.kind !== \"REMOVED\") for (const p of r.requires) used.add(p);\n }\n }\n }\n const unenforced = [...vocabulary.byId.keys()].filter((id) => !used.has(id));\n if (unenforced.length === 0) return [];\n return [\n {\n severity: \"warn\",\n code: \"permissions.unenforced\",\n message: `landscape: ${unenforced.length} declared permission(s) that no requirement's \\`Requires:\\` line names — write the requirement that gates on each, or drop the declaration`,\n details: unenforced,\n },\n ];\n}\n\n/** The uncached read, for the `--service` path where no fleet context exists. */\nasync function readRequirementsAt(path: string): Promise<Requirement[]> {\n if (!existsSync(path)) return [];\n return parseRequirements(await readFile(path, \"utf8\"));\n}\n\n/**\n * The capability vocabulary graded against the fleet that realizes it — the\n * axis's fleet half, beside permissionFindings and shaped by the same two\n * verdicts. `capability.invalid` is a `validate --all` run's ONE finding about\n * an unreadable file — fleet scope, so single-target runs stay silent about\n * it — and it suppresses the rest of the family (the unknown\n * grades at every service target return [] for the same vocabulary — one\n * breach, one finding, never a cascade). `capability.unrealized` is one warn\n * PER declared-but-unnamed capability, subject = the id, because the roadmap's\n * criterion is 'one warning per capability, not one per service'. Holding\n * NEITHER `architecture/capabilities.yaml` nor `capabilities/` is total\n * silence: the fleet's own files are this axis's opt-in, either of them.\n *\n * The used set comes through capabilityRollup with the fleet's own cached\n * reader, so under --all the walk re-parses nothing the service targets have\n * not already paid for — and the rollup, the warn and `loam list capabilities`\n * can never disagree about what \"realized\" means.\n *\n * The AUTHORED documents are graded here too, and they ride this function\n * rather than a target of their own for `viewsStaleFindings`' reason one tree\n * over: a capability belongs to no service, so only the fleet run has the whole\n * vocabulary and the enumerated tree in view at once. Their reads are NOT\n * wrapped, deliberately — a `capabilities/<id>/spec.md` that is not UTF-8 takes\n * the run down exactly as a `services/<id>/spec.md` does, because a document\n * loam cannot decode is a refusal everywhere else in the product and a quiet\n * \"zero requirements\" here would grade a whole capability green on bytes\n * nobody read.\n */\nexport async function capabilityFleetFindings(\n docsDir: DocsDir,\n services: ServiceEntry[],\n fleet: FleetContext | undefined,\n flows: readonly ParsedView[] | null,\n): Promise<Finding[]> {\n const vocab = fleet === undefined ? await readCapabilityVocabulary(docsDir) : await fleet.capabilities(docsDir);\n if (!vocab.present) return [];\n const read = fleet === undefined ? readRequirementsAt : (p: string) => fleet.readRequirements(p);\n const authored: Finding[] = docMissingFindings(vocab.tree);\n for (const doc of vocab.tree.docs) authored.push(...capabilityDocFindings(await read(doc.spec), doc.id));\n\n const invalid = invalidVocabularyFinding(vocab);\n if (invalid !== null) return [invalid, ...authored];\n // A USE CASE IS THE OTHER WAY TO KEEP A PROMISE, and it is the only carrier a\n // cross-service criterion has: \"I enter a login and a password and I am in\"\n // belongs to no single service's spec, so a fleet could satisfy it perfectly\n // through a `#cap-`/`#req-` tagged flow and still be told nobody realizes it.\n //\n // The claims go INTO the rollup rather than beside it, so both unrealized\n // grades and `loam list capabilities` read one set of rows. `flows === null`\n // means loam could not READ the flows (no preload, or an `architecture/` that\n // did not parse); the key is then OMITTED rather than empty, and every\n // `keptBy` below is `undefined` — the three-state contract `rollup.ts` states.\n const index = await capabilityRequirementIndex(vocab, read);\n const keptByFlows =\n flows === null\n ? undefined\n : useCaseRequirementClaims(flows, gradableCapabilityIds(vocab), (c) => index.byCapability.get(c));\n const rows = await capabilityRollup({ services, vocab, read, ...(keptByFlows === undefined ? {} : { keptByFlows }) });\n\n // The two unrealized grades read the SAME rollup, which is what keeps them\n // from disagreeing about the word: a capability is unrealized when nothing\n // names it by any join, and one of its requirements is unrealized when nothing\n // keeps that promise. Both are warnings, and the second is the one that\n // survives a healthy-looking row — a capability with four requirements and\n // three kept reports nothing at all through the first.\n //\n // The suspension falls out of the DATA rather than a branch: `keptBy` absent\n // means nobody looked, and a promise nobody looked for is not a promise\n // reported unkept.\n const unrealizedRequirements = rows.flatMap((row) =>\n (row.requirements ?? [])\n .filter((req) => req.realizedBy.length === 0 && req.keptBy?.length === 0)\n .map((req) => ({ capability: row.id, id: req.id, name: req.name })),\n );\n return [\n ...authored,\n ...unrealizedFindings(vocab, usedCapabilities(rows)),\n ...requirementUnrealizedFindings(unrealizedRequirements),\n ];\n}\n\nexport function externalProducerOf(\n message: string,\n land: LoadedDoc | null,\n landSvcOf: ((id: string) => DeclaredService) | null,\n known: ReadonlySet<RawServiceId>,\n): string | null {\n if (land === null || land.errors.length > 0 || landSvcOf === null) return null;\n const knownIds: ReadonlySet<string> = known;\n const byId = new Map(land.elements.map((e) => [e.id, e]));\n for (const r of land.relationships) {\n if (r.publishes !== message) continue;\n if (knownIds.has(landSvcOf(r.source))) continue;\n for (let id = r.source; ; ) {\n const e = byId.get(id);\n if (e !== undefined && e.tags.includes(EXTERNAL_TAG)) return e.title;\n const dot = id.lastIndexOf(\".\");\n if (dot === -1) break;\n id = id.slice(0, dot);\n }\n }\n return null;\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The health.yaml → model reconciliation: every id the `dependencies:` block
|
|
3
|
+
* declares must be something this service's OWN model answers to.
|
|
4
|
+
*
|
|
5
|
+
* Without this reconciliation, a disabled optional service registry can leave
|
|
6
|
+
* the model, landscape, arch spec and runbook corrected while health.yaml still
|
|
7
|
+
* declares `serviceRegistry` as a `critical: startup` dependency under a green
|
|
8
|
+
* validator — in the file an on-call engineer reaches for first. Its
|
|
9
|
+
* dependencies block was otherwise free text as far as validation was
|
|
10
|
+
* concerned.
|
|
11
|
+
*
|
|
12
|
+
* Resolution is against the SERVICE'S OWN model — deliberately never the
|
|
13
|
+
* landscape. A private datastore lives nested inside the service element
|
|
14
|
+
* (`landscape.datastore-private` is the check pushing it there), so the
|
|
15
|
+
* landscape carries only what crosses the service boundary, while the model
|
|
16
|
+
* carries everything the service touches — and that is the set the on-call
|
|
17
|
+
* file has to agree with. An id resolves by the same names every other join
|
|
18
|
+
* uses: the element's likec4 id, its `metadata { service }` binding, or its
|
|
19
|
+
* title. Exact match, no case folding — a fold would be a permanent
|
|
20
|
+
* equivalence semantic no other loam join has, able to silently join two
|
|
21
|
+
* genuinely different names; the did-you-mean hint bridges the typo case
|
|
22
|
+
* instead.
|
|
23
|
+
*/
|
|
24
|
+
import { closeIds } from "../../../core/c4/arch.js";
|
|
25
|
+
import {} from "../../../core/c4/likec4.js";
|
|
26
|
+
import { elementService } from "../../../core/c4/resolve/service.js";
|
|
27
|
+
import {} from "../../../core/vocabulary/report.js";
|
|
28
|
+
export function healthDependencyFindings(deps) {
|
|
29
|
+
const { service, dependencies, elements, modelPath } = deps;
|
|
30
|
+
// An absent or unparsable model already has its own finding, and grading
|
|
31
|
+
// health.yaml against a model nobody could read would manufacture warns out
|
|
32
|
+
// of the wrong file's breakage.
|
|
33
|
+
if (elements.length === 0)
|
|
34
|
+
return [];
|
|
35
|
+
const findings = [];
|
|
36
|
+
for (const dep of dependencies) {
|
|
37
|
+
if (elements.some((e) => e.id === dep || elementService(e) === dep))
|
|
38
|
+
continue;
|
|
39
|
+
const pool = [...new Set(elements.flatMap((e) => [e.id, elementService(e)]))];
|
|
40
|
+
const close = closeIds(dep, pool);
|
|
41
|
+
findings.push({
|
|
42
|
+
severity: "warn",
|
|
43
|
+
code: "health.dependency-unmodelled",
|
|
44
|
+
subject: service,
|
|
45
|
+
message: `${service}: health.yaml declares dependency '${dep}' but nothing in ` +
|
|
46
|
+
`${modelPath} answers to that name — either a dependency nobody ` +
|
|
47
|
+
`modelled, or one that no longer exists. Model it (an external system as its own element ` +
|
|
48
|
+
`with the edge this service actually has; a private store as a nested container), or ` +
|
|
49
|
+
`delete the entry` +
|
|
50
|
+
(close.length > 0 ? `. Did you mean: ${close.join(", ")}?` : ""),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return findings;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=health-deps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-deps.js","sourceRoot":"","sources":["../../../../src/commands/validate/checks/health-deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAa,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAgB,MAAM,oCAAoC,CAAC;AAkBlE,MAAM,UAAU,wBAAwB,CAAC,IAAgB;IACvD,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC5D,yEAAyE;IACzE,4EAA4E;IAC5E,gCAAgC;IAChC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,cAAc,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;YAAE,SAAS;QAC9E,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC,CAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QACxF,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,8BAA8B;YACpC,OAAO,EAAE,OAAO;YAChB,OAAO,EACL,GAAG,OAAO,sCAAsC,GAAG,mBAAmB;gBACtE,GAAG,SAAS,qDAAqD;gBACjE,0FAA0F;gBAC1F,sFAAsF;gBACtF,kBAAkB;gBAClB,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,mBAAmB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * The health.yaml → model reconciliation: every id the `dependencies:` block\n * declares must be something this service's OWN model answers to.\n *\n * Without this reconciliation, a disabled optional service registry can leave\n * the model, landscape, arch spec and runbook corrected while health.yaml still\n * declares `serviceRegistry` as a `critical: startup` dependency under a green\n * validator — in the file an on-call engineer reaches for first. Its\n * dependencies block was otherwise free text as far as validation was\n * concerned.\n *\n * Resolution is against the SERVICE'S OWN model — deliberately never the\n * landscape. A private datastore lives nested inside the service element\n * (`landscape.datastore-private` is the check pushing it there), so the\n * landscape carries only what crosses the service boundary, while the model\n * carries everything the service touches — and that is the set the on-call\n * file has to agree with. An id resolves by the same names every other join\n * uses: the element's likec4 id, its `metadata { service }` binding, or its\n * title. Exact match, no case folding — a fold would be a permanent\n * equivalence semantic no other loam join has, able to silently join two\n * genuinely different names; the did-you-mean hint bridges the typo case\n * instead.\n */\nimport { closeIds } from \"../../../core/c4/arch.js\";\nimport { type Elem } from \"../../../core/c4/likec4.js\";\nimport { elementService } from \"../../../core/c4/resolve/service.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\n\nexport interface HealthDeps {\n service: string;\n /**\n * The resolved path of the service's own `model.likec4` — the file this\n * check reads and the one its message names. Handed in rather than joined\n * from the id: `services/<id>/model.likec4` is right only for an unfiled\n * service, and every sibling finding in this command prints the resolved\n * path of the file it means.\n */\n modelPath: string;\n /** `dependencies:` ids, already muted by the caller when health.yaml is unreadable. */\n dependencies: string[];\n /** The service's own parsed model — empty when absent or invalid, which mutes the check. */\n elements: Elem[];\n}\n\nexport function healthDependencyFindings(deps: HealthDeps): Finding[] {\n const { service, dependencies, elements, modelPath } = deps;\n // An absent or unparsable model already has its own finding, and grading\n // health.yaml against a model nobody could read would manufacture warns out\n // of the wrong file's breakage.\n if (elements.length === 0) return [];\n const findings: Finding[] = [];\n for (const dep of dependencies) {\n if (elements.some((e) => e.id === dep || elementService(e) === dep)) continue;\n const pool = [...new Set(elements.flatMap((e) => [e.id, elementService(e) as string]))];\n const close = closeIds(dep, pool);\n findings.push({\n severity: \"warn\",\n code: \"health.dependency-unmodelled\",\n subject: service,\n message:\n `${service}: health.yaml declares dependency '${dep}' but nothing in ` +\n `${modelPath} answers to that name — either a dependency nobody ` +\n `modelled, or one that no longer exists. Model it (an external system as its own element ` +\n `with the edge this service actually has; a private store as a nested container), or ` +\n `delete the entry` +\n (close.length > 0 ? `. Did you mean: ${close.join(\", \")}?` : \"\"),\n });\n }\n return findings;\n}\n"]}
|