@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,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which hops of which business flows a contract removal breaks.
|
|
3
|
+
*
|
|
4
|
+
* `loam diff --base` already answers "who still depends on this operation" with
|
|
5
|
+
* landscape edges and other services' living requirements. Both are true and
|
|
6
|
+
* neither is the sentence a reviewer wants: an edge is a line in a diagram and a
|
|
7
|
+
* requirement is a heading in somebody else's spec, while "step 4 of Checkout
|
|
8
|
+
* stops working" is the fact a human weighs a pull request against. This module
|
|
9
|
+
* is the join that produces that sentence, and it produces NOTHING ELSE — no
|
|
10
|
+
* finding, no code, no severity. `core/diff/victims.ts` folds its answers into
|
|
11
|
+
* the `details[]` of the two codes that already exist.
|
|
12
|
+
*
|
|
13
|
+
* ## Attributed is a victim; contested is a suspension
|
|
14
|
+
*
|
|
15
|
+
* The distinction is the whole reason this module is careful rather than a
|
|
16
|
+
* filter. `attributeStep` (core/c4/resolve/steps.ts) grades a hop by the DISTINCT-op
|
|
17
|
+
* count across the relationships backing it:
|
|
18
|
+
*
|
|
19
|
+
* - `attributed` with `op === X` means every relationship behind the hop agrees
|
|
20
|
+
* the hop exercises X. Remove X and the flow breaks at that hop. That is
|
|
21
|
+
* positive evidence, and it is a victim.
|
|
22
|
+
* - `contested` means the relationships DISAGREE, so loam does not know which
|
|
23
|
+
* operation the hop exercises. It must not claim the flow breaks — a guessed
|
|
24
|
+
* victim reads exactly like a real one and turns a warn into an error on
|
|
25
|
+
* somebody's PR — and it must not go silent either, because silence here is
|
|
26
|
+
* the fail-open shape `ConsumerScan.suspended` exists to forbid. It is a
|
|
27
|
+
* suspension: loam says it could not answer.
|
|
28
|
+
*
|
|
29
|
+
* A contested hop is suspended only when X is among its candidate operations.
|
|
30
|
+
* That is not a noise trade, it is the honest boundary: the candidates ARE the
|
|
31
|
+
* relationships backing the hop, so an operation none of them names is an
|
|
32
|
+
* operation the hop cannot be exercising under any reading of the disagreement.
|
|
33
|
+
* Suspending every contested hop in the fleet on every removal would report
|
|
34
|
+
* doubt loam does not actually have.
|
|
35
|
+
*
|
|
36
|
+
* An `unbacked` hop is neither. Nothing in the model backs it, so it names no
|
|
37
|
+
* operation of anybody's contract — `usecase.step-unbacked` is already the error
|
|
38
|
+
* that convicts it, and repeating that here would file a modelling defect
|
|
39
|
+
* against whoever happened to remove an endpoint.
|
|
40
|
+
*/
|
|
41
|
+
import { attributeStep } from "../c4/resolve/steps.js";
|
|
42
|
+
import { hopPlace } from "./place.js";
|
|
43
|
+
const EMPTY = { breaks: [], unsure: [] };
|
|
44
|
+
/** Every hop of every graded use case, view order preserved, so output is stable. */
|
|
45
|
+
function hops(scan) {
|
|
46
|
+
return scan.views.flatMap((view) => view.steps.map((step) => ({ view, step })));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The flows one removed operation breaks, and the ones loam cannot answer for.
|
|
50
|
+
*
|
|
51
|
+
* An `unreadable` scan contributes NOTHING — neither break nor suspension — and
|
|
52
|
+
* that is `core/diff/victims.ts`'s own landscape doctrine applied one axis over:
|
|
53
|
+
* an unreadable fleet map proves nothing either way, `landscape.invalid` is
|
|
54
|
+
* validate's finding to make, and inventing doubt out of a parse error would
|
|
55
|
+
* point the reviewer at a file their change never touched. The caller reports
|
|
56
|
+
* the hole; this join stays quiet about it.
|
|
57
|
+
*/
|
|
58
|
+
export function hopsExercising(scan, removed) {
|
|
59
|
+
if (scan.kind !== "read")
|
|
60
|
+
return EMPTY;
|
|
61
|
+
const out = { breaks: [], unsure: [] };
|
|
62
|
+
for (const { view, step } of hops(scan)) {
|
|
63
|
+
const attribution = attributeStep(step, scan.model);
|
|
64
|
+
if (attribution.verdict === "unbacked")
|
|
65
|
+
continue;
|
|
66
|
+
// The callee, through the SAME resolver every other join in this run uses,
|
|
67
|
+
// so a hop drawn into a modelled container `payment.api` is filed against
|
|
68
|
+
// payment-service rather than against a service called "api".
|
|
69
|
+
if (scan.resolve(attribution.to) !== removed.provider)
|
|
70
|
+
continue;
|
|
71
|
+
if (attribution.verdict === "attributed") {
|
|
72
|
+
if (attribution.op === removed.op)
|
|
73
|
+
out.breaks.push(hopPlace(view, step));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!attribution.ops.includes(removed.op))
|
|
77
|
+
continue;
|
|
78
|
+
out.unsure.push(`${hopPlace(view, step)} — ${attribution.rels.length} relationships back ` +
|
|
79
|
+
`${attribution.from} -> ${attribution.to} and they name different operations, so whether this hop ` +
|
|
80
|
+
`exercises '${removed.op}' could not be answered`);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The flows one removed message breaks.
|
|
86
|
+
*
|
|
87
|
+
* No suspension arm, and the asymmetry is a fact about the model rather than an
|
|
88
|
+
* omission: `attributeStep` contests OPERATIONS, because that is where two
|
|
89
|
+
* relationships between one pair of endpoints can disagree. A `consumes` edge
|
|
90
|
+
* names a message outright, so a hop either is backed by one or is not — there
|
|
91
|
+
* is no disagreement for loam to refuse to resolve.
|
|
92
|
+
*
|
|
93
|
+
* The provider exclusion mirrors `messageConsumers` exactly: an edge INTO the
|
|
94
|
+
* provider is the provider's own consumption, which leaves with the same change,
|
|
95
|
+
* so a hop backed only by that edge is not a victim of it.
|
|
96
|
+
*/
|
|
97
|
+
export function hopsConsuming(scan, removed) {
|
|
98
|
+
if (scan.kind !== "read")
|
|
99
|
+
return [];
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const { view, step } of hops(scan)) {
|
|
102
|
+
const attribution = attributeStep(step, scan.model);
|
|
103
|
+
if (attribution.verdict === "unbacked")
|
|
104
|
+
continue;
|
|
105
|
+
const consumes = attribution.rels.some((rel) => rel.consumes === removed.name && scan.resolve(rel.target) !== removed.provider);
|
|
106
|
+
if (consumes)
|
|
107
|
+
out.push(hopPlace(view, step));
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../../src/core/usecases/operations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which hops of which business flows a contract removal breaks.\n *\n * `loam diff --base` already answers \"who still depends on this operation\" with\n * landscape edges and other services' living requirements. Both are true and\n * neither is the sentence a reviewer wants: an edge is a line in a diagram and a\n * requirement is a heading in somebody else's spec, while \"step 4 of Checkout\n * stops working\" is the fact a human weighs a pull request against. This module\n * is the join that produces that sentence, and it produces NOTHING ELSE — no\n * finding, no code, no severity. `core/diff/victims.ts` folds its answers into\n * the `details[]` of the two codes that already exist.\n *\n * ## Attributed is a victim; contested is a suspension\n *\n * The distinction is the whole reason this module is careful rather than a\n * filter. `attributeStep` (core/c4/resolve/steps.ts) grades a hop by the DISTINCT-op\n * count across the relationships backing it:\n *\n * - `attributed` with `op === X` means every relationship behind the hop agrees\n * the hop exercises X. Remove X and the flow breaks at that hop. That is\n * positive evidence, and it is a victim.\n * - `contested` means the relationships DISAGREE, so loam does not know which\n * operation the hop exercises. It must not claim the flow breaks — a guessed\n * victim reads exactly like a real one and turns a warn into an error on\n * somebody's PR — and it must not go silent either, because silence here is\n * the fail-open shape `ConsumerScan.suspended` exists to forbid. It is a\n * suspension: loam says it could not answer.\n *\n * A contested hop is suspended only when X is among its candidate operations.\n * That is not a noise trade, it is the honest boundary: the candidates ARE the\n * relationships backing the hop, so an operation none of them names is an\n * operation the hop cannot be exercising under any reading of the disagreement.\n * Suspending every contested hop in the fleet on every removal would report\n * doubt loam does not actually have.\n *\n * An `unbacked` hop is neither. Nothing in the model backs it, so it names no\n * operation of anybody's contract — `usecase.step-unbacked` is already the error\n * that convicts it, and repeating that here would file a modelling defect\n * against whoever happened to remove an endpoint.\n */\nimport { attributeStep } from \"../c4/resolve/steps.js\";\nimport type { ParsedStep, ParsedView } from \"../c4/parsed/dynamic-views.js\";\nimport { hopPlace } from \"./place.js\";\nimport type { UseCaseScan } from \"./fleet.js\";\n\n/**\n * What one contract removal does to the fleet's use cases.\n *\n * Two lists rather than `victims`/`suspended` — the vocabulary\n * `core/diff/victims.ts` uses — because this module answers about FLOWS and that\n * one answers about consumers. The caller does the folding, and naming the two\n * differently is what stops a reader taking this for a second implementation of\n * `ConsumerScan` (it is not: it never scans a document, and it cannot be\n * unreadable — the scan it runs over was already graded).\n */\nexport interface UseCaseHits {\n /** Hops the removal breaks, each spelled by `hopPlace`. */\n breaks: string[];\n /** Hops loam refuses to grade, each saying why in its own sentence. */\n unsure: string[];\n}\n\n/** The one removal a scan is being asked about. */\nexport interface RemovedOperation {\n /** The service dropping it — the same id the diff files its findings under. */\n provider: string;\n /** The operationId leaving the contract. */\n op: string;\n}\n\nconst EMPTY: UseCaseHits = { breaks: [], unsure: [] };\n\n/** Every hop of every graded use case, view order preserved, so output is stable. */\nfunction hops(scan: Extract<UseCaseScan, { kind: \"read\" }>): Array<{ view: ParsedView; step: ParsedStep }> {\n return scan.views.flatMap((view) => view.steps.map((step) => ({ view, step })));\n}\n\n/**\n * The flows one removed operation breaks, and the ones loam cannot answer for.\n *\n * An `unreadable` scan contributes NOTHING — neither break nor suspension — and\n * that is `core/diff/victims.ts`'s own landscape doctrine applied one axis over:\n * an unreadable fleet map proves nothing either way, `landscape.invalid` is\n * validate's finding to make, and inventing doubt out of a parse error would\n * point the reviewer at a file their change never touched. The caller reports\n * the hole; this join stays quiet about it.\n */\nexport function hopsExercising(scan: UseCaseScan, removed: RemovedOperation): UseCaseHits {\n if (scan.kind !== \"read\") return EMPTY;\n const out: UseCaseHits = { breaks: [], unsure: [] };\n for (const { view, step } of hops(scan)) {\n const attribution = attributeStep(step, scan.model);\n if (attribution.verdict === \"unbacked\") continue;\n // The callee, through the SAME resolver every other join in this run uses,\n // so a hop drawn into a modelled container `payment.api` is filed against\n // payment-service rather than against a service called \"api\".\n if (scan.resolve(attribution.to) !== removed.provider) continue;\n if (attribution.verdict === \"attributed\") {\n if (attribution.op === removed.op) out.breaks.push(hopPlace(view, step));\n continue;\n }\n if (!attribution.ops.includes(removed.op)) continue;\n out.unsure.push(\n `${hopPlace(view, step)} — ${attribution.rels.length} relationships back ` +\n `${attribution.from} -> ${attribution.to} and they name different operations, so whether this hop ` +\n `exercises '${removed.op}' could not be answered`,\n );\n }\n return out;\n}\n\n/** The one message removal a scan is being asked about. */\nexport interface RemovedMessage {\n /** The service dropping the declaration — the diff's own subject. */\n provider: string;\n /** The AsyncAPI message name leaving the contract. */\n name: string;\n}\n\n/**\n * The flows one removed message breaks.\n *\n * No suspension arm, and the asymmetry is a fact about the model rather than an\n * omission: `attributeStep` contests OPERATIONS, because that is where two\n * relationships between one pair of endpoints can disagree. A `consumes` edge\n * names a message outright, so a hop either is backed by one or is not — there\n * is no disagreement for loam to refuse to resolve.\n *\n * The provider exclusion mirrors `messageConsumers` exactly: an edge INTO the\n * provider is the provider's own consumption, which leaves with the same change,\n * so a hop backed only by that edge is not a victim of it.\n */\nexport function hopsConsuming(scan: UseCaseScan, removed: RemovedMessage): string[] {\n if (scan.kind !== \"read\") return [];\n const out: string[] = [];\n for (const { view, step } of hops(scan)) {\n const attribution = attributeStep(step, scan.model);\n if (attribution.verdict === \"unbacked\") continue;\n const consumes = attribution.rels.some(\n (rel) => rel.consumes === removed.name && scan.resolve(rel.target) !== removed.provider,\n );\n if (consumes) out.push(hopPlace(view, step));\n }\n return out;\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The document the view was written in, spelled repo-relative.
|
|
3
|
+
*
|
|
4
|
+
* `sourcePath` is relative to the LikeC4 PROJECT root, which for
|
|
5
|
+
* `loadArchitecture` is `architecture/`, so the prefix is added here and nowhere
|
|
6
|
+
* else. The caller owes one thing in exchange: only views that came from
|
|
7
|
+
* `loadProject` may be spelled with this, because the single-file loaders name
|
|
8
|
+
* every document after themselves (`source.c4`) and this function would then
|
|
9
|
+
* spell a path that has never existed.
|
|
10
|
+
*
|
|
11
|
+
* An ABSENT `sourcePath` names the directory rather than guessing a file.
|
|
12
|
+
* LikeC4 synthesizes only element views and `readDynamicViews` drops those, so
|
|
13
|
+
* nothing at the 1.59.2 pin reaches this arm; if a release ever stopped emitting
|
|
14
|
+
* the field, falling back to `architecture/landscape.likec4` would put a false
|
|
15
|
+
* cross-reference in a message.
|
|
16
|
+
*/
|
|
17
|
+
export function viewFile(view) {
|
|
18
|
+
return view.sourcePath === undefined ? "architecture/" : `architecture/${view.sourcePath}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `step 4 'confirms the order'`, or `step 4` where the author labelled nothing.
|
|
22
|
+
*
|
|
23
|
+
* The ordinal is the view's own leaf numbering (`ParsedStep.ordinal`), which
|
|
24
|
+
* skips group brackets — so it counts what the author sees on the diagram, and
|
|
25
|
+
* a message naming step 4 is naming the fourth arrow.
|
|
26
|
+
*/
|
|
27
|
+
export function stepPlace(step) {
|
|
28
|
+
return step.title === undefined ? `step ${step.ordinal}` : `step ${step.ordinal} '${step.title}'`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A use case and one of its hops, named the way a reader outside `validate`
|
|
32
|
+
* meets them: `use case 'uc_checkout' step 4 'authorizes the payment'
|
|
33
|
+
* (architecture/usecases/checkout.likec4)`.
|
|
34
|
+
*
|
|
35
|
+
* The file goes LAST here and first in validate's `viewPlace`, and that is not
|
|
36
|
+
* an inconsistency to tidy away. A validate finding is a line in a report about
|
|
37
|
+
* documents, so it leads with the document; a diff victim is one entry in a
|
|
38
|
+
* `details[]` list whose other entries name edges and requirements, so it has to
|
|
39
|
+
* lead with the thing that broke and carry the file as the place to go fix it.
|
|
40
|
+
*/
|
|
41
|
+
export function hopPlace(view, step) {
|
|
42
|
+
return `use case '${view.id}' ${stepPlace(step)} (${viewFile(view)})`;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=place.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"place.js","sourceRoot":"","sources":["../../../src/core/usecases/place.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAgB;IACvC,OAAO,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,UAAU,EAAE,CAAC;AAC7F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,IAAgB;IACxC,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,KAAK,GAAG,CAAC;AACpG,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAgB,EAAE,IAAgB;IACzD,OAAO,aAAa,IAAI,CAAC,EAAE,KAAK,SAAS,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;AACxE,CAAC","sourcesContent":["/**\n * Where a use case lives and where one of its hops sits — the two spellings\n * every surface that mentions a use case has to agree on.\n *\n * They began in `commands/validate/fleet/usecases/place.ts`, beside the four\n * `usecase.*` graders that were their only reader. `loam diff` naming the flow a\n * removal breaks is the second reader and `loam delta`/`loam context` are the\n * third and fourth, and `core/` may not import from `commands/` (AGENTS.md), so\n * the alternative was a second spelling of `architecture/<sourcePath>` in core.\n * The two would drift silently and in the worst possible direction: a file path\n * in a message that no longer names the file the view is in is how the next\n * reader stops looking (docs/CODE-STYLE.md).\n *\n * What did NOT move is `viewPlace`, the `landscape: …` opening the validate\n * findings carry. That prefix is the name of a validate TARGET, so it is command\n * vocabulary — a `loam diff` finding is filed against a service, not against the\n * fleet map, and borrowing validate's prefix there would claim a target that run\n * does not have.\n */\nimport type { ParsedStep, ParsedView } from \"../c4/parsed/dynamic-views.js\";\n\n/**\n * The document the view was written in, spelled repo-relative.\n *\n * `sourcePath` is relative to the LikeC4 PROJECT root, which for\n * `loadArchitecture` is `architecture/`, so the prefix is added here and nowhere\n * else. The caller owes one thing in exchange: only views that came from\n * `loadProject` may be spelled with this, because the single-file loaders name\n * every document after themselves (`source.c4`) and this function would then\n * spell a path that has never existed.\n *\n * An ABSENT `sourcePath` names the directory rather than guessing a file.\n * LikeC4 synthesizes only element views and `readDynamicViews` drops those, so\n * nothing at the 1.59.2 pin reaches this arm; if a release ever stopped emitting\n * the field, falling back to `architecture/landscape.likec4` would put a false\n * cross-reference in a message.\n */\nexport function viewFile(view: ParsedView): string {\n return view.sourcePath === undefined ? \"architecture/\" : `architecture/${view.sourcePath}`;\n}\n\n/**\n * `step 4 'confirms the order'`, or `step 4` where the author labelled nothing.\n *\n * The ordinal is the view's own leaf numbering (`ParsedStep.ordinal`), which\n * skips group brackets — so it counts what the author sees on the diagram, and\n * a message naming step 4 is naming the fourth arrow.\n */\nexport function stepPlace(step: ParsedStep): string {\n return step.title === undefined ? `step ${step.ordinal}` : `step ${step.ordinal} '${step.title}'`;\n}\n\n/**\n * A use case and one of its hops, named the way a reader outside `validate`\n * meets them: `use case 'uc_checkout' step 4 'authorizes the payment'\n * (architecture/usecases/checkout.likec4)`.\n *\n * The file goes LAST here and first in validate's `viewPlace`, and that is not\n * an inconsistency to tidy away. A validate finding is a line in a report about\n * documents, so it leads with the document; a diff victim is one entry in a\n * `details[]` list whose other entries name edges and requirements, so it has to\n * lead with the thing that broke and carry the file as the place to go fix it.\n */\nexport function hopPlace(view: ParsedView, step: ParsedStep): string {\n return `use case '${view.id}' ${stepPlace(step)} (${viewFile(view)})`;\n}\n"]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which business flows run through a service — the blast radius `loam delta`
|
|
3
|
+
* and `loam status` report beside the requirements and contracts a feature
|
|
4
|
+
* changes.
|
|
5
|
+
*
|
|
6
|
+
* ## The join is on ENDPOINTS, never on relationships
|
|
7
|
+
*
|
|
8
|
+
* `core/usecases/operations.ts` asks what a hop EXERCISES, which needs the
|
|
9
|
+
* relationship behind it. This asks something weaker and more useful early:
|
|
10
|
+
* does this hop mention the service at all? So it resolves the step's own two
|
|
11
|
+
* endpoints and stops — no `attributeStep`, no candidate scan.
|
|
12
|
+
*
|
|
13
|
+
* That is the difference between answering and not answering for the service a
|
|
14
|
+
* feature is about to INTRODUCE. A flow drawn for work that has not happened yet
|
|
15
|
+
* has hops nothing in the model backs — `usecase.step-unbacked` is validate's
|
|
16
|
+
* finding about exactly that, and it is an ERROR precisely because the fleet map
|
|
17
|
+
* has not caught up — and a relationship join reports such a flow as touching
|
|
18
|
+
* nobody. The endpoints are still there; the arrow is what is missing. An
|
|
19
|
+
* implementer reading a delta brief needs to know the flow exists most of all in
|
|
20
|
+
* that state, because that is the state their change is supposed to end.
|
|
21
|
+
*
|
|
22
|
+
* ## Everything a caller sees, it sees per HOP
|
|
23
|
+
*
|
|
24
|
+
* A view is reported only when at least one of its hops touches the addressed
|
|
25
|
+
* services, and only those hops travel. "Checkout touches you" is not actionable;
|
|
26
|
+
* "Checkout touches you at step 4" is where the reader goes to look.
|
|
27
|
+
*/
|
|
28
|
+
import { compareIds } from "../repo/entries.js";
|
|
29
|
+
import { readUseCases } from "./fleet.js";
|
|
30
|
+
import { viewFile } from "./place.js";
|
|
31
|
+
/** The addressed services one hop mentions, source and target both, sorted and de-duplicated. */
|
|
32
|
+
function touched(step, scan, services) {
|
|
33
|
+
const hit = [step.source, step.target].map((id) => scan.resolve(id)).filter((id) => services.has(id));
|
|
34
|
+
return [...new Set(hit)].sort(compareIds);
|
|
35
|
+
}
|
|
36
|
+
/** One view's touching hops, in the author's own step order — the order the diagram reads in. */
|
|
37
|
+
function touchedView(view, scan, services) {
|
|
38
|
+
const steps = [];
|
|
39
|
+
for (const step of view.steps) {
|
|
40
|
+
const hit = touched(step, scan, services);
|
|
41
|
+
if (hit.length === 0)
|
|
42
|
+
continue;
|
|
43
|
+
steps.push({
|
|
44
|
+
ordinal: step.ordinal,
|
|
45
|
+
...(step.title === undefined ? {} : { title: step.title }),
|
|
46
|
+
source: step.source,
|
|
47
|
+
target: step.target,
|
|
48
|
+
services: hit,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (steps.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
return {
|
|
54
|
+
id: view.id,
|
|
55
|
+
...(view.title === undefined ? {} : { title: view.title }),
|
|
56
|
+
file: viewFile(view),
|
|
57
|
+
tags: [...view.tags].sort(compareIds),
|
|
58
|
+
steps,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The flows touching a set of services, over a scan somebody already has.
|
|
63
|
+
*
|
|
64
|
+
* Separate from `useCaseBlastRadius` below because `core/pack/joins.ts` reads
|
|
65
|
+
* the scan ONCE and asks two different questions of it — this one and the
|
|
66
|
+
* capability claim join — and a second read there would be a second LikeC4
|
|
67
|
+
* project load inside a single `loam context`.
|
|
68
|
+
*
|
|
69
|
+
* An `unreadable` scan answers with nothing. Every caller pairs this with the
|
|
70
|
+
* scan's own health, because an empty list means "no flow mentions these
|
|
71
|
+
* services" and "nobody could look" only when read together.
|
|
72
|
+
*/
|
|
73
|
+
export function flowsTouching(scan, services) {
|
|
74
|
+
if (scan.kind !== "read")
|
|
75
|
+
return [];
|
|
76
|
+
return (scan.views
|
|
77
|
+
.map((view) => touchedView(view, scan, services))
|
|
78
|
+
.filter((flow) => flow !== null)
|
|
79
|
+
// Sorted by (file, view id) rather than reported in LikeC4's own record
|
|
80
|
+
// order: nothing in loam has measured that the parse preserves declaration
|
|
81
|
+
// order, so a payload ordered by it would reorder under a dependency bump.
|
|
82
|
+
.sort((a, b) => compareIds(a.file, b.file) || compareIds(a.id, b.id)));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The flows touching a set of services, read and joined in one call.
|
|
86
|
+
*
|
|
87
|
+
* The load underneath is `readUseCases`, which carries its own cheap gate: a
|
|
88
|
+
* fleet whose `architecture/` documents never mention the reserved tag prefix
|
|
89
|
+
* answers `{ flows: [] }` without starting LikeC4 at all. That gate is what
|
|
90
|
+
* makes this callable from `loam delta`, which sits in `/loam-implement`'s inner
|
|
91
|
+
* loop and would otherwise pay a Langium workspace spin-up on every iteration of
|
|
92
|
+
* somebody's afternoon.
|
|
93
|
+
*/
|
|
94
|
+
export async function useCaseBlastRadius(req) {
|
|
95
|
+
const scan = await readUseCases({ docsDir: req.docsDir, known: req.known });
|
|
96
|
+
if (scan.kind !== "read") {
|
|
97
|
+
return { unreadable: true, ...(scan.errors[0] === undefined ? {} : { error: scan.errors[0] }), flows: [] };
|
|
98
|
+
}
|
|
99
|
+
return { unreadable: false, flows: flowsTouching(scan, req.services) };
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=touch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"touch.js","sourceRoot":"","sources":["../../../src/core/usecases/touch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which business flows run through a service — the blast radius `loam delta`\n * and `loam status` report beside the requirements and contracts a feature\n * changes.\n *\n * ## The join is on ENDPOINTS, never on relationships\n *\n * `core/usecases/operations.ts` asks what a hop EXERCISES, which needs the\n * relationship behind it. This asks something weaker and more useful early:\n * does this hop mention the service at all? So it resolves the step's own two\n * endpoints and stops — no `attributeStep`, no candidate scan.\n *\n * That is the difference between answering and not answering for the service a\n * feature is about to INTRODUCE. A flow drawn for work that has not happened yet\n * has hops nothing in the model backs — `usecase.step-unbacked` is validate's\n * finding about exactly that, and it is an ERROR precisely because the fleet map\n * has not caught up — and a relationship join reports such a flow as touching\n * nobody. The endpoints are still there; the arrow is what is missing. An\n * implementer reading a delta brief needs to know the flow exists most of all in\n * that state, because that is the state their change is supposed to end.\n *\n * ## Everything a caller sees, it sees per HOP\n *\n * A view is reported only when at least one of its hops touches the addressed\n * services, and only those hops travel. \"Checkout touches you\" is not actionable;\n * \"Checkout touches you at step 4\" is where the reader goes to look.\n */\nimport { compareIds } from \"../repo/entries.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { ParsedStep, ParsedView } from \"../c4/parsed/dynamic-views.js\";\nimport { readUseCases, type UseCaseScan } from \"./fleet.js\";\nimport { viewFile } from \"./place.js\";\n\n/** One hop of a flow that mentions a service the caller asked about. */\nexport interface TouchedStep {\n /** The view's own leaf numbering — what the author sees on the diagram. */\n ordinal: number;\n title?: string;\n /** The step's endpoints as the view declares them: element ids, already oriented. */\n source: string;\n target: string;\n /**\n * Which of the ADDRESSED services this hop mentions, sorted. Both, when a flow\n * calls a service and it answers within the same addressed set.\n */\n services: string[];\n}\n\n/** One flow that touches the addressed services, with only the hops that do. */\nexport interface TouchedUseCase {\n id: string;\n title?: string;\n /** Repo-relative document the view is written in — where the reader goes to change it. */\n file: string;\n /**\n * The `#cap-` tags the view carries, verbatim and unresolved.\n *\n * Verbatim because resolving them means reading\n * `architecture/capabilities.yaml`, and a slug is not injective — the join can\n * legitimately answer \"two capabilities\" (core/capabilities/usecase-join.ts).\n * A blast-radius list is not the place to make that call: `loam validate --all`\n * already grades every tag with `usecase.capability-unresolved`, and a second\n * resolution here would be a second answer to the same question.\n */\n tags: string[];\n steps: TouchedStep[];\n}\n\n/**\n * The flows over a set of services, and whether loam could look at all.\n *\n * `unreadable` is a HOLE, in the sense `core/pack/pack.ts` uses the word: `flows`\n * is empty because `architecture/` did not parse, not because the fleet draws no\n * use cases. A consumer that cannot tell those apart reads \"no business flow\n * depends on this\" off a directory nobody could open, which is the vacuously\n * green answer every surface in loam is built to refuse.\n */\nexport interface UseCaseBlastRadius {\n unreadable: boolean;\n /** LikeC4's first message, so a reader knows which document to open. */\n error?: string;\n flows: TouchedUseCase[];\n}\n\nexport interface BlastRadiusRequest {\n docsDir: DocsDir;\n /**\n * The fleet the element→service resolver is built with. Wider than `services`\n * on purpose: it should be every service the resolver could legitimately land\n * on, so a hop into a modelled container resolves to its owner rather than to\n * the container's own title.\n */\n known: ReadonlySet<string>;\n /** The services being asked about. */\n services: ReadonlySet<string>;\n}\n\n/** The addressed services one hop mentions, source and target both, sorted and de-duplicated. */\nfunction touched(step: ParsedStep, scan: Extract<UseCaseScan, { kind: \"read\" }>, services: ReadonlySet<string>): string[] {\n const hit = [step.source, step.target].map((id) => scan.resolve(id)).filter((id) => services.has(id));\n return [...new Set(hit)].sort(compareIds);\n}\n\n/** One view's touching hops, in the author's own step order — the order the diagram reads in. */\nfunction touchedView(view: ParsedView, scan: Extract<UseCaseScan, { kind: \"read\" }>, services: ReadonlySet<string>): TouchedUseCase | null {\n const steps: TouchedStep[] = [];\n for (const step of view.steps) {\n const hit = touched(step, scan, services);\n if (hit.length === 0) continue;\n steps.push({\n ordinal: step.ordinal,\n ...(step.title === undefined ? {} : { title: step.title }),\n source: step.source,\n target: step.target,\n services: hit,\n });\n }\n if (steps.length === 0) return null;\n return {\n id: view.id,\n ...(view.title === undefined ? {} : { title: view.title }),\n file: viewFile(view),\n tags: [...view.tags].sort(compareIds),\n steps,\n };\n}\n\n/**\n * The flows touching a set of services, over a scan somebody already has.\n *\n * Separate from `useCaseBlastRadius` below because `core/pack/joins.ts` reads\n * the scan ONCE and asks two different questions of it — this one and the\n * capability claim join — and a second read there would be a second LikeC4\n * project load inside a single `loam context`.\n *\n * An `unreadable` scan answers with nothing. Every caller pairs this with the\n * scan's own health, because an empty list means \"no flow mentions these\n * services\" and \"nobody could look\" only when read together.\n */\nexport function flowsTouching(scan: UseCaseScan, services: ReadonlySet<string>): TouchedUseCase[] {\n if (scan.kind !== \"read\") return [];\n return (\n scan.views\n .map((view) => touchedView(view, scan, services))\n .filter((flow): flow is TouchedUseCase => flow !== null)\n // Sorted by (file, view id) rather than reported in LikeC4's own record\n // order: nothing in loam has measured that the parse preserves declaration\n // order, so a payload ordered by it would reorder under a dependency bump.\n .sort((a, b) => compareIds(a.file, b.file) || compareIds(a.id, b.id))\n );\n}\n\n/**\n * The flows touching a set of services, read and joined in one call.\n *\n * The load underneath is `readUseCases`, which carries its own cheap gate: a\n * fleet whose `architecture/` documents never mention the reserved tag prefix\n * answers `{ flows: [] }` without starting LikeC4 at all. That gate is what\n * makes this callable from `loam delta`, which sits in `/loam-implement`'s inner\n * loop and would otherwise pay a Langium workspace spin-up on every iteration of\n * somebody's afternoon.\n */\nexport async function useCaseBlastRadius(req: BlastRadiusRequest): Promise<UseCaseBlastRadius> {\n const scan = await readUseCases({ docsDir: req.docsDir, known: req.known });\n if (scan.kind !== \"read\") {\n return { unreadable: true, ...(scan.errors[0] === undefined ? {} : { error: scan.errors[0] }), flows: [] };\n }\n return { unreadable: false, flows: flowsTouching(scan, req.services) };\n}\n"]}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The answers, and the one thing loam checks about them.
|
|
3
|
+
*
|
|
4
|
+
* loam does not judge an answer. It cannot: it never read the code. What it
|
|
5
|
+
* guarantees is that every question was asked and that every answer sits next
|
|
6
|
+
* to the question it answers — so an answer set that does not correspond to the
|
|
7
|
+
* current checklist is refused rather than merged. An unchecked claim must
|
|
8
|
+
* never be able to masquerade as checked, and that refusal is `checkAnswers`.
|
|
9
|
+
*
|
|
10
|
+
* A scenario claim is answered mechanically wherever it can be: `--results`
|
|
11
|
+
* reads a cucumber JSON report and matches it by the digest tag `loam gherkin`
|
|
12
|
+
* stamped, and only a green run confirms one (`core/results.ts`). Where it
|
|
13
|
+
* cannot — a legacy service whose suite is months away, which is the fleet loam
|
|
14
|
+
* exists for — `--record` may still take an agent's word for it, and
|
|
15
|
+
* `./record.ts` marks that answer attested rather than run.
|
|
16
|
+
*/
|
|
17
|
+
import { isRecord } from "../kernel/records.js";
|
|
18
|
+
import {} from "./checklist.js";
|
|
19
|
+
import {} from "./pins/pin.js";
|
|
20
|
+
/**
|
|
21
|
+
* Two verdicts, deliberately. Anything an agent cannot show evidence for is
|
|
22
|
+
* unconfirmed — a third value for "not applicable" would absorb every claim
|
|
23
|
+
* nobody wanted to answer.
|
|
24
|
+
*/
|
|
25
|
+
export const VERDICTS = ["confirmed", "unconfirmed"];
|
|
26
|
+
/**
|
|
27
|
+
* Who answered a claim: the `runner` (a cucumber report's digest-tagged
|
|
28
|
+
* scenarios, matched mechanically by `--results`), the `external-runner` (an
|
|
29
|
+
* API contract-test report's operationId-matched entries, matched mechanically
|
|
30
|
+
* by `--contract-results` — external because loam did not generate the suite
|
|
31
|
+
* the way it generates the gherkin, so the join key is the operationId the
|
|
32
|
+
* claim asserts rather than a digest loam stamped), or an `agent` (somebody's
|
|
33
|
+
* word about the code, taken back by `--record`). On the record so a reviewer
|
|
34
|
+
* can tell a green run from an assertion. `external-runner` never answers a
|
|
35
|
+
* `scenario.tested` claim, which is what keeps the attested boundary —
|
|
36
|
+
* `attestedClaims` in `./record.ts` — exactly where it was.
|
|
37
|
+
*/
|
|
38
|
+
export const ANSWERED_BY = ["runner", "external-runner", "agent"];
|
|
39
|
+
/**
|
|
40
|
+
* Accept an answer set only if it answers THIS checklist: every claim once, no
|
|
41
|
+
* claim twice, nothing extra, and no "confirmed" that shows nothing.
|
|
42
|
+
*
|
|
43
|
+
* The refusals are the point of the command. If a stale or partial answer file
|
|
44
|
+
* could be recorded, `verification.yaml` would say a claim was checked when
|
|
45
|
+
* nobody ever asked it — and a record that can lie is worse than no record,
|
|
46
|
+
* because it looks like evidence.
|
|
47
|
+
*
|
|
48
|
+
* `runnerOwned` is the composition rule under `--results` and
|
|
49
|
+
* `--contract-results`: ids a mechanical report answers — scenario claims under
|
|
50
|
+
* the first flag, api.exposes claims under the second — which an answers file
|
|
51
|
+
* must therefore not touch. An entry naming one refuses with its own diagnosis
|
|
52
|
+
* — the id IS on the feature's checklist, so calling it unknown would send the
|
|
53
|
+
* caller hunting a staleness that is not there. `--diff-answers`
|
|
54
|
+
* (commands/verify/cross/diff.ts) calls this with it UNSET on purpose: under a
|
|
55
|
+
* diff both agents answer everything in scope, scenario claims included,
|
|
56
|
+
* because the diff consults no report — so making scenario claims runner-only
|
|
57
|
+
* even without `--results` would break that lens.
|
|
58
|
+
*
|
|
59
|
+
* `context` is what turns the last line from a circle into an instruction. In a
|
|
60
|
+
* fleet the commonest way to "miss" a claim is that it is not yours: the
|
|
61
|
+
* checklist spans ten repositories and this one can only answer its own. Told
|
|
62
|
+
* only to "answer the claims it lists", an agent in the payment-service repo
|
|
63
|
+
* re-runs verify, sees the same checkout-web claims it cannot speak for, and
|
|
64
|
+
* either loops or invents evidence. So when every unanswered claim belongs to
|
|
65
|
+
* another service, say THAT, and name the command that records them.
|
|
66
|
+
*/
|
|
67
|
+
export function checkAnswers(claims, raw, runnerOwned, context) {
|
|
68
|
+
const refuse = (code, message) => ({ ok: false, code, message });
|
|
69
|
+
const list = Array.isArray(raw)
|
|
70
|
+
? raw
|
|
71
|
+
: isRecord(raw) && Array.isArray(raw["answers"])
|
|
72
|
+
? raw["answers"]
|
|
73
|
+
: null;
|
|
74
|
+
if (list === null) {
|
|
75
|
+
return refuse("answers-unreadable", "Expected a JSON array of answers, or an object with an `answers` array: " +
|
|
76
|
+
`[{ "id": "...", "verdict": "confirmed", "evidence": ["file:line"] }]`);
|
|
77
|
+
}
|
|
78
|
+
const answers = [];
|
|
79
|
+
for (const [i, entry] of list.entries()) {
|
|
80
|
+
const where = `answer ${i + 1}`;
|
|
81
|
+
if (!isRecord(entry))
|
|
82
|
+
return refuse("answers-unreadable", `${where} is not an object`);
|
|
83
|
+
const id = entry["id"];
|
|
84
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
85
|
+
return refuse("answers-unreadable", `${where} has no claim id`);
|
|
86
|
+
}
|
|
87
|
+
const verdict = entry["verdict"];
|
|
88
|
+
if (typeof verdict !== "string" || !VERDICTS.includes(verdict)) {
|
|
89
|
+
return refuse("answers-unreadable", `${where} ('${id}') has verdict ${JSON.stringify(verdict)} — expected one of ${VERDICTS.join(", ")}`);
|
|
90
|
+
}
|
|
91
|
+
const note = typeof entry["note"] === "string" ? entry["note"].trim() : "";
|
|
92
|
+
answers.push({
|
|
93
|
+
id,
|
|
94
|
+
verdict: verdict,
|
|
95
|
+
evidence: stringList(entry["evidence"]),
|
|
96
|
+
...(note.length > 0 ? { note } : {}),
|
|
97
|
+
answered_by: "agent",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const byId = new Map();
|
|
101
|
+
const twice = [];
|
|
102
|
+
for (const a of answers) {
|
|
103
|
+
if (byId.has(a.id))
|
|
104
|
+
twice.push(a.id);
|
|
105
|
+
else
|
|
106
|
+
byId.set(a.id, a);
|
|
107
|
+
}
|
|
108
|
+
const known = new Set(claims.map((c) => c.id));
|
|
109
|
+
const strayIds = [...new Set(answers.map((a) => a.id).filter((id) => !known.has(id)))];
|
|
110
|
+
const runnerHit = strayIds.filter((id) => runnerOwned?.has(id) === true);
|
|
111
|
+
const unknown = strayIds.filter((id) => runnerOwned?.has(id) !== true);
|
|
112
|
+
const missingClaims = claims.filter((c) => !byId.has(c.id));
|
|
113
|
+
const missing = missingClaims.map((c) => c.id);
|
|
114
|
+
if (runnerHit.length + unknown.length + missing.length + twice.length > 0) {
|
|
115
|
+
const parts = [
|
|
116
|
+
runnerHit.length > 0
|
|
117
|
+
? `${runnerHit.length} answer(s) name claim(s) a report owns under --results or --contract-results: ${runnerHit.join(", ")} — the report answers those; take them out of the answers file`
|
|
118
|
+
: "",
|
|
119
|
+
unknown.length > 0 ? `${unknown.length} answer(s) name a claim that is not on the checklist: ${unknown.join(", ")}` : "",
|
|
120
|
+
missing.length > 0 ? `${missing.length} claim(s) have no answer: ${missing.join(", ")}` : "",
|
|
121
|
+
twice.length > 0 ? `${twice.length} claim(s) answered more than once: ${[...new Set(twice)].join(", ")}` : "",
|
|
122
|
+
].filter((s) => s.length > 0);
|
|
123
|
+
return refuse("answers-mismatch", `These answers do not match the checklist — ${parts.join("; ")}. ` +
|
|
124
|
+
missingAdvice(missingClaims, claims.filter((c) => byId.has(c.id)), context));
|
|
125
|
+
}
|
|
126
|
+
const unevidenced = claims
|
|
127
|
+
.map((c) => byId.get(c.id))
|
|
128
|
+
.filter((a) => a.verdict === "confirmed" && a.evidence.length === 0)
|
|
129
|
+
.map((a) => a.id);
|
|
130
|
+
if (unevidenced.length > 0) {
|
|
131
|
+
return refuse("answers-unevidenced", `${unevidenced.length} claim(s) are confirmed with no evidence: ${unevidenced.join(", ")}. A confirmation with nothing behind it is an assertion — give a file:line, or answer 'unconfirmed'.`);
|
|
132
|
+
}
|
|
133
|
+
// Checklist order, not the order they were written in: the record reads in the
|
|
134
|
+
// order the questions were asked, whatever the agent did.
|
|
135
|
+
return { ok: true, answers: claims.map((c) => byId.get(c.id)) };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The last sentence of an `answers-mismatch`. "Re-run and answer the claims it
|
|
139
|
+
* lists" is sound advice only when the caller CAN answer them. In a fleet the
|
|
140
|
+
* checklist is federated — a feature's claims are filed under the service whose
|
|
141
|
+
* code answers them — so the honest diagnosis for a repo that answered
|
|
142
|
+
* everything it owns is "the rest is not yours", together with the form that
|
|
143
|
+
* records the rest. Anything else sends the caller round the same loop.
|
|
144
|
+
*/
|
|
145
|
+
function missingAdvice(missing, answered, context) {
|
|
146
|
+
const generic = "Re-run `loam verify` and answer the claims it lists.";
|
|
147
|
+
if (context === undefined || missing.length === 0 || answered.length === 0)
|
|
148
|
+
return generic;
|
|
149
|
+
// The signature of "I answered mine and the rest is not mine": the services
|
|
150
|
+
// that own the unanswered claims and the services that own the answered ones
|
|
151
|
+
// do not overlap. A single-service checklist with one claim forgotten fails
|
|
152
|
+
// this test and keeps the generic advice, which is the right advice there —
|
|
153
|
+
// that claim really is the caller's to answer.
|
|
154
|
+
const owners = [...new Set(missing.map((c) => c.subject))].sort();
|
|
155
|
+
const covered = new Set(answered.map((c) => c.subject));
|
|
156
|
+
if (owners.some((s) => covered.has(s)))
|
|
157
|
+
return generic;
|
|
158
|
+
const form = `\`loam verify ${context.feature} --record answers.json --service <svc>\``;
|
|
159
|
+
const mine = context.service;
|
|
160
|
+
return mine === undefined
|
|
161
|
+
? `Those claims are owned by ${owners.join(", ")} — each service attests its own from its own repository (${form}); ` +
|
|
162
|
+
"this all-at-once form has to answer the whole checklist in one file."
|
|
163
|
+
: `Every unanswered claim belongs to a different service (${owners.join(", ")}) and this repository can only attest '${mine}' — ` +
|
|
164
|
+
`run ${form} in each of those repositories instead.`;
|
|
165
|
+
}
|
|
166
|
+
function stringList(v) {
|
|
167
|
+
if (typeof v === "string")
|
|
168
|
+
return v.trim().length > 0 ? [v.trim()] : [];
|
|
169
|
+
if (!Array.isArray(v))
|
|
170
|
+
return [];
|
|
171
|
+
return v.map((x) => String(x).trim()).filter((s) => s.length > 0);
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=answers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answers.js","sourceRoot":"","sources":["../../../src/core/verify/answers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The answers, and the one thing loam checks about them.\n *\n * loam does not judge an answer. It cannot: it never read the code. What it\n * guarantees is that every question was asked and that every answer sits next\n * to the question it answers — so an answer set that does not correspond to the\n * current checklist is refused rather than merged. An unchecked claim must\n * never be able to masquerade as checked, and that refusal is `checkAnswers`.\n *\n * A scenario claim is answered mechanically wherever it can be: `--results`\n * reads a cucumber JSON report and matches it by the digest tag `loam gherkin`\n * stamped, and only a green run confirms one (`core/results.ts`). Where it\n * cannot — a legacy service whose suite is months away, which is the fleet loam\n * exists for — `--record` may still take an agent's word for it, and\n * `./record.ts` marks that answer attested rather than run.\n */\nimport { isRecord } from \"../kernel/records.js\";\nimport { type Claim } from \"./checklist.js\";\nimport { type EvidencePin } from \"./pins/pin.js\";\n\n/**\n * Two verdicts, deliberately. Anything an agent cannot show evidence for is\n * unconfirmed — a third value for \"not applicable\" would absorb every claim\n * nobody wanted to answer.\n */\nexport const VERDICTS = [\"confirmed\", \"unconfirmed\"] as const;\nexport type Verdict = (typeof VERDICTS)[number];\n\n/**\n * Who answered a claim: the `runner` (a cucumber report's digest-tagged\n * scenarios, matched mechanically by `--results`), the `external-runner` (an\n * API contract-test report's operationId-matched entries, matched mechanically\n * by `--contract-results` — external because loam did not generate the suite\n * the way it generates the gherkin, so the join key is the operationId the\n * claim asserts rather than a digest loam stamped), or an `agent` (somebody's\n * word about the code, taken back by `--record`). On the record so a reviewer\n * can tell a green run from an assertion. `external-runner` never answers a\n * `scenario.tested` claim, which is what keeps the attested boundary —\n * `attestedClaims` in `./record.ts` — exactly where it was.\n */\nexport const ANSWERED_BY = [\"runner\", \"external-runner\", \"agent\"] as const;\nexport type AnsweredBy = (typeof ANSWERED_BY)[number];\n\nexport interface Answer {\n id: string;\n verdict: Verdict;\n /** Where it can be seen — `file:line`, or a report scenario for the runner. Required for `confirmed`. */\n evidence: string[];\n note?: string;\n answered_by: AnsweredBy;\n /**\n * loam's own stamp, never caller input: `validateServiceEvidence` computes\n * these from the attested commit's blobs and the record path attaches them.\n * `checkAnswers` below deliberately never reads the field, so an\n * `evidence_pins` key in an answers FILE is ignored, not honored — an agent\n * must not be able to hand loam a pin loam did not derive.\n */\n evidence_pins?: EvidencePin[];\n}\n\n/** Why an answer set was refused. Each names a different way it fails to answer. */\nexport type AnswerRefusal = \"answers-unreadable\" | \"answers-mismatch\" | \"answers-unevidenced\";\n\nexport type AnswerCheck =\n /** The answers, re-ordered to the checklist, one per claim. */\n | { ok: true; answers: Answer[] }\n | { ok: false; code: AnswerRefusal; message: string };\n\n/**\n * Accept an answer set only if it answers THIS checklist: every claim once, no\n * claim twice, nothing extra, and no \"confirmed\" that shows nothing.\n *\n * The refusals are the point of the command. If a stale or partial answer file\n * could be recorded, `verification.yaml` would say a claim was checked when\n * nobody ever asked it — and a record that can lie is worse than no record,\n * because it looks like evidence.\n *\n * `runnerOwned` is the composition rule under `--results` and\n * `--contract-results`: ids a mechanical report answers — scenario claims under\n * the first flag, api.exposes claims under the second — which an answers file\n * must therefore not touch. An entry naming one refuses with its own diagnosis\n * — the id IS on the feature's checklist, so calling it unknown would send the\n * caller hunting a staleness that is not there. `--diff-answers`\n * (commands/verify/cross/diff.ts) calls this with it UNSET on purpose: under a\n * diff both agents answer everything in scope, scenario claims included,\n * because the diff consults no report — so making scenario claims runner-only\n * even without `--results` would break that lens.\n *\n * `context` is what turns the last line from a circle into an instruction. In a\n * fleet the commonest way to \"miss\" a claim is that it is not yours: the\n * checklist spans ten repositories and this one can only answer its own. Told\n * only to \"answer the claims it lists\", an agent in the payment-service repo\n * re-runs verify, sees the same checkout-web claims it cannot speak for, and\n * either loops or invents evidence. So when every unanswered claim belongs to\n * another service, say THAT, and name the command that records them.\n */\nexport function checkAnswers(\n claims: Claim[],\n raw: unknown,\n runnerOwned?: ReadonlySet<string>,\n context?: { feature: string; service?: string },\n): AnswerCheck {\n const refuse = (code: AnswerRefusal, message: string): AnswerCheck => ({ ok: false, code, message });\n\n const list = Array.isArray(raw)\n ? raw\n : isRecord(raw) && Array.isArray(raw[\"answers\"])\n ? (raw[\"answers\"] as unknown[])\n : null;\n if (list === null) {\n return refuse(\n \"answers-unreadable\",\n \"Expected a JSON array of answers, or an object with an `answers` array: \" +\n `[{ \"id\": \"...\", \"verdict\": \"confirmed\", \"evidence\": [\"file:line\"] }]`,\n );\n }\n\n const answers: Answer[] = [];\n for (const [i, entry] of list.entries()) {\n const where = `answer ${i + 1}`;\n if (!isRecord(entry)) return refuse(\"answers-unreadable\", `${where} is not an object`);\n const id = entry[\"id\"];\n if (typeof id !== \"string\" || id.length === 0) {\n return refuse(\"answers-unreadable\", `${where} has no claim id`);\n }\n const verdict = entry[\"verdict\"];\n if (typeof verdict !== \"string\" || !(VERDICTS as readonly string[]).includes(verdict)) {\n return refuse(\n \"answers-unreadable\",\n `${where} ('${id}') has verdict ${JSON.stringify(verdict)} — expected one of ${VERDICTS.join(\", \")}`,\n );\n }\n const note = typeof entry[\"note\"] === \"string\" ? entry[\"note\"].trim() : \"\";\n answers.push({\n id,\n verdict: verdict as Verdict,\n evidence: stringList(entry[\"evidence\"]),\n ...(note.length > 0 ? { note } : {}),\n answered_by: \"agent\",\n });\n }\n\n const byId = new Map<string, Answer>();\n const twice: string[] = [];\n for (const a of answers) {\n if (byId.has(a.id)) twice.push(a.id);\n else byId.set(a.id, a);\n }\n const known = new Set(claims.map((c) => c.id));\n const strayIds = [...new Set(answers.map((a) => a.id).filter((id) => !known.has(id)))];\n const runnerHit = strayIds.filter((id) => runnerOwned?.has(id) === true);\n const unknown = strayIds.filter((id) => runnerOwned?.has(id) !== true);\n const missingClaims = claims.filter((c) => !byId.has(c.id));\n const missing = missingClaims.map((c) => c.id);\n if (runnerHit.length + unknown.length + missing.length + twice.length > 0) {\n const parts = [\n runnerHit.length > 0\n ? `${runnerHit.length} answer(s) name claim(s) a report owns under --results or --contract-results: ${runnerHit.join(\", \")} — the report answers those; take them out of the answers file`\n : \"\",\n unknown.length > 0 ? `${unknown.length} answer(s) name a claim that is not on the checklist: ${unknown.join(\", \")}` : \"\",\n missing.length > 0 ? `${missing.length} claim(s) have no answer: ${missing.join(\", \")}` : \"\",\n twice.length > 0 ? `${twice.length} claim(s) answered more than once: ${[...new Set(twice)].join(\", \")}` : \"\",\n ].filter((s) => s.length > 0);\n return refuse(\n \"answers-mismatch\",\n `These answers do not match the checklist — ${parts.join(\"; \")}. ` +\n missingAdvice(missingClaims, claims.filter((c) => byId.has(c.id)), context),\n );\n }\n\n const unevidenced = claims\n .map((c) => byId.get(c.id)!)\n .filter((a) => a.verdict === \"confirmed\" && a.evidence.length === 0)\n .map((a) => a.id);\n if (unevidenced.length > 0) {\n return refuse(\n \"answers-unevidenced\",\n `${unevidenced.length} claim(s) are confirmed with no evidence: ${unevidenced.join(\", \")}. A confirmation with nothing behind it is an assertion — give a file:line, or answer 'unconfirmed'.`,\n );\n }\n\n // Checklist order, not the order they were written in: the record reads in the\n // order the questions were asked, whatever the agent did.\n return { ok: true, answers: claims.map((c) => byId.get(c.id)!) };\n}\n\n/**\n * The last sentence of an `answers-mismatch`. \"Re-run and answer the claims it\n * lists\" is sound advice only when the caller CAN answer them. In a fleet the\n * checklist is federated — a feature's claims are filed under the service whose\n * code answers them — so the honest diagnosis for a repo that answered\n * everything it owns is \"the rest is not yours\", together with the form that\n * records the rest. Anything else sends the caller round the same loop.\n */\nfunction missingAdvice(\n missing: Claim[],\n answered: Claim[],\n context?: { feature: string; service?: string },\n): string {\n const generic = \"Re-run `loam verify` and answer the claims it lists.\";\n if (context === undefined || missing.length === 0 || answered.length === 0) return generic;\n // The signature of \"I answered mine and the rest is not mine\": the services\n // that own the unanswered claims and the services that own the answered ones\n // do not overlap. A single-service checklist with one claim forgotten fails\n // this test and keeps the generic advice, which is the right advice there —\n // that claim really is the caller's to answer.\n const owners = [...new Set(missing.map((c) => c.subject))].sort();\n const covered = new Set(answered.map((c) => c.subject));\n if (owners.some((s) => covered.has(s))) return generic;\n const form = `\\`loam verify ${context.feature} --record answers.json --service <svc>\\``;\n const mine = context.service;\n return mine === undefined\n ? `Those claims are owned by ${owners.join(\", \")} — each service attests its own from its own repository (${form}); ` +\n \"this all-at-once form has to answer the whole checklist in one file.\"\n : `Every unanswered claim belongs to a different service (${owners.join(\", \")}) and this repository can only attest '${mine}' — ` +\n `run ${form} in each of those repositories instead.`;\n}\n\nfunction stringList(v: unknown): string[] {\n if (typeof v === \"string\") return v.trim().length > 0 ? [v.trim()] : [];\n if (!Array.isArray(v)) return [];\n return v.map((x) => String(x).trim()).filter((s) => s.length > 0);\n}\n\n"]}
|