@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3448 -0
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +47 -23
- package/QUICKSTART.md +189 -12
- package/README.md +274 -65
- package/ROADMAP.md +572 -31
- package/SCHEMA.md +1709 -155
- package/WORKFLOW.md +298 -72
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/adopt/render.js +50 -9
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/archive.js +16 -19
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +77 -43
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +21 -19
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +167 -30
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +14 -19
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +137 -56
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +3 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +134 -30
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +85 -18
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +29 -7
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +102 -14
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +17 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/doctor.js +60 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +89 -13
- package/dist/commands/explain/explain.js.map +1 -1
- package/dist/commands/explore.js +16 -10
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +5 -7
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +44 -29
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +52 -9
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +37 -34
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +158 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +13 -11
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +9 -5
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +35 -6
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +41 -33
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/show/show.js +17 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +2 -0
- package/dist/commands/status/json.js.map +1 -1
- package/dist/commands/status/print.js +89 -37
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +103 -23
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +106 -46
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +289 -74
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +147 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +200 -21
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +42 -26
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +83 -33
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +78 -27
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +34 -126
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +11 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +26 -20
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +144 -126
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +44 -14
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +10 -2
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +50 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +40 -47
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +36 -160
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +21 -107
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +36 -14
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +121 -61
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +88 -28
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +38 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +85 -60
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +55 -52
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +81 -106
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +21 -9
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +24 -8
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +80 -8
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +88 -10
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +49 -22
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +39 -6
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/protocol.js +1 -1
- package/dist/core/agent/protocol.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/support/report.js +46 -5
- package/dist/core/agent/support/report.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +158 -11
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +78 -39
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +88 -27
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +128 -13
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +96 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +106 -4
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +6 -3
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +68 -27
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +32 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +33 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +12 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +96 -36
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +209 -130
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +109 -31
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +14 -3
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/arch.js +90 -182
- package/dist/core/c4/arch.js.map +1 -1
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +169 -14
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +123 -7
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +43 -12
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +79 -43
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +78 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/seed/template.js +65 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +297 -10
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +222 -141
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +79 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js +20 -0
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +20 -20
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +37 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +182 -114
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +160 -56
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +13 -19
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/delta/uncovered.js +2 -2
- package/dist/core/capabilities/delta/uncovered.js.map +1 -1
- package/dist/core/capabilities/findings.js +18 -22
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +103 -22
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +147 -16
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +28 -87
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.js.map +1 -0
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +51 -6
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/dependencies/fanin.js +2 -2
- package/dist/core/dependencies/fanin.js.map +1 -1
- package/dist/core/deployment/delta.js +191 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +49 -21
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/docs.js +31 -22
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +91 -11
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/document/apply.js +158 -11
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +26 -21
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/lookup.js +14 -1
- package/dist/core/explain/lookup.js.map +1 -1
- package/dist/core/explain/refusals.js +5 -5
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +68 -109
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +40 -10
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +81 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +11 -4
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +69 -28
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/delta.js +10 -7
- package/dist/core/glossary/delta.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/kernel/path-safety.js +38 -0
- package/dist/core/kernel/path-safety.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +51 -37
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/links/findings.js.map +1 -1
- package/dist/core/links/parse.js +38 -9
- package/dist/core/links/parse.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +40 -10
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +301 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +28 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +17 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +43 -4
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +50 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/authored/paths.js +18 -0
- package/dist/core/repo/authored/paths.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +92 -6
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +74 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +53 -32
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +121 -13
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.js.map +1 -0
- package/dist/core/staging/commit.js +26 -15
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +84 -6
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +24 -14
- package/dist/core/staging/txn/forward.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/contracts.js +39 -15
- package/dist/core/status/contracts.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +33 -8
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +24 -17
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/feature/next.js +20 -7
- package/dist/core/status/feature/next.js.map +1 -1
- package/dist/core/status/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +43 -14
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +153 -58
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/scan.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +199 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/claims.js +31 -7
- package/dist/core/usecases/delta/claims.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +32 -191
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +195 -36
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +26 -14
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/place.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +70 -12
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +4 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +75 -4
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/dist/core/vocabulary/report.js +16 -1
- package/dist/core/vocabulary/report.js.map +1 -1
- package/examples/README.md +54 -11
- package/examples/docs/architecture/landscape.likec4 +62 -6
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +7 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -37
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -31
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -220
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -165
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -247
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/repo/tree/views.js +0 -172
- package/dist/core/repo/tree/views.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirement-tag.js","sourceRoot":"","sources":["../../../../src/core/usecases/grade/requirement-tag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EACL,cAAc,EACd,OAAO,EACP,sBAAsB,GAGvB,MAAM,oCAAoC,CAAC;AAK5C,0FAA0F;AAC1F,SAAS,QAAQ,CAAC,EAAU;IAC1B,OAAO,GAAG,EAAE,MAAM,cAAc,GAAG,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,SAAS,MAAM,CAAC,KAAsD;IACpE,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,UAAU;YACb,OAAO,KAAK,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC;gBACpC,CAAC,CAAC,oGAAoG;oBAClG,4FAA4F;oBAC5F,qEAAqE;gBACzE,CAAC,CAAC,qBAAqB,KAAK,CAAC,YAAY,CAAC,MAAM,kBAAkB,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;oBAChG,oGAAoG;oBACpG,iGAAiG;oBACjG,mGAAmG,CAAC;QAC5G,KAAK,cAAc;YACjB,OAAO,CACL,eAAe,KAAK,CAAC,UAAU,yCAAyC,KAAK,CAAC,UAAU,YAAY;gBACpG,kGAAkG;gBAClG,4DAA4D,CAC7D,CAAC;QACJ,KAAK,OAAO;YACV,OAAO,CACL,gBAAgB,KAAK,CAAC,UAAU,qDAAqD;gBACrF,qFAAqF,CACtF,CAAC;QACJ,KAAK,MAAM;YACT,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;gBAC3B,CAAC,CAAC,gBAAgB,KAAK,CAAC,UAAU,mDAAmD,KAAK,CAAC,IAAI,KAAK;oBAChG,iBAAiB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;gBAC5D,CAAC,CAAC,gBAAgB,KAAK,CAAC,UAAU,mDAAmD,KAAK,CAAC,IAAI,MAAM;oBACjG,qDAAqD,CAAC;QAC9D;YACE,OAAO,CACL,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,qBAAqB,KAAK,CAAC,UAAU,iBAAiB,KAAK,CAAC,IAAI,IAAI;gBACvF,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,gEAAgE;gBACxF,mGAAmG;gBACnG,8DAA8D,CAC/D,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAgB,EAChB,MAAkC,EAClC,cAAuE,EACvE,KAAgB;IAEhB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,EAAE,CAAC;QAC7E,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,gCAAgC;YACtC,yEAAyE;YACzE,sEAAsE;YACtE,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,GAAG,8BAA8B,MAAM,CAAC,KAAK,CAAC,EAAE;SAC7F,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * `usecase.requirement-unresolved` — the `#req-` tag that names no requirement\n * of the capability the view is about.\n *\n * The join lives in `core/capabilities/usecase-join.ts` and none of it is\n * repeated here: this module owns the grade and the sentences. Six arms, six\n * fixes, and they are deliberately the same vocabulary\n * `capability.realizes-unknown` uses for the OTHER carrier of this claim — an\n * author who has learned one set of messages has learned both.\n *\n * ERROR, for `usecase.capability-unresolved`'s reason one join deeper: the tag\n * is the architect's claim that a flow satisfies a named business promise, and a\n * claim that resolves to nothing reads exactly like one that does — in the view,\n * in review, and to anyone asking which flow keeps that promise.\n *\n * AN UNRESOLVED CLAIM SUPPRESSES NOTHING. `capability.requirement-unrealized`\n * counts only RESOLVED claims, so a typo'd `#req-` tag leaves the requirement\n * reported as realized by nobody — which is the truth, and is exactly what a\n * broken claim should not be able to hide. The two findings arriving together is\n * the intended reading: one says the tag is wrong, the other says the promise is\n * still unkept.\n */\nimport {\n REQ_TAG_PREFIX,\n tagSlug,\n resolveRequirementTags,\n type CapabilityClaim,\n type RequirementClaim,\n} from \"../../capabilities/usecase-join.js\";\nimport type { ParsedView } from \"../../c4/parsed/dynamic-views.js\";\nimport type { Finding } from \"../../vocabulary/report.js\";\nimport type { ViewPlace } from \"../place.js\";\n\n/** A `Requirement-ID` as an author must write it in a tag: `CHK.ONCE (#req-CHK-ONCE)`. */\nfunction idAndTag(id: string): string {\n return `${id} (#${REQ_TAG_PREFIX}${tagSlug(id)})`;\n}\n\n/**\n * The fix for one unresolved claim. The `unscoped` arm splits in two on the\n * count, because none and several are opposite mistakes: one author forgot to\n * say which capability, the other said two and left the id homeless.\n */\nfunction advice(claim: Exclude<RequirementClaim, { kind: \"resolved\" }>): string {\n switch (claim.kind) {\n case \"unscoped\":\n return claim.capabilities.length === 0\n ? \"the view carries no `#cap-` tag that resolves to a declared capability, and a `Requirement-ID` is \" +\n \"unique only inside its own document — so there is nothing for this id to be looked up in. \" +\n \"Add the `#cap-<slug>` tag naming the capability this flow is about.\"\n : `the view resolves ${claim.capabilities.length} capabilities (${claim.capabilities.join(\", \")}), ` +\n \"so nothing says which document the id belongs to. A flow realizing promises from two capabilities \" +\n \"needs one `#req-` tag per capability to be unambiguous, which the tag grammar cannot express — \" +\n \"split the flow, or drop the `#req-` tag and let `Realizes:` on the service requirements carry it.\";\n case \"undocumented\":\n return (\n `capability '${claim.capability}' is declared but has no capabilities/${claim.capability}/spec.md, ` +\n \"so it carries no requirements to satisfy. Write the document — a name alone can be claimed with \" +\n \"`#cap-`, but only a document has promises a flow can keep.\"\n );\n case \"empty\":\n return (\n `capabilities/${claim.capability}/spec.md exists and declares no requirements yet — ` +\n \"add the `## Requirements` section, each requirement carrying its `Requirement-ID:`.\"\n );\n case \"none\":\n return claim.close.length > 0\n ? `capabilities/${claim.capability}/spec.md declares no requirement flattening to '${claim.slug}'. ` +\n `Did you mean: ${claim.close.map(idAndTag).join(\", \")}?`\n : `capabilities/${claim.capability}/spec.md declares no requirement flattening to '${claim.slug}' — ` +\n \"check the `Requirement-ID:` lines in that document.\";\n default:\n return (\n `${claim.ids.length} requirements of '${claim.capability}' flatten to '${claim.slug}' ` +\n `(${claim.ids.join(\", \")}) — nothing in the tag can say which promise this flow keeps. ` +\n \"Rename one of them: a guessed requirement reports the wrong promise as kept and the right one as \" +\n \"unimplemented, and both answers look exactly like the truth.\"\n );\n }\n}\n\n/**\n * One finding per `#req-` tag that does not resolve — never one per view.\n *\n * Several `#req-` tags on one view are legal and normal: a checkout flow that\n * charges once AND honours the price keeps two promises, and there is no honest\n * way to make its author pick one. So a broken tag beside a working one must\n * neither swallow the break nor lose the promise the view genuinely does keep.\n *\n * `claims` is the view's OWN capability verdicts, already resolved by the\n * caller — passed rather than recomputed so the scope this grade uses and the\n * scope `capabilityTagFindings` graded are the same list, and an unresolved\n * `#cap-` tag cannot silently scope a requirement lookup.\n */\nexport function requirementTagFindings(\n view: ParsedView,\n claims: readonly CapabilityClaim[],\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined,\n place: ViewPlace,\n): Finding[] {\n const scope = claims.flatMap((claim) => (claim.kind === \"resolved\" ? [claim.id] : []));\n const findings: Finding[] = [];\n for (const claim of resolveRequirementTags(view.tags, scope, requirementsOf)) {\n if (claim.kind === \"resolved\") continue;\n findings.push({\n severity: \"error\",\n code: \"usecase.requirement-unresolved\",\n // The VIEW, not the tag — a subject is what a reader goes and opens, and\n // one broken use case counts once however many of its tags are wrong.\n subject: view.id,\n message: `${place(view)} is tagged #${claim.tag}, and it does not resolve: ${advice(claim)}`,\n });\n }\n return findings;\n}\n"]}
|
|
@@ -2,12 +2,21 @@
|
|
|
2
2
|
* The three grades one hop of a use case can earn: `usecase.step-unbacked`,
|
|
3
3
|
* `usecase.step-contested` and `usecase.step-unlinked`.
|
|
4
4
|
*
|
|
5
|
-
* The attribution itself is `core/c4/
|
|
5
|
+
* The attribution itself is `core/c4/resolve/steps.ts`'s `attributeStep` — which declared
|
|
6
6
|
* relationship backs a hop, oriented on `isBackward` and decided by the
|
|
7
7
|
* DISTINCT-op count — and none of that reasoning is repeated here. This module
|
|
8
|
-
* owns three things: which verdict earns which code, the
|
|
8
|
+
* owns three things: which verdict earns which code, the four guards that keep
|
|
9
9
|
* the warn honest, and the sentences.
|
|
10
10
|
*
|
|
11
|
+
* TWO SCOPES, ONE GRADER. The fleet target grades the `architecture/` project's
|
|
12
|
+
* views and the service target grades a service's own project — every `.likec4`
|
|
13
|
+
* beside `model.likec4`, read the way the renderer reads it
|
|
14
|
+
* (`commands/validate/service/usecases/`). Both call `stepFindings` below with
|
|
15
|
+
* their own `StepGrading`; what differs is only where a finding says the view
|
|
16
|
+
* is (`place`). A second copy of the verdict→code mapping per scope is the
|
|
17
|
+
* drift this package's banner forbids, which is why the intra-service case is
|
|
18
|
+
* a fourth guard here rather than a grader of its own.
|
|
19
|
+
*
|
|
11
20
|
* WHY `step-unbacked` IS THE CHECK THAT EARNS THE FEATURE. A hop between two
|
|
12
21
|
* elements that both exist, with no relationship between them anywhere in the
|
|
13
22
|
* model, is ZERO LikeC4 errors: a `dynamic view` is allowed to show a call the
|
|
@@ -24,13 +33,19 @@
|
|
|
24
33
|
* once for the disagreement, and once for whichever candidate happened to carry
|
|
25
34
|
* no operation at all.
|
|
26
35
|
*/
|
|
27
|
-
import { attributeStep } from "
|
|
28
|
-
import { ACTOR_KINDS } from "
|
|
29
|
-
import { stepPlace } from "
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
import { attributeStep, pickingTitle } from "../../c4/resolve/steps.js";
|
|
37
|
+
import { ACTOR_KINDS } from "../../vocabulary/maturity.js";
|
|
38
|
+
import { stepPlace } from "../place.js";
|
|
39
|
+
/**
|
|
40
|
+
* `…checkout.likec4 — dynamic view 'uc_checkout' step 2 'authorizes the payment'`.
|
|
41
|
+
*
|
|
42
|
+
* Spelled once per hop in `stepFindings` and handed to each sentence builder as
|
|
43
|
+
* a string, rather than recomputed from the grading inside each of them: three
|
|
44
|
+
* builders reaching for `grading.place` on their own would be three
|
|
45
|
+
* places for one scope to forget the injection.
|
|
46
|
+
*/
|
|
47
|
+
function placeOf(hop, grading) {
|
|
48
|
+
return `${grading.place(hop.view)} ${stepPlace(hop.step)}`;
|
|
34
49
|
}
|
|
35
50
|
/**
|
|
36
51
|
* A hop nothing in the model backs.
|
|
@@ -42,9 +57,9 @@ function placeOf(hop) {
|
|
|
42
57
|
* spelling they used would be a hint that restates the defect — and the sentence
|
|
43
58
|
* says which pair that spelling looks the hop up under, because `<-` attributes
|
|
44
59
|
* a reply to the CALL it answers rather than to the direction the message
|
|
45
|
-
* travels (`callPair` in core/c4/
|
|
60
|
+
* travels (`callPair` in core/c4/resolve/steps.ts holds the measurement).
|
|
46
61
|
*/
|
|
47
|
-
function unbackedFinding(hop, attribution) {
|
|
62
|
+
function unbackedFinding(hop, attribution, place) {
|
|
48
63
|
const other = hop.step.isBackward
|
|
49
64
|
? { spelling: `${hop.step.source} -> ${hop.step.target}`, when: "is not a return step" }
|
|
50
65
|
: { spelling: `${hop.step.target} <- ${hop.step.source}`, when: "is a return step" };
|
|
@@ -52,26 +67,57 @@ function unbackedFinding(hop, attribution) {
|
|
|
52
67
|
severity: "error",
|
|
53
68
|
code: "usecase.step-unbacked",
|
|
54
69
|
subject: hop.view.id,
|
|
55
|
-
message: `${
|
|
70
|
+
message: `${place}: nothing in the model declares ${attribution.from} -> ${attribution.to}, so this hop is ` +
|
|
56
71
|
"backed by no relationship — and LikeC4 reports no error for it, so the view still renders and the " +
|
|
57
72
|
"project still parses. Draw the edge in `model { }` (with `metadata { op '<operationId>' }` where it is " +
|
|
58
73
|
`a call), or, if this hop ${other.when}, write it as \`${other.spelling}\` — a reply is attributed to the ` +
|
|
59
74
|
`call it answers, so that spelling looks the hop up under ${attribution.to} -> ${attribution.from} instead.`,
|
|
60
75
|
};
|
|
61
76
|
}
|
|
62
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* `web -> orders.api "Calls createOrder" (op: createOrder)`, `(publishes: order.Placed)`,
|
|
79
|
+
* or `(no op)` where the edge declares no name at all.
|
|
80
|
+
*
|
|
81
|
+
* Every declared name is printed, the message ones included, because each is
|
|
82
|
+
* what the step's title can open with to pick this edge — a line that showed
|
|
83
|
+
* `no op` for a `publishes` edge would hide the one spelling that resolves it.
|
|
84
|
+
* An empty value is not printed: `op ''` parses, names nothing a title could
|
|
85
|
+
* open with, and `op: ` would read as a name somebody forgot to finish.
|
|
86
|
+
*/
|
|
63
87
|
function candidateLine(rel) {
|
|
64
|
-
const
|
|
65
|
-
|
|
88
|
+
const names = [
|
|
89
|
+
...(rel.op === undefined || rel.op === "" ? [] : [`op: ${rel.op}`]),
|
|
90
|
+
...(rel.publishes === undefined || rel.publishes === "" ? [] : [`publishes: ${rel.publishes}`]),
|
|
91
|
+
...(rel.consumes === undefined || rel.consumes === "" ? [] : [`consumes: ${rel.consumes}`]),
|
|
92
|
+
];
|
|
93
|
+
return `${rel.source} -> ${rel.target} "${rel.title ?? ""}" (${names.length === 0 ? "no op" : names.join(", ")})`;
|
|
66
94
|
}
|
|
67
95
|
/**
|
|
68
|
-
* A hop whose candidates disagree about the operation
|
|
96
|
+
* A hop whose candidates disagree about the operation, and whose title picks
|
|
97
|
+
* none of them.
|
|
69
98
|
*
|
|
70
99
|
* WARN, not error, because the model is complete — every candidate is a
|
|
71
100
|
* relationship somebody drew on purpose — and the honest answer is sometimes
|
|
72
101
|
* "two paths, one flow". What loam refuses to do is pick: a guessed operation is
|
|
73
102
|
* indistinguishable from a right one in every rollup built over the use case,
|
|
74
|
-
* and it stays wrong invisibly.
|
|
103
|
+
* and it stays wrong invisibly. The AUTHOR can pick, by opening the step's
|
|
104
|
+
* title with the operation id it means (`pickedByTitle` in
|
|
105
|
+
* core/c4/resolve/steps.ts), and the message leads with that repair because it
|
|
106
|
+
* is the one that works in every scope. The older "draw the hop between the
|
|
107
|
+
* elements that name exactly one" is still offered, last and qualified: it works
|
|
108
|
+
* where the graded project declares those elements — a service's own flow over
|
|
109
|
+
* its containers, a map that draws a service's containers itself — and is
|
|
110
|
+
* closed on a fleet flow whose services are single elements on the map, which
|
|
111
|
+
* is the hop that had no repair at all before the title could pick.
|
|
112
|
+
*
|
|
113
|
+
* The example spells the author's own title behind a candidate's declared name,
|
|
114
|
+
* so the line to write is visible rather than described, and `pickingTitle`
|
|
115
|
+
* chooses it by trying it — an example that would leave the hop contested is
|
|
116
|
+
* never offered. Which candidate it names is otherwise arbitrary, the first
|
|
117
|
+
* that works, and the sentence says "for example" because of it: `details` is
|
|
118
|
+
* where the author finds the one meant. When no single name settles the
|
|
119
|
+
* disagreement — every name shared by candidates that still disagree — there is
|
|
120
|
+
* no example to give, and the sentence goes without one.
|
|
75
121
|
*
|
|
76
122
|
* `details` carries every candidate rather than the distinct ops, because the
|
|
77
123
|
* repair is made on a RELATIONSHIP: the author has to find the edge that names
|
|
@@ -79,27 +125,32 @@ function candidateLine(rel) {
|
|
|
79
125
|
* that is. An absent `metadata { op }` prints as `no op` and is a candidate like
|
|
80
126
|
* any other — it is exactly as much of a disagreement as a second name.
|
|
81
127
|
*/
|
|
82
|
-
function contestedFinding(hop, attribution) {
|
|
128
|
+
function contestedFinding(hop, attribution, place) {
|
|
129
|
+
const example = pickingTitle(attribution.rels, hop.step.title);
|
|
83
130
|
return {
|
|
84
131
|
severity: "warn",
|
|
85
132
|
code: "usecase.step-contested",
|
|
86
133
|
subject: hop.view.id,
|
|
87
|
-
message: `${
|
|
134
|
+
message: `${place}: ${attribution.rels.length} relationships back ${attribution.from} -> ` +
|
|
88
135
|
`${attribution.to} and they name ${attribution.ops.length} different operations, so nothing can say ` +
|
|
89
136
|
"which one this hop exercises. loam names the candidates instead of picking one — a guessed operation " +
|
|
90
|
-
"reads exactly like a right one in every rollup built over this flow.
|
|
91
|
-
"
|
|
137
|
+
"reads exactly like a right one in every rollup built over this flow. Say which one the hop means by " +
|
|
138
|
+
"opening the step's title with that candidate's operation id (or the message it publishes or consumes), " +
|
|
139
|
+
`then punctuation before any further words${example === undefined ? "" : ` — for example '${example}'`}; ` +
|
|
140
|
+
"or give the candidates one operation; or, where this project declares the elements behind each " +
|
|
141
|
+
"candidate, draw the hop between them.",
|
|
92
142
|
details: attribution.rels.map(candidateLine),
|
|
93
143
|
};
|
|
94
144
|
}
|
|
95
145
|
/**
|
|
96
146
|
* The service this hop calls, when the hop is a `step-unlinked` candidate at
|
|
97
|
-
* all — `null` when any of the
|
|
147
|
+
* all — `null` when any of the four guards refuses. ALL FOUR are mechanical,
|
|
98
148
|
* and each closes a whole class of warnings that would otherwise be simply
|
|
99
149
|
* wrong. The count is stated because a reader auditing this function is meant to
|
|
100
|
-
* find
|
|
101
|
-
* them is surplus: the one a miscount invites deleting is the
|
|
102
|
-
* it puts the warning back on the first hop of almost every flow
|
|
150
|
+
* find four bullets and four `return null`s below and conclude that none of
|
|
151
|
+
* them is surplus: the one a miscount invites deleting is the actor guard, and
|
|
152
|
+
* deleting it puts the warning back on the first hop of almost every flow;
|
|
153
|
+
* deleting the last puts it on EVERY hop of every flow drawn inside a service.
|
|
103
154
|
*
|
|
104
155
|
* - A `publishes` or `consumes` on ANY candidate means the hop does reach a
|
|
105
156
|
* declared API — an AsyncAPI one. The event spine already grades it
|
|
@@ -119,16 +170,31 @@ function contestedFinding(hop, attribution) {
|
|
|
119
170
|
* reads the TARGET — and the target here is one of ours, so without this the
|
|
120
171
|
* warning fired on the FIRST HOP OF ALMOST EVERY FLOW, which is where a
|
|
121
172
|
* sequence diagram puts its actor. Measured on the published example fleet
|
|
122
|
-
* before it was added. This is the same exemption `fleet/census.ts`,
|
|
123
|
-
* `arch-coverage.ts`, `core/gate/partners.ts` and `core/verify/checklist.ts`
|
|
173
|
+
* before it was added. This is the same exemption `commands/validate/fleet/census.ts`,
|
|
174
|
+
* `commands/validate/arch-coverage.ts`, `core/gate/partners.ts` and `core/verify/checklist.ts`
|
|
124
175
|
* each already apply, through the same `ACTOR_KINDS` — a person was never a
|
|
125
176
|
* participant in any of loam's other censuses either.
|
|
177
|
+
* - The CALLER must not resolve to the provider itself. A hop between two
|
|
178
|
+
* containers of one service — `api -> db` inside a service's own project,
|
|
179
|
+
* or the same pair on a landscape that draws the service as containers —
|
|
180
|
+
* names no operation of that service's contract: an operationId is what a
|
|
181
|
+
* caller OUTSIDE the boundary uses, and a service owes none to itself. The
|
|
182
|
+
* three guards above all pass such a hop (the edge carries no message, the
|
|
183
|
+
* target IS a real `services/<id>/`, the caller is a container), so without
|
|
184
|
+
* this one the warning fired on every hop of every intra-service flow the
|
|
185
|
+
* moment loam started reading them — and the message ("names no operation
|
|
186
|
+
* of X's contract") was about X calling X. Through the same shared
|
|
187
|
+
* resolver as the provider, so the two sides of the comparison cannot
|
|
188
|
+
* disagree about which directory an element stands for.
|
|
126
189
|
*
|
|
127
190
|
* The op test reads the ATTRIBUTION rather than the relationships: an
|
|
128
191
|
* `attributed` verdict with no `op` means every candidate agreed the hop names
|
|
129
192
|
* no operation, which is the state this warn is about. Candidates that disagree
|
|
130
193
|
* never reach here at all — that is the suppression, and it is why it cannot be
|
|
131
|
-
* forgotten.
|
|
194
|
+
* forgotten. Candidates the step's title picked out of a disagreement do reach
|
|
195
|
+
* here, as the picked ones only, and a pick never lands on an edge declaring no
|
|
196
|
+
* name: a title picks by `op`, `publishes` or `consumes`, so a picked verdict
|
|
197
|
+
* either carries its `op` or is a message hop the first guard lets go.
|
|
132
198
|
*/
|
|
133
199
|
function unlinkedProvider(attribution, grading) {
|
|
134
200
|
if (attribution.op !== undefined)
|
|
@@ -138,6 +204,8 @@ function unlinkedProvider(attribution, grading) {
|
|
|
138
204
|
if (isActor(attribution.from, grading.model.elements))
|
|
139
205
|
return null;
|
|
140
206
|
const provider = grading.resolve(attribution.to);
|
|
207
|
+
if (grading.resolve(attribution.from) === provider)
|
|
208
|
+
return null;
|
|
141
209
|
return grading.services.has(provider) ? provider : null;
|
|
142
210
|
}
|
|
143
211
|
/**
|
|
@@ -161,13 +229,13 @@ function isActor(id, elements) {
|
|
|
161
229
|
* provider's contract the step exercises — and the whole point of grading a use
|
|
162
230
|
* case is that the flow joins through to the API it drives.
|
|
163
231
|
*/
|
|
164
|
-
function unlinkedFinding(hop, attribution, provider) {
|
|
232
|
+
function unlinkedFinding(hop, attribution, provider, place) {
|
|
165
233
|
const backing = attribution.rels.length === 1 ? "one relationship" : `${attribution.rels.length} relationships`;
|
|
166
234
|
return {
|
|
167
235
|
severity: "warn",
|
|
168
236
|
code: "usecase.step-unlinked",
|
|
169
237
|
subject: hop.view.id,
|
|
170
|
-
message: `${
|
|
238
|
+
message: `${place}: ${attribution.from} -> ${attribution.to} is backed by ${backing} carrying no ` +
|
|
171
239
|
"`metadata { op }` and no `publishes`/`consumes`, so this hop names no operation of " +
|
|
172
240
|
`${provider}'s contract — the use case reaches the fleet map and stops there. Add ` +
|
|
173
241
|
"`metadata { op '<operationId>' }` to the edge in `model { }`, or `metadata { publishes '<message>' }` / " +
|
|
@@ -183,14 +251,15 @@ function unlinkedFinding(hop, attribution, provider) {
|
|
|
183
251
|
*/
|
|
184
252
|
export function stepFindings(hop, grading) {
|
|
185
253
|
const attribution = attributeStep(hop.step, grading.model);
|
|
254
|
+
const place = placeOf(hop, grading);
|
|
186
255
|
switch (attribution.verdict) {
|
|
187
256
|
case "unbacked":
|
|
188
|
-
return [unbackedFinding(hop, attribution)];
|
|
257
|
+
return [unbackedFinding(hop, attribution, place)];
|
|
189
258
|
case "contested":
|
|
190
|
-
return [contestedFinding(hop, attribution)];
|
|
259
|
+
return [contestedFinding(hop, attribution, place)];
|
|
191
260
|
case "attributed": {
|
|
192
261
|
const provider = unlinkedProvider(attribution, grading);
|
|
193
|
-
return provider === null ? [] : [unlinkedFinding(hop, attribution, provider)];
|
|
262
|
+
return provider === null ? [] : [unlinkedFinding(hop, attribution, provider, place)];
|
|
194
263
|
}
|
|
195
264
|
}
|
|
196
265
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../../../../src/core/usecases/grade/steps.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The three grades one hop of a use case can earn: `usecase.step-unbacked`,\n * `usecase.step-contested` and `usecase.step-unlinked`.\n *\n * The attribution itself is `core/c4/resolve/steps.ts`'s `attributeStep` — which declared\n * relationship backs a hop, oriented on `isBackward` and decided by the\n * DISTINCT-op count — and none of that reasoning is repeated here. This module\n * owns three things: which verdict earns which code, the four guards that keep\n * the warn honest, and the sentences.\n *\n * TWO SCOPES, ONE GRADER. The fleet target grades the `architecture/` project's\n * views and the service target grades a service's own project — every `.likec4`\n * beside `model.likec4`, read the way the renderer reads it\n * (`commands/validate/service/usecases/`). Both call `stepFindings` below with\n * their own `StepGrading`; what differs is only where a finding says the view\n * is (`place`). A second copy of the verdict→code mapping per scope is the\n * drift this package's banner forbids, which is why the intra-service case is\n * a fourth guard here rather than a grader of its own.\n *\n * WHY `step-unbacked` IS THE CHECK THAT EARNS THE FEATURE. A hop between two\n * elements that both exist, with no relationship between them anywhere in the\n * model, is ZERO LikeC4 errors: a `dynamic view` is allowed to show a call the\n * model never declared, so the diagram renders, the project parses, and every\n * other check in loam stays green. The flow reads as fine in every tool the\n * fleet has. If loam does not convict it, nothing does — which is why it is an\n * error rather than an advisory.\n *\n * ONE BREACH, ONE FINDING, and here that is structural rather than remembered.\n * `contested` and `attributed` are disjoint verdicts, so deriving\n * `step-unlinked` from the `attributed` arm — never from the candidate\n * relationships — is what makes a contested step report once. A grader that\n * walked `rels` looking for a missing `op` would convict the same hop twice:\n * once for the disagreement, and once for whichever candidate happened to carry\n * no operation at all.\n */\nimport { attributeStep, pickingTitle, type StepAttribution, type StepScope } from \"../../c4/resolve/steps.js\";\nimport type { Elem, Rel } from \"../../c4/likec4.js\";\nimport type { ParsedStep, ParsedView } from \"../../c4/parsed/dynamic-views.js\";\nimport { ACTOR_KINDS } from \"../../vocabulary/maturity.js\";\nimport type { Finding } from \"../../vocabulary/report.js\";\nimport { stepPlace, type ViewPlace } from \"../place.js\";\n\n/** One hop being graded and the view it belongs to — never passed apart. */\nexport interface GradedStep {\n view: ParsedView;\n step: ParsedStep;\n}\n\n/**\n * What a hop is graded against, built ONCE per run.\n *\n * `services` and `model.known` are the same set by construction (`./usecases.ts`\n * builds both from one value), which is what stops \"which service is this\n * element\" being answered one way by the attribution and another by the provider\n * guard below. `resolve` is injected for the same reason `fleetShapeFindings`\n * takes it: the fleet target already holds the shared element→service resolver,\n * and a second one built here could not disagree today but would be a second\n * place to change.\n */\nexport interface StepGrading {\n model: StepScope;\n /**\n * The `services/<id>/` directories that exist. The SAME enumerated set in\n * both scopes — the service target hands over the whole fleet too, never an\n * empty set — so the documented invariant above (`services` and `model.known`\n * are one value) holds wherever this grader runs, and the intra-service\n * exemption is the fourth guard in `unlinkedProvider` rather than a scope\n * that quietly disables the provider test.\n */\n services: ReadonlySet<string>;\n /** The shared element→service resolver every other edge join uses. */\n resolve: (id: string) => string;\n /**\n * Where a finding says the view is — the opening of every message in this\n * family. REQUIRED since the graders moved into `core/`: the fleet target's\n * `landscape: architecture/<file> — dynamic view '<id>'` is `viewPlace` in\n * `commands/validate/fleet/usecases/place.ts`, command vocabulary core may not\n * import, so every scope states its own. The feature gate injects the feature's\n * `usecases/` file (`./feature.ts`), and the service target\n * injects its own, because a view read out of a service's project carries a\n * `sourcePath` relative to the SERVICE directory, and the `landscape:`\n * opening is the fleet target's name — a finding filed on a service must not\n * claim it (`../place.ts` records why the prefix stayed command vocabulary).\n */\n place: ViewPlace;\n}\n\ntype Unbacked = Extract<StepAttribution, { verdict: \"unbacked\" }>;\ntype Contested = Extract<StepAttribution, { verdict: \"contested\" }>;\ntype Attributed = Extract<StepAttribution, { verdict: \"attributed\" }>;\n\n/**\n * `…checkout.likec4 — dynamic view 'uc_checkout' step 2 'authorizes the payment'`.\n *\n * Spelled once per hop in `stepFindings` and handed to each sentence builder as\n * a string, rather than recomputed from the grading inside each of them: three\n * builders reaching for `grading.place` on their own would be three\n * places for one scope to forget the injection.\n */\nfunction placeOf(hop: GradedStep, grading: StepGrading): string {\n return `${grading.place(hop.view)} ${stepPlace(hop.step)}`;\n}\n\n/**\n * A hop nothing in the model backs.\n *\n * Two fixes, because there are genuinely two mistakes that land here and the\n * author is the only one who can say which they made: the edge really is\n * missing, or the hop is a reply written the long way round. The second is\n * offered as the OTHER spelling of what they already typed — suggesting the\n * spelling they used would be a hint that restates the defect — and the sentence\n * says which pair that spelling looks the hop up under, because `<-` attributes\n * a reply to the CALL it answers rather than to the direction the message\n * travels (`callPair` in core/c4/resolve/steps.ts holds the measurement).\n */\nfunction unbackedFinding(hop: GradedStep, attribution: Unbacked, place: string): Finding {\n const other = hop.step.isBackward\n ? { spelling: `${hop.step.source} -> ${hop.step.target}`, when: \"is not a return step\" }\n : { spelling: `${hop.step.target} <- ${hop.step.source}`, when: \"is a return step\" };\n return {\n severity: \"error\",\n code: \"usecase.step-unbacked\",\n subject: hop.view.id,\n message:\n `${place}: nothing in the model declares ${attribution.from} -> ${attribution.to}, so this hop is ` +\n \"backed by no relationship — and LikeC4 reports no error for it, so the view still renders and the \" +\n \"project still parses. Draw the edge in `model { }` (with `metadata { op '<operationId>' }` where it is \" +\n `a call), or, if this hop ${other.when}, write it as \\`${other.spelling}\\` — a reply is attributed to the ` +\n `call it answers, so that spelling looks the hop up under ${attribution.to} -> ${attribution.from} instead.`,\n };\n}\n\n/**\n * `web -> orders.api \"Calls createOrder\" (op: createOrder)`, `(publishes: order.Placed)`,\n * or `(no op)` where the edge declares no name at all.\n *\n * Every declared name is printed, the message ones included, because each is\n * what the step's title can open with to pick this edge — a line that showed\n * `no op` for a `publishes` edge would hide the one spelling that resolves it.\n * An empty value is not printed: `op ''` parses, names nothing a title could\n * open with, and `op: ` would read as a name somebody forgot to finish.\n */\nfunction candidateLine(rel: Rel): string {\n const names = [\n ...(rel.op === undefined || rel.op === \"\" ? [] : [`op: ${rel.op}`]),\n ...(rel.publishes === undefined || rel.publishes === \"\" ? [] : [`publishes: ${rel.publishes}`]),\n ...(rel.consumes === undefined || rel.consumes === \"\" ? [] : [`consumes: ${rel.consumes}`]),\n ];\n return `${rel.source} -> ${rel.target} \"${rel.title ?? \"\"}\" (${names.length === 0 ? \"no op\" : names.join(\", \")})`;\n}\n\n/**\n * A hop whose candidates disagree about the operation, and whose title picks\n * none of them.\n *\n * WARN, not error, because the model is complete — every candidate is a\n * relationship somebody drew on purpose — and the honest answer is sometimes\n * \"two paths, one flow\". What loam refuses to do is pick: a guessed operation is\n * indistinguishable from a right one in every rollup built over the use case,\n * and it stays wrong invisibly. The AUTHOR can pick, by opening the step's\n * title with the operation id it means (`pickedByTitle` in\n * core/c4/resolve/steps.ts), and the message leads with that repair because it\n * is the one that works in every scope. The older \"draw the hop between the\n * elements that name exactly one\" is still offered, last and qualified: it works\n * where the graded project declares those elements — a service's own flow over\n * its containers, a map that draws a service's containers itself — and is\n * closed on a fleet flow whose services are single elements on the map, which\n * is the hop that had no repair at all before the title could pick.\n *\n * The example spells the author's own title behind a candidate's declared name,\n * so the line to write is visible rather than described, and `pickingTitle`\n * chooses it by trying it — an example that would leave the hop contested is\n * never offered. Which candidate it names is otherwise arbitrary, the first\n * that works, and the sentence says \"for example\" because of it: `details` is\n * where the author finds the one meant. When no single name settles the\n * disagreement — every name shared by candidates that still disagree — there is\n * no example to give, and the sentence goes without one.\n *\n * `details` carries every candidate rather than the distinct ops, because the\n * repair is made on a RELATIONSHIP: the author has to find the edge that names\n * the wrong operation, and a list of operation names does not say which edge\n * that is. An absent `metadata { op }` prints as `no op` and is a candidate like\n * any other — it is exactly as much of a disagreement as a second name.\n */\nfunction contestedFinding(hop: GradedStep, attribution: Contested, place: string): Finding {\n const example = pickingTitle(attribution.rels, hop.step.title);\n return {\n severity: \"warn\",\n code: \"usecase.step-contested\",\n subject: hop.view.id,\n message:\n `${place}: ${attribution.rels.length} relationships back ${attribution.from} -> ` +\n `${attribution.to} and they name ${attribution.ops.length} different operations, so nothing can say ` +\n \"which one this hop exercises. loam names the candidates instead of picking one — a guessed operation \" +\n \"reads exactly like a right one in every rollup built over this flow. Say which one the hop means by \" +\n \"opening the step's title with that candidate's operation id (or the message it publishes or consumes), \" +\n `then punctuation before any further words${example === undefined ? \"\" : ` — for example '${example}'`}; ` +\n \"or give the candidates one operation; or, where this project declares the elements behind each \" +\n \"candidate, draw the hop between them.\",\n details: attribution.rels.map(candidateLine),\n };\n}\n\n/**\n * The service this hop calls, when the hop is a `step-unlinked` candidate at\n * all — `null` when any of the four guards refuses. ALL FOUR are mechanical,\n * and each closes a whole class of warnings that would otherwise be simply\n * wrong. The count is stated because a reader auditing this function is meant to\n * find four bullets and four `return null`s below and conclude that none of\n * them is surplus: the one a miscount invites deleting is the actor guard, and\n * deleting it puts the warning back on the first hop of almost every flow;\n * deleting the last puts it on EVERY hop of every flow drawn inside a service.\n *\n * - A `publishes` or `consumes` on ANY candidate means the hop does reach a\n * declared API — an AsyncAPI one. The event spine already grades it\n * (`spine.message-undefined` and its family), and asking for an `op` as well\n * would be loam demanding an HTTP operationId for a Kafka message. Read\n * across every candidate rather than the first, because on the service tier\n * the candidates are one call drawn as containers.\n * - The target must resolve to a real `services/<id>/`. Without it every hop\n * into an `#external` system, an actor, a database, a cache or a queue warns\n * — none of which owns an `openapi.yaml`, so none of them CAN carry the\n * operationId the message would be asking for. Through the shared resolver,\n * so \"the provider\" here and \"the provider\" in the spine check cannot\n * disagree about which element stands for which directory.\n * - The CALLER must not be an actor. A person is not a caller: `customer ->\n * checkoutWeb 'opens the basket'` is somebody using the app, and the app owes\n * no operationId for a click. The guard above does not cover this, because it\n * reads the TARGET — and the target here is one of ours, so without this the\n * warning fired on the FIRST HOP OF ALMOST EVERY FLOW, which is where a\n * sequence diagram puts its actor. Measured on the published example fleet\n * before it was added. This is the same exemption `commands/validate/fleet/census.ts`,\n * `commands/validate/arch-coverage.ts`, `core/gate/partners.ts` and `core/verify/checklist.ts`\n * each already apply, through the same `ACTOR_KINDS` — a person was never a\n * participant in any of loam's other censuses either.\n * - The CALLER must not resolve to the provider itself. A hop between two\n * containers of one service — `api -> db` inside a service's own project,\n * or the same pair on a landscape that draws the service as containers —\n * names no operation of that service's contract: an operationId is what a\n * caller OUTSIDE the boundary uses, and a service owes none to itself. The\n * three guards above all pass such a hop (the edge carries no message, the\n * target IS a real `services/<id>/`, the caller is a container), so without\n * this one the warning fired on every hop of every intra-service flow the\n * moment loam started reading them — and the message (\"names no operation\n * of X's contract\") was about X calling X. Through the same shared\n * resolver as the provider, so the two sides of the comparison cannot\n * disagree about which directory an element stands for.\n *\n * The op test reads the ATTRIBUTION rather than the relationships: an\n * `attributed` verdict with no `op` means every candidate agreed the hop names\n * no operation, which is the state this warn is about. Candidates that disagree\n * never reach here at all — that is the suppression, and it is why it cannot be\n * forgotten. Candidates the step's title picked out of a disagreement do reach\n * here, as the picked ones only, and a pick never lands on an edge declaring no\n * name: a title picks by `op`, `publishes` or `consumes`, so a picked verdict\n * either carries its `op` or is a message hop the first guard lets go.\n */\nfunction unlinkedProvider(attribution: Attributed, grading: StepGrading): string | null {\n if (attribution.op !== undefined) return null;\n if (attribution.rels.some((rel) => rel.publishes !== undefined || rel.consumes !== undefined)) return null;\n if (isActor(attribution.from, grading.model.elements)) return null;\n const provider = grading.resolve(attribution.to);\n if (grading.resolve(attribution.from) === provider) return null;\n return grading.services.has(provider) ? provider : null;\n}\n\n/**\n * Is this endpoint a person rather than a system?\n *\n * Exact id, not the ancestor walk the service resolver does: an actor is a\n * top-level element in every landscape loam has seen, and a nested one would be\n * a person inside a system, which is not a shape the model has. An endpoint that\n * names no element cannot occur — a step naming one is a LikeC4 reference error,\n * so the document never reaches a check.\n */\nfunction isActor(id: string, elements: Elem[]): boolean {\n const element = elements.find((candidate) => candidate.id === id);\n return element !== undefined && ACTOR_KINDS.has(element.kind.toLowerCase());\n}\n\n/**\n * A hop backed by the model and joined to no contract.\n *\n * The mirror of `spine.op-link-missing` at flow altitude: the use case reaches\n * the fleet map and stops there, so nothing can say which operation of the\n * provider's contract the step exercises — and the whole point of grading a use\n * case is that the flow joins through to the API it drives.\n */\nfunction unlinkedFinding(hop: GradedStep, attribution: Attributed, provider: string, place: string): Finding {\n const backing =\n attribution.rels.length === 1 ? \"one relationship\" : `${attribution.rels.length} relationships`;\n return {\n severity: \"warn\",\n code: \"usecase.step-unlinked\",\n subject: hop.view.id,\n message:\n `${place}: ${attribution.from} -> ${attribution.to} is backed by ${backing} carrying no ` +\n \"`metadata { op }` and no `publishes`/`consumes`, so this hop names no operation of \" +\n `${provider}'s contract — the use case reaches the fleet map and stops there. Add ` +\n \"`metadata { op '<operationId>' }` to the edge in `model { }`, or `metadata { publishes '<message>' }` / \" +\n \"`consumes` where the hop is an event.\",\n };\n}\n\n/**\n * The findings one hop earns — at most one, whatever is wrong with it.\n *\n * A `switch` over the closed verdict union with every arm returning, so a fourth\n * verdict added upstream is a compile error here rather than a hop that silently\n * stops being graded.\n */\nexport function stepFindings(hop: GradedStep, grading: StepGrading): Finding[] {\n const attribution = attributeStep(hop.step, grading.model);\n const place = placeOf(hop, grading);\n switch (attribution.verdict) {\n case \"unbacked\":\n return [unbackedFinding(hop, attribution, place)];\n case \"contested\":\n return [contestedFinding(hop, attribution, place)];\n case \"attributed\": {\n const provider = unlinkedProvider(attribution, grading);\n return provider === null ? [] : [unlinkedFinding(hop, attribution, provider, place)];\n }\n }\n}\n"]}
|
|
@@ -2,14 +2,26 @@
|
|
|
2
2
|
* The use-case axis of `validate --all`: the `dynamic view`s a fleet declares,
|
|
3
3
|
* graded as business flows against the model they are drawn over.
|
|
4
4
|
*
|
|
5
|
+
* IN `core/` SINCE THE FEATURE GATE BECAME ITS SECOND CALLER. It began as
|
|
6
|
+
* `commands/validate/fleet/usecases/`, the fleet target's own package; the
|
|
7
|
+
* archive gate and `loam status` both grade a feature through
|
|
8
|
+
* `core/coherence/`, which may not import a command, so a feature's own flows
|
|
9
|
+
* archived ungraded and the next `validate --all` convicted them
|
|
10
|
+
* (`usecase.step-unbacked`, `usecase.requirement-unresolved` — the real-fleet
|
|
11
|
+
* review, 2026-10-21). One grader for both scopes is the rule this package's
|
|
12
|
+
* banner already stated for the service target; `./feature.ts` is the third
|
|
13
|
+
* caller, and `place` on the scope is the one thing each states for itself.
|
|
14
|
+
*
|
|
5
15
|
* ONLY VIEWS CARRYING A RESERVED TAG ARE GRADED, and that opt-in is what lets
|
|
6
16
|
* this ship into a fleet that already has diagrams. An untagged `dynamic view`
|
|
7
17
|
* is somebody's hand-drawn sequence — drawn to explain something to a person,
|
|
8
18
|
* not to be joined to a model — and grading it would turn every existing docs
|
|
9
|
-
* repo red on upgrade over documents nobody promised anything about. A `#cap
|
|
10
|
-
* or `#
|
|
11
|
-
* model", and nothing else says it: a view with no tags and a view whose
|
|
12
|
-
* are all somebody's own vocabulary are the same thing here.
|
|
19
|
+
* repo red on upgrade over documents nobody promised anything about. A `#cap-`,
|
|
20
|
+
* `#req-` or `#suite-` tag is the author saying "this is a use case; hold it to
|
|
21
|
+
* the model", and nothing else says it: a view with no tags and a view whose
|
|
22
|
+
* tags are all somebody's own vocabulary are the same thing here. A
|
|
23
|
+
* `#suite-`-only view resolves nothing below — it carries no claim — and its
|
|
24
|
+
* hops are graded like any other's.
|
|
13
25
|
*
|
|
14
26
|
* THE TAG OPTS A VIEW IN WHETHER OR NOT IT RESOLVES. A `#cap-` slug that names
|
|
15
27
|
* no declared capability is a broken claim about the vocabulary, not a withdrawn
|
|
@@ -28,13 +40,13 @@
|
|
|
28
40
|
* than per hop — the per-hop cost is a scan, so anything rebuilt per hop is paid
|
|
29
41
|
* against every hop in the fleet.
|
|
30
42
|
*/
|
|
31
|
-
import { compareIds } from "
|
|
32
|
-
import { resolveCapabilityTags } from "
|
|
33
|
-
import { isUseCase } from "
|
|
43
|
+
import { compareIds } from "../../repo/entries.js";
|
|
44
|
+
import { resolveCapabilityTags } from "../../capabilities/usecase-join.js";
|
|
45
|
+
import { isUseCase } from "../fleet.js";
|
|
34
46
|
import { capabilityTagFindings } from "./capability-tag.js";
|
|
35
47
|
import { requirementTagFindings } from "./requirement-tag.js";
|
|
36
48
|
import { stepFindings } from "./steps.js";
|
|
37
|
-
import { viewFile } from "
|
|
49
|
+
import { viewFile } from "../place.js";
|
|
38
50
|
/**
|
|
39
51
|
* The views that opted in, in a stable order.
|
|
40
52
|
*
|
|
@@ -73,6 +85,7 @@ export function useCaseFindings(scope) {
|
|
|
73
85
|
model: { elements: scope.elements, relationships: scope.relationships, known: scope.services },
|
|
74
86
|
services: scope.services,
|
|
75
87
|
resolve: scope.resolve,
|
|
88
|
+
place: scope.place,
|
|
76
89
|
};
|
|
77
90
|
const findings = [];
|
|
78
91
|
for (const view of gradedViews(scope.views)) {
|
|
@@ -88,8 +101,8 @@ export function useCaseFindings(scope) {
|
|
|
88
101
|
// the capability axis would be convicted for the axis not existing.
|
|
89
102
|
if (scope.capabilities !== null) {
|
|
90
103
|
const claims = resolveCapabilityTags(view.tags, scope.capabilities);
|
|
91
|
-
findings.push(...capabilityTagFindings(view, claims));
|
|
92
|
-
findings.push(...requirementTagFindings(view, claims, scope.requirementsOf));
|
|
104
|
+
findings.push(...capabilityTagFindings(view, claims, scope.place));
|
|
105
|
+
findings.push(...requirementTagFindings(view, claims, scope.requirementsOf, scope.place));
|
|
93
106
|
}
|
|
94
107
|
for (const step of view.steps)
|
|
95
108
|
findings.push(...stepFindings({ view, step }, grading));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usecases.js","sourceRoot":"","sources":["../../../../src/core/usecases/grade/usecases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAoB,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAkB,MAAM,aAAa,CAAC;AAkDvD;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,WAAW,CAAC,KAA4B;IAC/C,OAAO,KAAK;SACT,MAAM,CAAC,SAAS,CAAC;SACjB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,KAAmB;IACjD,MAAM,OAAO,GAAgB;QAC3B,mEAAmE;QACnE,0EAA0E;QAC1E,8DAA8D;QAC9D,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE;QAC9F,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC;IACF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5C,4EAA4E;QAC5E,6EAA6E;QAC7E,sEAAsE;QACtE,gDAAgD;QAChD,EAAE;QACF,wEAAwE;QACxE,2EAA2E;QAC3E,wEAAwE;QACxE,0EAA0E;QAC1E,oEAAoE;QACpE,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;YACpE,QAAQ,CAAC,IAAI,CAAC,GAAG,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YACnE,QAAQ,CAAC,IAAI,CAAC,GAAG,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5F,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * The use-case axis of `validate --all`: the `dynamic view`s a fleet declares,\n * graded as business flows against the model they are drawn over.\n *\n * IN `core/` SINCE THE FEATURE GATE BECAME ITS SECOND CALLER. It began as\n * `commands/validate/fleet/usecases/`, the fleet target's own package; the\n * archive gate and `loam status` both grade a feature through\n * `core/coherence/`, which may not import a command, so a feature's own flows\n * archived ungraded and the next `validate --all` convicted them\n * (`usecase.step-unbacked`, `usecase.requirement-unresolved` — the real-fleet\n * review, 2026-10-21). One grader for both scopes is the rule this package's\n * banner already stated for the service target; `./feature.ts` is the third\n * caller, and `place` on the scope is the one thing each states for itself.\n *\n * ONLY VIEWS CARRYING A RESERVED TAG ARE GRADED, and that opt-in is what lets\n * this ship into a fleet that already has diagrams. An untagged `dynamic view`\n * is somebody's hand-drawn sequence — drawn to explain something to a person,\n * not to be joined to a model — and grading it would turn every existing docs\n * repo red on upgrade over documents nobody promised anything about. A `#cap-`,\n * `#req-` or `#suite-` tag is the author saying \"this is a use case; hold it to\n * the model\", and nothing else says it: a view with no tags and a view whose\n * tags are all somebody's own vocabulary are the same thing here. A\n * `#suite-`-only view resolves nothing below — it carries no claim — and its\n * hops are graded like any other's.\n *\n * THE TAG OPTS A VIEW IN WHETHER OR NOT IT RESOLVES. A `#cap-` slug that names\n * no declared capability is a broken claim about the vocabulary, not a withdrawn\n * one, so its steps are still graded — and the step grades read\n * `architecture/capabilities.yaml` nowhere at all. Coupling them would mean a\n * fleet that has not adopted the capability axis could never have a use case\n * checked, which inverts the adoption order the whole axis is built on: draw the\n * flow first, name the capability when the vocabulary exists.\n *\n * Everything here is LINEAR IN STEPS over documents `validate --all` has already\n * parsed, and nothing in it reads a file or throws — a fleet with no tagged view\n * produces exactly nothing. Linear in steps is not free per step: `attributeStep`\n * scans `relationships` once for the exact tier and again for the resolved one,\n * and `isActor` scans `elements`, so the real shape is O(steps × relationships)\n * plus O(steps × elements). That is why `StepGrading` is built once below rather\n * than per hop — the per-hop cost is a scan, so anything rebuilt per hop is paid\n * against every hop in the fleet.\n */\nimport { compareIds } from \"../../repo/entries.js\";\nimport { resolveCapabilityTags } from \"../../capabilities/usecase-join.js\";\nimport { isUseCase } from \"../fleet.js\";\nimport type { Elem, Rel } from \"../../c4/likec4.js\";\nimport type { ParsedView } from \"../../c4/parsed/dynamic-views.js\";\nimport type { Finding } from \"../../vocabulary/report.js\";\nimport { capabilityTagFindings } from \"./capability-tag.js\";\nimport { requirementTagFindings } from \"./requirement-tag.js\";\nimport { stepFindings, type StepGrading } from \"./steps.js\";\nimport { viewFile, type ViewPlace } from \"../place.js\";\n\n/** What the use-case grades run over: the views, the model they are drawn on, and the fleet. */\nexport interface UseCaseScope {\n /**\n * Every `dynamic view` the `architecture/` PROJECT declares, each carrying the\n * file it was written in.\n *\n * The caller must pass views that came from `loadProject` and no others. Only\n * that loader gives a view a `sourcePath` relative to `architecture/`; the\n * single-file loaders name every document after themselves (`source.c4`), and\n * a finding built from one of those would name a file that has never existed\n * — see `../place.ts`, which is where the path is spelled. A feature's own\n * flows come from the staged post-merge project (`../delta/overlay.ts`), whose\n * `sourcePath` is mapped back to the file the author wrote.\n */\n views: readonly ParsedView[];\n elements: Elem[];\n relationships: Rel[];\n /** The `services/<id>/` directories that exist. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver — the same one every other edge join uses. */\n resolve: (id: string) => string;\n /**\n * The declared capability ids, or `null` when `architecture/capabilities.yaml`\n * is absent or does not read as a vocabulary.\n *\n * `null` and `[]` are deliberately different answers, and the join at\n * `core/capabilities/usecase-join.ts` says why it cannot tell them apart\n * itself: an empty list means \"the fleet declares no capabilities\", which is a\n * real verdict every tag then fails against, while `null` means \"there is no\n * vocabulary to grade against\" and suspends the tag grade entirely. The\n * caller applies the ladder because the caller is the one that read the file.\n */\n capabilities: readonly string[] | null;\n /**\n * The requirement ids one capability's document declares, or `undefined` for a\n * capability with no document — the scope a `#req-` tag is resolved in.\n *\n * A function rather than the map itself, so this package never learns what a\n * capability document is: the fleet target reads them once\n * (`capabilityRequirementIndex`) and hands the lookup over. `undefined` and an\n * empty set are different answers here, exactly as they are one join over —\n * no document at all, against a document declaring no promises yet.\n */\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined;\n /** Where every finding says its view is — the scope's own spelling (`ViewPlace`). */\n place: ViewPlace;\n}\n\n/**\n * The views that opted in, in a stable order.\n *\n * The opt-in itself is `isUseCase` in `core/usecases/fleet.ts` and is not\n * restated here. It used to be — the same `startsWith` pair, spelled a second\n * time — and the two spellings drifted: this one took either reserved prefix\n * while the reader-side one took `#cap-` alone, so `validate` graded a\n * `#req-`-only flow that `loam diff`, `delta`, `status`, the packs and `list\n * capabilities` could not see. What is left here is the ORDER, which is this\n * package's own concern.\n *\n * Sorted by (file, view id) rather than reported in LikeC4's own record order:\n * nothing in loam has measured that the parse preserves declaration order, so a\n * report whose row order depends on that would reorder under a dependency bump\n * — the diff-stability rule `core/capabilities/rollup.ts` states for its rows,\n * applied to findings.\n */\nfunction gradedViews(views: readonly ParsedView[]): ParsedView[] {\n return views\n .filter(isUseCase)\n .sort((a, b) => compareIds(viewFile(a), viewFile(b)) || compareIds(a.id, b.id));\n}\n\n/**\n * Every use-case finding a fleet's declared views earn.\n *\n * The grading record is built once and shared across every step of every view:\n * `attributeStep` caches its element→service resolver on the identity of the\n * elements array and the fleet set, so handing it one `StepScope` for the whole\n * run is what keeps the fleet's use cases from rebuilding that id map per hop.\n */\nexport function useCaseFindings(scope: UseCaseScope): Finding[] {\n const grading: StepGrading = {\n // `known` and `services` come from ONE value, so the fleet set the\n // attribution resolves against and the fleet set the provider guard tests\n // cannot drift apart into two answers about the same element.\n model: { elements: scope.elements, relationships: scope.relationships, known: scope.services },\n services: scope.services,\n resolve: scope.resolve,\n place: scope.place,\n };\n const findings: Finding[] = [];\n for (const view of gradedViews(scope.views)) {\n // ONE resolution of this view's capability tags, shared by both grades. The\n // `#req-` tag is scoped by exactly the capabilities the `#cap-` tags resolve\n // to, so resolving twice would be two chances to disagree about which\n // document a `Requirement-ID` is unique inside.\n //\n // THE LADDER IS THE `null`, NOT AN EMPTY LIST, and the distinction is a\n // wrong answer away: a suspended vocabulary handed on as `[]` reads to the\n // requirement grade as \"this view resolves no capability\", which is its\n // `unscoped` ERROR — so every `#req-` tag in a fleet that has not adopted\n // the capability axis would be convicted for the axis not existing.\n if (scope.capabilities !== null) {\n const claims = resolveCapabilityTags(view.tags, scope.capabilities);\n findings.push(...capabilityTagFindings(view, claims, scope.place));\n findings.push(...requirementTagFindings(view, claims, scope.requirementsOf, scope.place));\n }\n for (const step of view.steps) findings.push(...stepFindings({ view, step }, grading));\n }\n return findings;\n}\n"]}
|
|
@@ -7,24 +7,28 @@
|
|
|
7
7
|
* requirement is a heading in somebody else's spec, while "step 4 of Checkout
|
|
8
8
|
* stops working" is the fact a human weighs a pull request against. This module
|
|
9
9
|
* is the join that produces that sentence, and it produces NOTHING ELSE — no
|
|
10
|
-
* finding, no code, no severity. `core/
|
|
11
|
-
* the `details[]` of the two codes that already exist.
|
|
10
|
+
* finding, no code, no severity. `core/consumers/consumers.ts` folds its
|
|
11
|
+
* answers into the `details[]` of the two codes that already exist.
|
|
12
12
|
*
|
|
13
13
|
* ## Attributed is a victim; contested is a suspension
|
|
14
14
|
*
|
|
15
15
|
* The distinction is the whole reason this module is careful rather than a
|
|
16
|
-
* filter. `attributeStep` (core/c4/
|
|
16
|
+
* filter. `attributeStep` (core/c4/resolve/steps.ts) grades a hop by the DISTINCT-op
|
|
17
17
|
* count across the relationships backing it:
|
|
18
18
|
*
|
|
19
19
|
* - `attributed` with `op === X` means every relationship behind the hop agrees
|
|
20
|
-
* the hop exercises X
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* victim
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
20
|
+
* the hop exercises X, or they disagree and the step's title opens with a
|
|
21
|
+
* name declared by candidates that agree on X — the author naming the one
|
|
22
|
+
* the hop means, by its op or by the message that edge also carries. Remove
|
|
23
|
+
* X and the flow breaks at that hop. That is positive evidence, and it is a
|
|
24
|
+
* victim.
|
|
25
|
+
* - `contested` means the relationships DISAGREE and the title names none of
|
|
26
|
+
* them, so loam does not know which operation the hop exercises. It must not
|
|
27
|
+
* claim the flow breaks — a guessed victim reads exactly like a real one and
|
|
28
|
+
* turns a warn into an error on somebody's PR — and it must not go silent
|
|
29
|
+
* either, because silence here is the fail-open shape
|
|
30
|
+
* `ConsumerScan.suspended` exists to forbid. It is a suspension: loam says it
|
|
31
|
+
* could not answer.
|
|
28
32
|
*
|
|
29
33
|
* A contested hop is suspended only when X is among its candidate operations.
|
|
30
34
|
* That is not a noise trade, it is the honest boundary: the candidates ARE the
|
|
@@ -38,7 +42,7 @@
|
|
|
38
42
|
* that convicts it, and repeating that here would file a modelling defect
|
|
39
43
|
* against whoever happened to remove an endpoint.
|
|
40
44
|
*/
|
|
41
|
-
import { attributeStep } from "../c4/
|
|
45
|
+
import { attributeStep } from "../c4/resolve/steps.js";
|
|
42
46
|
import { hopPlace } from "./place.js";
|
|
43
47
|
const EMPTY = { breaks: [], unsure: [] };
|
|
44
48
|
/** Every hop of every graded use case, view order preserved, so output is stable. */
|
|
@@ -49,8 +53,9 @@ function hops(scan) {
|
|
|
49
53
|
* The flows one removed operation breaks, and the ones loam cannot answer for.
|
|
50
54
|
*
|
|
51
55
|
* An `unreadable` scan contributes NOTHING — neither break nor suspension — and
|
|
52
|
-
* that is
|
|
53
|
-
*
|
|
56
|
+
* that is the consumer index's own landscape doctrine (`livingMap` in
|
|
57
|
+
* `core/consumers/current.ts`, which reads the map for every consumer
|
|
58
|
+
* question) applied one axis over: an unreadable fleet map proves nothing either way, `landscape.invalid` is
|
|
54
59
|
* validate's finding to make, and inventing doubt out of a parse error would
|
|
55
60
|
* point the reviewer at a file their change never touched. The caller reports
|
|
56
61
|
* the hole; this join stays quiet about it.
|
|
@@ -68,6 +73,13 @@ export function hopsExercising(scan, removed) {
|
|
|
68
73
|
// payment-service rather than against a service called "api".
|
|
69
74
|
if (scan.resolve(attribution.to) !== removed.provider)
|
|
70
75
|
continue;
|
|
76
|
+
// And the CALLER, through the same resolver: a hop whose two endpoints
|
|
77
|
+
// resolve to one service is that service calling itself, which owes no
|
|
78
|
+
// operationId of its own contract — `usecase.step-unlinked`'s fourth guard,
|
|
79
|
+
// applied to the victim join. Without it an internal hop was a break of the
|
|
80
|
+
// provider's own removal (verification 2026-09-04, R3).
|
|
81
|
+
if (scan.resolve(attribution.from) === removed.provider)
|
|
82
|
+
continue;
|
|
71
83
|
if (attribution.verdict === "attributed") {
|
|
72
84
|
if (attribution.op === removed.op)
|
|
73
85
|
out.breaks.push(hopPlace(view, step));
|
|
@@ -1 +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/arch.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/arch.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"]}
|
|
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/consumers/consumers.ts` folds its\n * answers into 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, or they disagree and the step's title opens with a\n * name declared by candidates that agree on X — the author naming the one\n * the hop means, by its op or by the message that edge also carries. Remove\n * X and the flow breaks at that hop. That is positive evidence, and it is a\n * victim.\n * - `contested` means the relationships DISAGREE and the title names none of\n * them, so loam does not know which operation the hop exercises. It must not\n * claim the flow breaks — a guessed victim reads exactly like a real one and\n * turns a warn into an error on somebody's PR — and it must not go silent\n * either, because silence here is the fail-open shape\n * `ConsumerScan.suspended` exists to forbid. It is a suspension: loam says it\n * 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/consumers/consumers.ts` uses — because this module answers about FLOWS\n * and that 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 the consumer index's own landscape doctrine (`livingMap` in\n * `core/consumers/current.ts`, which reads the map for every consumer\n * question) applied one axis over: 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 // And the CALLER, through the same resolver: a hop whose two endpoints\n // resolve to one service is that service calling itself, which owes no\n // operationId of its own contract — `usecase.step-unlinked`'s fourth guard,\n // applied to the victim join. Without it an internal hop was a break of the\n // provider's own removal (verification 2026-09-04, R3).\n if (scan.resolve(attribution.from) === 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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"place.js","sourceRoot":"","sources":["../../../src/core/usecases/place.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"place.js","sourceRoot":"","sources":["../../../src/core/usecases/place.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;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 * Where a use-case FINDING says its view is — the opening of every message the\n * graders in `./grade/` write. A function each scope supplies rather than one\n * spelling, because the three scopes file a view three ways: the fleet target\n * as `landscape: architecture/<file> — dynamic view '<id>'` (`viewPlace`, which\n * stayed command vocabulary for the reason above), the service target against\n * its own directory, and the feature gate against the feature's `usecases/`\n * file the view is still written in.\n */\nexport type ViewPlace = (view: ParsedView) => string;\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"]}
|