@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,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the generated suite in THIS repository already carries — read-only, and
|
|
3
|
+
* never written.
|
|
4
|
+
*
|
|
5
|
+
* `loam gherkin` stamps every scenario it emits with `@loam-digest-<16hex>`,
|
|
6
|
+
* the same hex a `scenario.tested` claim carries (`core/gherkin/digest.ts` owns
|
|
7
|
+
* the one recipe). So the presence of a stamp answers exactly one question: has
|
|
8
|
+
* a test skeleton for THESE words been generated yet? That is the difference
|
|
9
|
+
* between a unit waiting at `generate` and one waiting at `implement`, and it is
|
|
10
|
+
* the only reason a packet looks at the service repository at all.
|
|
11
|
+
*
|
|
12
|
+
* The resolution is `loam gherkin`'s own — `gherkinRoot` for the directory,
|
|
13
|
+
* `parseStampedFeature` for the bytes — because a second spelling of either
|
|
14
|
+
* would let the packet grade a suite the emitter does not own, or miss one it
|
|
15
|
+
* does.
|
|
16
|
+
*
|
|
17
|
+
* TWO ABSENCES, KEPT APART. A root directory that is not there means the suite
|
|
18
|
+
* has not been generated: the stamps ARE readable and the set is empty, so every
|
|
19
|
+
* scenario unit honestly reads `generate`. No binding, an unreadable repository,
|
|
20
|
+
* or an output directory that escapes it means loam COULD NOT LOOK, which is
|
|
21
|
+
* reported as "unknown" and never as an empty set — "nothing is wrong" and "I
|
|
22
|
+
* could not look" are opposite facts, and only one of them is a task list.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync } from "node:fs";
|
|
25
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
import { parseStampedFeature } from "../gherkin/read.js";
|
|
28
|
+
import { gherkinRoot } from "../gherkin/stamp.js";
|
|
29
|
+
export async function readStamps(binding) {
|
|
30
|
+
const unknown = { state: "unknown", digests: new Set(), service: null };
|
|
31
|
+
if (binding.service === undefined)
|
|
32
|
+
return unknown;
|
|
33
|
+
let root;
|
|
34
|
+
try {
|
|
35
|
+
root = gherkinRoot(binding.repoDir, binding.gherkinDir);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// An output directory that escapes the repo is a configuration loam refuses
|
|
39
|
+
// to write through (`core/kernel/path-safety.ts`), so it is also one this
|
|
40
|
+
// must not read through — and it is a could-not-look, not an empty suite.
|
|
41
|
+
return unknown;
|
|
42
|
+
}
|
|
43
|
+
if (!existsSync(root))
|
|
44
|
+
return { state: "read", digests: new Set(), service: binding.service };
|
|
45
|
+
const digests = new Set();
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// The directory is there and will not open. Unreadable is not empty.
|
|
52
|
+
return unknown;
|
|
53
|
+
}
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (entry.isDirectory() || !entry.name.endsWith(".feature"))
|
|
56
|
+
continue;
|
|
57
|
+
let text;
|
|
58
|
+
try {
|
|
59
|
+
text = await readFile(join(root, entry.name), "utf8");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// One unreadable file under a readable root: the rest of the suite still
|
|
63
|
+
// answers, and a file loam cannot read carries no stamps it may credit.
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const stamped = parseStampedFeature(text);
|
|
67
|
+
// Null means line 1 is not loam's stamp — the file is not loam's writing,
|
|
68
|
+
// and no staleness judgment may be built on it.
|
|
69
|
+
if (stamped === null)
|
|
70
|
+
continue;
|
|
71
|
+
for (const scenario of stamped.scenarios)
|
|
72
|
+
digests.add(scenario.digest);
|
|
73
|
+
}
|
|
74
|
+
return { state: "read", digests, service: binding.service };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=stamps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamps.js","sourceRoot":"","sources":["../../../src/core/tasks/stamps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAoBlD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAuB;IACtD,MAAM,OAAO,GAAiB,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACtF,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IAClD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;QAC5E,0EAA0E;QAC1E,0EAA0E;QAC1E,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9F,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,SAAS;QACtE,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;YACzE,wEAAwE;YACxE,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC1C,0EAA0E;QAC1E,gDAAgD;QAChD,IAAI,OAAO,KAAK,IAAI;YAAE,SAAS;QAC/B,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;AAC9D,CAAC","sourcesContent":["/**\n * What the generated suite in THIS repository already carries — read-only, and\n * never written.\n *\n * `loam gherkin` stamps every scenario it emits with `@loam-digest-<16hex>`,\n * the same hex a `scenario.tested` claim carries (`core/gherkin/digest.ts` owns\n * the one recipe). So the presence of a stamp answers exactly one question: has\n * a test skeleton for THESE words been generated yet? That is the difference\n * between a unit waiting at `generate` and one waiting at `implement`, and it is\n * the only reason a packet looks at the service repository at all.\n *\n * The resolution is `loam gherkin`'s own — `gherkinRoot` for the directory,\n * `parseStampedFeature` for the bytes — because a second spelling of either\n * would let the packet grade a suite the emitter does not own, or miss one it\n * does.\n *\n * TWO ABSENCES, KEPT APART. A root directory that is not there means the suite\n * has not been generated: the stamps ARE readable and the set is empty, so every\n * scenario unit honestly reads `generate`. No binding, an unreadable repository,\n * or an output directory that escapes it means loam COULD NOT LOOK, which is\n * reported as \"unknown\" and never as an empty set — \"nothing is wrong\" and \"I\n * could not look\" are opposite facts, and only one of them is a task list.\n */\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parseStampedFeature } from \"../gherkin/read.js\";\nimport { gherkinRoot } from \"../gherkin/stamp.js\";\n\n/** Which repository's suite to read, as loam.json describes it. */\nexport interface GherkinBinding {\n /** The repo root loam.json was found in — never the cwd, which may be any subdirectory. */\n repoDir: string;\n /** The service this repository says it is; absent in a docs repo or an unbound checkout. */\n service?: string;\n /** `gherkinDir` from loam.json, when it names one. */\n gherkinDir?: string;\n}\n\nexport interface StampReading {\n state: \"read\" | \"unknown\";\n /** The `@loam-digest-…` values found. Meaningful only when `state` is \"read\". */\n digests: ReadonlySet<string>;\n /** The service the stamps belong to — nothing may be said about any other repository's suite. */\n service: string | null;\n}\n\nexport async function readStamps(binding: GherkinBinding): Promise<StampReading> {\n const unknown: StampReading = { state: \"unknown\", digests: new Set(), service: null };\n if (binding.service === undefined) return unknown;\n let root: string;\n try {\n root = gherkinRoot(binding.repoDir, binding.gherkinDir);\n } catch {\n // An output directory that escapes the repo is a configuration loam refuses\n // to write through (`core/kernel/path-safety.ts`), so it is also one this\n // must not read through — and it is a could-not-look, not an empty suite.\n return unknown;\n }\n if (!existsSync(root)) return { state: \"read\", digests: new Set(), service: binding.service };\n const digests = new Set<string>();\n let entries;\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch {\n // The directory is there and will not open. Unreadable is not empty.\n return unknown;\n }\n for (const entry of entries) {\n if (entry.isDirectory() || !entry.name.endsWith(\".feature\")) continue;\n let text: string;\n try {\n text = await readFile(join(root, entry.name), \"utf8\");\n } catch {\n // One unreadable file under a readable root: the rest of the suite still\n // answers, and a file loam cannot read carries no stamps it may credit.\n continue;\n }\n const stamped = parseStampedFeature(text);\n // Null means line 1 is not loam's stamp — the file is not loam's writing,\n // and no staleness judgment may be built on it.\n if (stamped === null) continue;\n for (const scenario of stamped.scenarios) digests.add(scenario.digest);\n }\n return { state: \"read\", digests, service: binding.service };\n}\n"]}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit.js","sourceRoot":"","sources":["../../../src/core/tasks/unit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * What a unit of work IS, and the total order they are read in.\r\n *\r\n * A unit is the third rendering of one ledger. `loam verify` answers \"answered\r\n * by whom\"; this answers \"what is still open, at which step, and what closes\r\n * it\". Nothing here is a second definition of a claim: for a claim-backed unit\r\n * the id, kind, subject, claim text, digest, operation and token are the\r\n * `Claim`'s own fields verbatim (`core/verify/checklist.ts`) — the task id IS\r\n * the claim id, which is what lets an agent hand a unit id straight to\r\n * `loam verify` and lets two runs be diffed.\r\n *\r\n * `at` is the step the unit is waiting at, and it is deliberately NOT spelled\r\n * `stage` or `verdict`: `stage` is the published `ArtifactStatus` key\r\n * (`core/status/report.ts`) and `verdict` is `VerificationVerdict`\r\n * (`core/verify/record.ts`). A third meaning for either word on a payload an\r\n * agent reads beside the other two is how a consumer comes to branch on the\r\n * wrong field.\r\n */\r\nimport { createHash } from \"node:crypto\";\r\nimport { CLAIM_KINDS, ID_LENGTH } from \"../verify/claims/identity.js\";\r\nimport { compareIds } from \"../repo/entries.js\";\r\n\r\n/**\r\n * Where a unit is waiting.\r\n *\r\n * `describe` is the first BDD act — a requirement with no scenario at all, so\r\n * there is nothing to generate a test from and nothing to claim. `generate`\r\n * means the scenario exists and this repository's generated suite does not\r\n * carry its stamp yet. `implement` is everything else that is open.\r\n * `attested` is the one step that is not work: the claim is confirmed on an\r\n * agent's word rather than on a run, so the unit never vanishes — it stays\r\n * visible, and closing it means a cucumber report, not an edit.\r\n */\r\nexport type TaskStep = \"describe\" | \"generate\" | \"implement\" | \"attested\";\r\n\r\n/** Where the words a unit is about are written down, so a reader can open the file. */\r\nexport interface RequirementSource {\r\n /** Repo-relative, like every path in the envelope. */\r\n path: string;\r\n /** 1-based heading line, when the parser recorded one. */\r\n line?: number;\r\n requirement: string;\r\n requirementId?: string;\r\n}\r\n\r\n/**\r\n * A scenario's source — the requirement's, plus the scenario's own words.\r\n *\r\n * A variant rather than four more optional fields on the shape above: `scenario`\r\n * and `lines` are only ever set together, and only for `scenario.tested`. The\r\n * body rides VERBATIM because it is the acceptance criterion the implementer is\r\n * meant to satisfy and the exact text the digest was taken over — paraphrasing\r\n * it here would be the one thing the whole digest chain exists to prevent.\r\n */\r\nexport interface ScenarioSource extends RequirementSource {\r\n scenario: string;\r\n lines: string[];\r\n}\r\n\r\nexport type TaskSource = RequirementSource | ScenarioSource;\r\n\r\nexport interface TaskUnit {\r\n /** The claim id verbatim, or a `requirement.described-…` id from `describedUnitId`. */\r\n id: string;\r\n kind: string;\r\n subject: string;\r\n at: TaskStep;\r\n claim: string;\r\n digest?: string;\r\n operation?: string;\r\n token?: string;\r\n dependsOn: string[];\r\n source?: TaskSource;\r\n done: { check: string };\r\n}\r\n\r\n/**\r\n * The one kind that is NOT a claim kind.\r\n *\r\n * `CLAIM_KINDS` is frozen: a member added to it re-hashes nothing, but it\r\n * changes the checklist's question set, and every `verification.yaml` in the\r\n * fleet would read as stale on upgrade. A requirement with no scenarios is also\r\n * not a promise anybody can answer with evidence — there is no text to test —\r\n * so it lives in a namespace of its own, with an id recipe of its own.\r\n */\r\nexport const DESCRIBED_KIND = \"requirement.described\";\r\n\r\n/**\r\n * The id of a `requirement.described` unit: content-derived, like a claim id\r\n * and for a claim id's reason — rename the requirement and the unit retires\r\n * itself rather than following a heading around.\r\n *\r\n * `\"loam.task\"` leads the tuple so this namespace can never collide with\r\n * `claimId`'s, whose tuples start with the feature id. NUL-joined for\r\n * `claimId`'s reason: no part's own text can spell the separator, so\r\n * [\"a b\",\"c\"] and [\"a\",\"b c\"] stay two units. `axisFile` is `spec.md` or\r\n * `arch.spec.md` — the two axes are two namespaces everywhere else in loam\r\n * (`core/gherkin/digest.ts` salts the scenario hash with it), and one\r\n * requirement name may legitimately appear on both.\r\n */\r\nexport function describedUnitId(\r\n featureId: string,\r\n service: string,\r\n axisFile: string,\r\n requirement: string,\r\n): string {\r\n const tuple = [\"loam.task\", featureId, DESCRIBED_KIND, service, axisFile, requirement].join(\"\u0000\");\r\n return `${DESCRIBED_KIND}-${createHash(\"sha256\").update(tuple).digest(\"hex\").slice(0, ID_LENGTH)}`;\r\n}\r\n\r\n/**\r\n * What closes this unit, in one sentence a reader can act on without opening\r\n * another document. Every arm names the mechanical channel that may answer it,\r\n * because the channels are not interchangeable: a scenario claim confirmed\r\n * without a run is `attested` and not `verified` (core/verify/record.ts), and an\r\n * api.exposes claim is answered by a contract report rather than by a cucumber\r\n * one.\r\n */\r\nexport function doneCheck(unit: { kind: string; digest?: string; operation?: string }): string {\r\n if (unit.kind === DESCRIBED_KIND) return \"the requirement carries at least one #### Scenario:\";\r\n if (unit.kind === \"scenario.tested\") {\r\n // Both report shapes by name: a fleet whose runner has no cucumber\r\n // formatter otherwise reads this as \"cucumber or nothing\", and the\r\n // runner-neutral shape exists precisely so it is not.\r\n return (\r\n `scenario @loam-digest-${unit.digest ?? \"\"} passes in a test run — a cucumber JSON report, ` +\r\n \"or a loamScenarioReport entry carrying that digest — and is recorded confirmed (loam verify --results)\"\r\n );\r\n }\r\n if (unit.kind === \"api.exposes\") {\r\n return `operationId '${unit.operation ?? \"\"}' reported passed via loam verify --contract-results and recorded`;\r\n }\r\n return \"recorded confirmed with file:line evidence (loam verify --record)\";\r\n}\r\n\r\n/**\r\n * The one edge v1 draws: a `c4.calls` unit depends on the `api.exposes` units\r\n * that promise the operation it calls.\r\n *\r\n * The join is EXACT and cross-kind on purpose — a call edge's `token` against a\r\n * provider claim's `operation`, never token against token. An AsyncAPI message\r\n * name and an operationId are different namespaces that routinely spell the\r\n * same word (`payment.Authorized` is not an operationId, but `createSplit` is a\r\n * perfectly plausible message name), and `event.declares` carries a `token`\r\n * too: joining on token equality would wire a consumer's call to an unrelated\r\n * message declaration and then order the whole packet by that fiction.\r\n * `operation` is set on `api.exposes` and nothing else — `core/verify/checklist.ts`\r\n * says so in the field's own docblock, and `contestedOperations` already\r\n * depends on it.\r\n *\r\n * Ordering is ADVICE, never a lock. The frontier does not exclude a unit for\r\n * having open dependencies; the sort merely puts providers first. An\r\n * implementer who starts at the consumer is not doing anything wrong, and a\r\n * packet that refused to show them the work would be a scheduler.\r\n */\r\nexport function linkDependencies(units: TaskUnit[]): void {\r\n const providers = new Map<string, string[]>();\r\n for (const unit of units) {\r\n if (unit.kind !== \"api.exposes\" || unit.operation === undefined) continue;\r\n const ids = providers.get(unit.operation) ?? [];\r\n ids.push(unit.id);\r\n providers.set(unit.operation, ids);\r\n }\r\n for (const unit of units) {\r\n if (unit.kind !== \"c4.calls\" || unit.token === undefined) continue;\r\n unit.dependsOn = [...(providers.get(unit.token) ?? [])].sort();\r\n }\r\n}\r\n\r\n/**\r\n * The reading order, total and deterministic — `--json` consumers diff it.\r\n *\r\n * Describe first, because a requirement with no scenario blocks the whole BDD\r\n * chain under it: there is nothing to generate and nothing to test. Then the\r\n * claim kinds in `CLAIM_KINDS` order, which is read off the vocabulary rather\r\n * than respelled here — that order already reads as the story of a feature (the\r\n * service exists, it exposes its operations, it declares its messages, the calls\r\n * into it are wired, the behaviour is tested), and a second copy of it would be\r\n * free to disagree.\r\n *\r\n * Within a kind a unit somebody depends on sorts before one nobody does, so the\r\n * provider of a shared operation leads its own kind. Then the subject by\r\n * `compareIds` (the fleet's one id ordering), then the source line so a service's\r\n * units read in document order, then the id as the final tiebreak — a unit with\r\n * no source must still land somewhere stable.\r\n */\r\nexport function sortUnits(units: TaskUnit[]): TaskUnit[] {\r\n const dependedOn = new Set(units.flatMap((unit) => unit.dependsOn));\r\n const kindRank = (unit: TaskUnit): number => {\r\n const index = (CLAIM_KINDS as readonly string[]).indexOf(unit.kind);\r\n // A kind outside the vocabulary sorts last rather than first: -1 would put\r\n // an unknown kind ahead of `service.exists`, which is the one position that\r\n // silently reorders every packet the day a kind is added.\r\n return index === -1 ? CLAIM_KINDS.length : index;\r\n };\r\n const lineOf = (unit: TaskUnit): number => unit.source?.line ?? Number.MAX_SAFE_INTEGER;\r\n return [...units].sort(\r\n (a, b) =>\r\n Number(a.at !== \"describe\") - Number(b.at !== \"describe\") ||\r\n kindRank(a) - kindRank(b) ||\r\n Number(dependedOn.has(b.id)) - Number(dependedOn.has(a.id)) ||\r\n compareIds(a.subject, b.subject) ||\r\n lineOf(a) - lineOf(b) ||\r\n compareIds(a.id, b.id),\r\n );\r\n}\r\n\r\n/** How many unit ids the frontier carries before it starts eliding. */\r\nexport const FRONTIER_LIMIT = 10;\r\n\r\n/** An open unit is one that is still work: `attested` is complete work resting on an assertion. */\r\nexport function isOpen(unit: TaskUnit): boolean {\r\n return unit.at !== \"attested\";\r\n}\r\n"]}
|
|
@@ -25,6 +25,7 @@ import { capabilityRequirementIndex } from "../capabilities/findings.js";
|
|
|
25
25
|
import { compareIds } from "../repo/entries.js";
|
|
26
26
|
import { readUseCases } from "./fleet.js";
|
|
27
27
|
import { viewFile } from "./place.js";
|
|
28
|
+
import { participantsOf } from "./suites/participants.js";
|
|
28
29
|
/** The tag spelling a declared capability id would carry — `identity/tokens` → `cap-identity-tokens`. */
|
|
29
30
|
function tagFor(capability) {
|
|
30
31
|
return `${CAP_TAG_PREFIX}${tagSlug(capability)}`;
|
|
@@ -65,6 +66,14 @@ export function flowsClaiming(scan, capability) {
|
|
|
65
66
|
* exactly why it is written down that the reason it exists is the return type,
|
|
66
67
|
* not the call site.
|
|
67
68
|
*
|
|
69
|
+
* The walk itself is `./suites/participants.ts`' `participantsOf`, which
|
|
70
|
+
* this function used to spell for itself: a suite asks the same question of a
|
|
71
|
+
* different set of views and needs the participants that resolve to NO service
|
|
72
|
+
* as well. The restriction now lives here as a choice — this function reads
|
|
73
|
+
* `services` and drops `unresolved` — rather than as a filter inside the walk,
|
|
74
|
+
* so the one union a test stand is provisioned from and the one `explore` seeds
|
|
75
|
+
* from cannot resolve an element two ways.
|
|
76
|
+
*
|
|
68
77
|
* The cost of the restriction, stated so the next reader can weigh it: a flow
|
|
69
78
|
* drawn through a service the fleet has not adopted yet contributes no seed.
|
|
70
79
|
* That service has no documentation to explore, so what is lost is a name in a
|
|
@@ -75,20 +84,8 @@ export function servicesInFlowsClaiming(scan, capabilities) {
|
|
|
75
84
|
if (scan.kind !== "read")
|
|
76
85
|
return [];
|
|
77
86
|
const tags = new Set(capabilities.map(tagFor));
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
for (const view of scan.views) {
|
|
81
|
-
if (![...tags].some((tag) => claims(view, tag)))
|
|
82
|
-
continue;
|
|
83
|
-
for (const step of view.steps) {
|
|
84
|
-
for (const id of [step.source, step.target]) {
|
|
85
|
-
const service = scan.resolve(id);
|
|
86
|
-
if (known.has(service))
|
|
87
|
-
out.add(service);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return [...out].sort(compareIds);
|
|
87
|
+
const claiming = scan.views.filter((view) => [...tags].some((tag) => claims(view, tag)));
|
|
88
|
+
return participantsOf(scan, claiming).services;
|
|
92
89
|
}
|
|
93
90
|
/**
|
|
94
91
|
* The `<capability>#<Requirement-ID>` pairs a fleet's flows RESOLVE to — the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../../../src/core/usecases/capability.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The capability half of the use-case axis: which flows CLAIM a declared\n * capability, and which services those flows run through.\n *\n * `core/capabilities/usecase-join.ts` owns the tag grammar and the resolution\n * ladder — it answers \"what does this tag name?\", including the `many` arm the\n * slug's non-injectivity forces. This module asks the question the other way\n * round: given a capability id somebody already has, which views carry its tag?\n * That direction needs no ladder, because the id is not in doubt; it needs only\n * the slug, so it takes the slug and stops.\n *\n * ## What this deliberately does NOT do\n *\n * It does not grade the tag. A view tagged `#cap-payments-refunds` matches BOTH\n * `payments/refunds` and `payments-refunds` when a fleet declares both, and this\n * module reports it under both — because that is what the tag says, and the tag\n * is what the author wrote. `usecase.capability-unresolved` is already an ERROR\n * over exactly that collision (`validate`'s use-case package), so a second\n * verdict here would be a second answer to a question the gate has already\n * settled. The same stance `PackPermission` takes on an undeclared permission:\n * carry the claim, let validate convict it.\n */\nimport {\n tagSlug,\n CAP_TAG_PREFIX,\n resolveCapabilityTags,\n resolveRequirementTags,\n} from \"../capabilities/usecase-join.js\";\nimport { capabilityRequirementIndex } from \"../capabilities/findings.js\";\nimport type { CapabilityVocabulary } from \"../capabilities/capabilities.js\";\nimport type { Requirement } from \"../document/spec.js\";\nimport { compareIds } from \"../repo/entries.js\";\nimport type { ParsedView } from \"../c4/parsed/dynamic-views.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport { readUseCases, type UseCaseScan } from \"./fleet.js\";\nimport { viewFile } from \"./place.js\";\n\n/** One flow claiming a capability: enough to name it and to open the file it lives in. */\nexport interface ClaimingFlow {\n id: string;\n title?: string;\n /** Repo-relative document the view is written in. */\n file: string;\n}\n\n/** The tag spelling a declared capability id would carry — `identity/tokens` → `cap-identity-tokens`. */\nfunction tagFor(capability: string): string {\n return `${CAP_TAG_PREFIX}${tagSlug(capability)}`;\n}\n\n/** Does this view claim the capability? Exact tag match, case included, as `resolveCapabilityTags` matches. */\nfunction claims(view: ParsedView, tag: string): boolean {\n return view.tags.includes(tag);\n}\n\n/**\n * The flows claiming one declared capability, sorted by (file, id).\n *\n * Sorted rather than reported in LikeC4's record order for the reason every\n * other list in this axis is: nothing has measured that the parse preserves\n * declaration order, and a payload ordered by an unmeasured upstream detail\n * reorders under a dependency bump.\n */\nexport function flowsClaiming(scan: UseCaseScan, capability: string): ClaimingFlow[] {\n if (scan.kind !== \"read\") return [];\n const tag = tagFor(capability);\n return scan.views\n .filter((view) => claims(view, tag))\n .map((view) => ({ id: view.id, ...(view.title === undefined ? {} : { title: view.title }), file: viewFile(view) }))\n .sort((a, b) => compareIds(a.file, b.file) || compareIds(a.id, b.id));\n}\n\n/**\n * Every ENUMERATED service the flows claiming these capabilities run through.\n *\n * The restriction to the enumerated fleet is this function's own contract, not\n * a guard somebody downstream is relying on: a step's endpoints resolve to\n * whatever the element stands for, and a sequence diagram is full of endpoints\n * that own no directory — the actor who starts the flow, the topic in the\n * middle, the database at the end. A function called `servicesInFlows…` that\n * answered `Customer` would be lying in its own name. `loam explore`, today's\n * only caller, filters the result against its enumeration again for a different\n * reason (it needs `ServiceEntry`s, in enumeration order), so removing the\n * filter here would not currently change what that command prints — which is\n * exactly why it is written down that the reason it exists is the return type,\n * not the call site.\n *\n * The cost of the restriction, stated so the next reader can weigh it: a flow\n * drawn through a service the fleet has not adopted yet contributes no seed.\n * That service has no documentation to explore, so what is lost is a name in a\n * list, and `usecase.step-unlinked` / `landscape.service-undocumented` are where\n * the fleet is told about it.\n */\nexport function servicesInFlowsClaiming(scan: UseCaseScan, capabilities: readonly string[]): string[] {\n if (scan.kind !== \"read\") return [];\n const tags = new Set(capabilities.map(tagFor));\n const known = scan.model.known;\n const out = new Set<string>();\n for (const view of scan.views) {\n if (![...tags].some((tag) => claims(view, tag))) continue;\n for (const step of view.steps) {\n for (const id of [step.source, step.target]) {\n const service = scan.resolve(id);\n if (known.has(service)) out.add(service);\n }\n }\n }\n return [...out].sort(compareIds);\n}\n\n/**\n * The `<capability>#<Requirement-ID>` pairs a fleet's flows RESOLVE to — the\n * business promises a use case claims to keep.\n *\n * Lives in core rather than beside the grade that reports a broken tag, because\n * two very different callers need the same answer and they must not compute it\n * twice: `validate --all` subtracts these from\n * `capability.requirement-unrealized` (a promise a flow keeps is not unkept),\n * and `loam list capabilities` reports them beside the service requirements\n * that realize the same promise. Two implementations of \"which promises does\n * this fleet keep\" is two answers to the question the axis exists to settle.\n *\n * A MAP RATHER THAN A SET, because the two callers need different depths of the\n * same answer and one of them needs names. `validate` only asks whether a\n * promise is kept (`.has`, which a Map answers identically); a listing that said\n * \"kept\" without saying by WHAT would leave a reader unable to open the flow, go\n * look at its hops and judge whether the promise really is kept — which is the\n * whole reason the listing carries the realizing service requirements by name\n * rather than as a count. Widening the return type was cheaper than a second\n * function, and a second function is how the two corpora start disagreeing.\n *\n * ONLY RESOLVED CLAIMS COUNT, and both halves must resolve: the `#cap-` tag to\n * exactly one declared capability, and the `#req-` tag to exactly one of its\n * requirements. A broken tag of either kind is already an ERROR\n * (`usecase.capability-unresolved`, `usecase.requirement-unresolved`) and must\n * never also mark a promise kept — a typo that silenced the unrealized warning\n * would turn a mistake into a green fleet.\n *\n * `declared` carries the vocabulary ladder: `null` means there is nothing to\n * grade against, and the honest answer is then an empty map — no flow can be\n * said to keep a promise loam cannot see declared.\n *\n * Diff-stable by construction: the view ids under each promise are DEDUPLICATED\n * and sorted with compareIds, so nothing here depends on LikeC4's view order,\n * on how many tags one view carried, or on the readdir order the project was\n * staged in. The map's own key order reaches no output — both callers look\n * promises up by key — but the values are printed, so they are sorted.\n */\nexport function useCaseRequirementClaims(\n views: readonly ParsedView[],\n declared: readonly string[] | null,\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined,\n): ReadonlyMap<string, string[]> {\n const claimed = new Map<string, Set<string>>();\n if (declared !== null) {\n for (const view of views) {\n const scope = resolveCapabilityTags(view.tags, declared).flatMap((claim) =>\n claim.kind === \"resolved\" ? [claim.id] : [],\n );\n for (const claim of resolveRequirementTags(view.tags, scope, requirementsOf)) {\n if (claim.kind !== \"resolved\") continue;\n const key = `${claim.capability}#${claim.id}`;\n const flows = claimed.get(key) ?? new Set<string>();\n flows.add(view.id);\n claimed.set(key, flows);\n }\n }\n }\n return new Map([...claimed].map(([key, flows]) => [key, [...flows].sort(compareIds)]));\n}\n\n/**\n * The same answer as `useCaseRequirementClaims`, read from disk — or the honest\n * refusal to give one.\n *\n * `validate --all` already holds the parsed project and has already applied the\n * vocabulary ladder by the time it grades, so it calls the pure function above\n * with what it has. `loam list capabilities` holds neither, and the three steps\n * between a docsDir and that answer — read the flows, take the ladder, index the\n * documents' requirement ids — are three chances to get the suppression rules\n * wrong. They live here rather than in the command for that reason and not for\n * brevity: a command that spelled `[...vocab.byId.keys()]` in place of the\n * ladder would report a flow's promise as kept against a vocabulary nobody could\n * read.\n *\n * THE `unreadable` ARM MUST TRAVEL. An `architecture/` that did not parse yields\n * no views, and an empty map is indistinguishable from \"no flow keeps\n * anything\" — which is the single wrong answer this axis exists to prevent, and\n * the reason `readUseCases` is a tagged union rather than a list.\n */\nexport type PromisesKept =\n | { kind: \"read\"; kept: ReadonlyMap<string, readonly string[]> }\n | { kind: \"unreadable\"; errors: string[] };\n\nexport interface PromisesKeptRequest {\n docsDir: DocsDir;\n vocab: CapabilityVocabulary;\n /** The enumerated fleet — `readUseCases`' own requirement, for its own reason. */\n known: ReadonlySet<string>;\n /** The requirement reader — pass a FleetContext's readRequirements, bound. */\n read: (path: string) => Promise<Requirement[]>;\n}\n\nexport async function promisesKeptByFlows(req: PromisesKeptRequest): Promise<PromisesKept> {\n const scan = await readUseCases({ docsDir: req.docsDir, known: req.known });\n if (scan.kind !== \"read\") return { kind: \"unreadable\", errors: scan.errors };\n // No flow, no claim — and no capability document is opened to prove it.\n // `readUseCases`' byte gate answers a fleet with no use cases without starting\n // LikeC4 at all, and reading every `capabilities/<id>/spec.md` here to resolve\n // a tag set that is empty would put back the cost the gate exists to remove.\n // The `read` arm is still what travels: the fleet WAS looked at.\n if (scan.views.length === 0) return { kind: \"read\", kept: new Map() };\n const index = await capabilityRequirementIndex(req.vocab, req.read);\n // `index.declared` rather than a second call to `gradableCapabilityIds`: the\n // index applied the ladder when it was built, and two applications of one rule\n // is where the two halves of this join start disagreeing about whether the\n // vocabulary is gradable at all.\n return {\n kind: \"read\",\n kept: useCaseRequirementClaims(scan.views, index.declared, (c) => index.byCapability.get(c)),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../../../src/core/usecases/capability.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The capability half of the use-case axis: which flows CLAIM a declared\n * capability, and which services those flows run through.\n *\n * `core/capabilities/usecase-join.ts` owns the tag grammar and the resolution\n * ladder — it answers \"what does this tag name?\", including the `many` arm the\n * slug's non-injectivity forces. This module asks the question the other way\n * round: given a capability id somebody already has, which views carry its tag?\n * That direction needs no ladder, because the id is not in doubt; it needs only\n * the slug, so it takes the slug and stops.\n *\n * ## What this deliberately does NOT do\n *\n * It does not grade the tag. A view tagged `#cap-payments-refunds` matches BOTH\n * `payments/refunds` and `payments-refunds` when a fleet declares both, and this\n * module reports it under both — because that is what the tag says, and the tag\n * is what the author wrote. `usecase.capability-unresolved` is already an ERROR\n * over exactly that collision (`validate`'s use-case package), so a second\n * verdict here would be a second answer to a question the gate has already\n * settled. The same stance `PackPermission` takes on an undeclared permission:\n * carry the claim, let validate convict it.\n */\nimport {\n tagSlug,\n CAP_TAG_PREFIX,\n resolveCapabilityTags,\n resolveRequirementTags,\n} from \"../capabilities/usecase-join.js\";\nimport { capabilityRequirementIndex } from \"../capabilities/findings.js\";\nimport type { CapabilityVocabulary } from \"../capabilities/capabilities.js\";\nimport type { Requirement } from \"../document/spec.js\";\nimport { compareIds } from \"../repo/entries.js\";\nimport type { ParsedView } from \"../c4/parsed/dynamic-views.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport { readUseCases, type UseCaseScan } from \"./fleet.js\";\nimport { viewFile } from \"./place.js\";\nimport { participantsOf } from \"./suites/participants.js\";\n\n/** One flow claiming a capability: enough to name it and to open the file it lives in. */\nexport interface ClaimingFlow {\n id: string;\n title?: string;\n /** Repo-relative document the view is written in. */\n file: string;\n}\n\n/** The tag spelling a declared capability id would carry — `identity/tokens` → `cap-identity-tokens`. */\nfunction tagFor(capability: string): string {\n return `${CAP_TAG_PREFIX}${tagSlug(capability)}`;\n}\n\n/** Does this view claim the capability? Exact tag match, case included, as `resolveCapabilityTags` matches. */\nfunction claims(view: ParsedView, tag: string): boolean {\n return view.tags.includes(tag);\n}\n\n/**\n * The flows claiming one declared capability, sorted by (file, id).\n *\n * Sorted rather than reported in LikeC4's record order for the reason every\n * other list in this axis is: nothing has measured that the parse preserves\n * declaration order, and a payload ordered by an unmeasured upstream detail\n * reorders under a dependency bump.\n */\nexport function flowsClaiming(scan: UseCaseScan, capability: string): ClaimingFlow[] {\n if (scan.kind !== \"read\") return [];\n const tag = tagFor(capability);\n return scan.views\n .filter((view) => claims(view, tag))\n .map((view) => ({ id: view.id, ...(view.title === undefined ? {} : { title: view.title }), file: viewFile(view) }))\n .sort((a, b) => compareIds(a.file, b.file) || compareIds(a.id, b.id));\n}\n\n/**\n * Every ENUMERATED service the flows claiming these capabilities run through.\n *\n * The restriction to the enumerated fleet is this function's own contract, not\n * a guard somebody downstream is relying on: a step's endpoints resolve to\n * whatever the element stands for, and a sequence diagram is full of endpoints\n * that own no directory — the actor who starts the flow, the topic in the\n * middle, the database at the end. A function called `servicesInFlows…` that\n * answered `Customer` would be lying in its own name. `loam explore`, today's\n * only caller, filters the result against its enumeration again for a different\n * reason (it needs `ServiceEntry`s, in enumeration order), so removing the\n * filter here would not currently change what that command prints — which is\n * exactly why it is written down that the reason it exists is the return type,\n * not the call site.\n *\n * The walk itself is `./suites/participants.ts`' `participantsOf`, which\n * this function used to spell for itself: a suite asks the same question of a\n * different set of views and needs the participants that resolve to NO service\n * as well. The restriction now lives here as a choice — this function reads\n * `services` and drops `unresolved` — rather than as a filter inside the walk,\n * so the one union a test stand is provisioned from and the one `explore` seeds\n * from cannot resolve an element two ways.\n *\n * The cost of the restriction, stated so the next reader can weigh it: a flow\n * drawn through a service the fleet has not adopted yet contributes no seed.\n * That service has no documentation to explore, so what is lost is a name in a\n * list, and `usecase.step-unlinked` / `landscape.service-undocumented` are where\n * the fleet is told about it.\n */\nexport function servicesInFlowsClaiming(scan: UseCaseScan, capabilities: readonly string[]): string[] {\n if (scan.kind !== \"read\") return [];\n const tags = new Set(capabilities.map(tagFor));\n const claiming = scan.views.filter((view) => [...tags].some((tag) => claims(view, tag)));\n return participantsOf(scan, claiming).services;\n}\n\n/**\n * The `<capability>#<Requirement-ID>` pairs a fleet's flows RESOLVE to — the\n * business promises a use case claims to keep.\n *\n * Lives in core rather than beside the grade that reports a broken tag, because\n * two very different callers need the same answer and they must not compute it\n * twice: `validate --all` subtracts these from\n * `capability.requirement-unrealized` (a promise a flow keeps is not unkept),\n * and `loam list capabilities` reports them beside the service requirements\n * that realize the same promise. Two implementations of \"which promises does\n * this fleet keep\" is two answers to the question the axis exists to settle.\n *\n * A MAP RATHER THAN A SET, because the two callers need different depths of the\n * same answer and one of them needs names. `validate` only asks whether a\n * promise is kept (`.has`, which a Map answers identically); a listing that said\n * \"kept\" without saying by WHAT would leave a reader unable to open the flow, go\n * look at its hops and judge whether the promise really is kept — which is the\n * whole reason the listing carries the realizing service requirements by name\n * rather than as a count. Widening the return type was cheaper than a second\n * function, and a second function is how the two corpora start disagreeing.\n *\n * ONLY RESOLVED CLAIMS COUNT, and both halves must resolve: the `#cap-` tag to\n * exactly one declared capability, and the `#req-` tag to exactly one of its\n * requirements. A broken tag of either kind is already an ERROR\n * (`usecase.capability-unresolved`, `usecase.requirement-unresolved`) and must\n * never also mark a promise kept — a typo that silenced the unrealized warning\n * would turn a mistake into a green fleet.\n *\n * `declared` carries the vocabulary ladder: `null` means there is nothing to\n * grade against, and the honest answer is then an empty map — no flow can be\n * said to keep a promise loam cannot see declared.\n *\n * Diff-stable by construction: the view ids under each promise are DEDUPLICATED\n * and sorted with compareIds, so nothing here depends on LikeC4's view order,\n * on how many tags one view carried, or on the readdir order the project was\n * staged in. The map's own key order reaches no output — both callers look\n * promises up by key — but the values are printed, so they are sorted.\n */\nexport function useCaseRequirementClaims(\n views: readonly ParsedView[],\n declared: readonly string[] | null,\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined,\n): ReadonlyMap<string, string[]> {\n const claimed = new Map<string, Set<string>>();\n if (declared !== null) {\n for (const view of views) {\n const scope = resolveCapabilityTags(view.tags, declared).flatMap((claim) =>\n claim.kind === \"resolved\" ? [claim.id] : [],\n );\n for (const claim of resolveRequirementTags(view.tags, scope, requirementsOf)) {\n if (claim.kind !== \"resolved\") continue;\n const key = `${claim.capability}#${claim.id}`;\n const flows = claimed.get(key) ?? new Set<string>();\n flows.add(view.id);\n claimed.set(key, flows);\n }\n }\n }\n return new Map([...claimed].map(([key, flows]) => [key, [...flows].sort(compareIds)]));\n}\n\n/**\n * The same answer as `useCaseRequirementClaims`, read from disk — or the honest\n * refusal to give one.\n *\n * `validate --all` already holds the parsed project and has already applied the\n * vocabulary ladder by the time it grades, so it calls the pure function above\n * with what it has. `loam list capabilities` holds neither, and the three steps\n * between a docsDir and that answer — read the flows, take the ladder, index the\n * documents' requirement ids — are three chances to get the suppression rules\n * wrong. They live here rather than in the command for that reason and not for\n * brevity: a command that spelled `[...vocab.byId.keys()]` in place of the\n * ladder would report a flow's promise as kept against a vocabulary nobody could\n * read.\n *\n * THE `unreadable` ARM MUST TRAVEL. An `architecture/` that did not parse yields\n * no views, and an empty map is indistinguishable from \"no flow keeps\n * anything\" — which is the single wrong answer this axis exists to prevent, and\n * the reason `readUseCases` is a tagged union rather than a list.\n */\nexport type PromisesKept =\n | { kind: \"read\"; kept: ReadonlyMap<string, readonly string[]> }\n | { kind: \"unreadable\"; errors: string[] };\n\nexport interface PromisesKeptRequest {\n docsDir: DocsDir;\n vocab: CapabilityVocabulary;\n /** The enumerated fleet — `readUseCases`' own requirement, for its own reason. */\n known: ReadonlySet<string>;\n /** The requirement reader — pass a FleetContext's readRequirements, bound. */\n read: (path: string) => Promise<Requirement[]>;\n}\n\nexport async function promisesKeptByFlows(req: PromisesKeptRequest): Promise<PromisesKept> {\n const scan = await readUseCases({ docsDir: req.docsDir, known: req.known });\n if (scan.kind !== \"read\") return { kind: \"unreadable\", errors: scan.errors };\n // No flow, no claim — and no capability document is opened to prove it.\n // `readUseCases`' byte gate answers a fleet with no use cases without starting\n // LikeC4 at all, and reading every `capabilities/<id>/spec.md` here to resolve\n // a tag set that is empty would put back the cost the gate exists to remove.\n // The `read` arm is still what travels: the fleet WAS looked at.\n if (scan.views.length === 0) return { kind: \"read\", kept: new Map() };\n const index = await capabilityRequirementIndex(req.vocab, req.read);\n // `index.declared` rather than a second call to `gradableCapabilityIds`: the\n // index applied the ladder when it was built, and two applications of one rule\n // is where the two halves of this join start disagreeing about whether the\n // vocabulary is gradable at all.\n return {\n kind: \"read\",\n kept: useCaseRequirementClaims(scan.views, index.declared, (c) => index.byCapability.get(c)),\n };\n}\n"]}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { withFeatureCapabilities } from "../../capabilities/delta/overlay.js";
|
|
2
|
-
import { gradableCapabilityIds } from "../../capabilities/findings.js";
|
|
2
|
+
import { capabilityRequirementIndex, gradableCapabilityIds } from "../../capabilities/findings.js";
|
|
3
3
|
import { resolveCapabilityTags, resolveRequirementTags } from "../../capabilities/usecase-join.js";
|
|
4
4
|
import { compareIds } from "../../repo/entries.js";
|
|
5
|
+
import { featureFlowIssues } from "../grade/feature.js";
|
|
5
6
|
import { readFeatureFlows } from "./overlay.js";
|
|
6
7
|
/**
|
|
7
8
|
* The promises this feature's own flows keep, read over the post-merge map.
|
|
8
9
|
*
|
|
9
10
|
* A feature with no flow is `read` with nothing kept, which is the ordinary
|
|
10
|
-
* answer and costs one walk over a directory that is not there.
|
|
11
|
+
* answer and costs one walk over a directory that is not there. The scan is
|
|
12
|
+
* read by the caller (`flowCoverage`), which grades the same flows too — one
|
|
13
|
+
* staged parse for both questions, because it is a Langium workspace spin.
|
|
11
14
|
*/
|
|
12
|
-
export
|
|
13
|
-
const scan = await readFeatureFlows(req);
|
|
15
|
+
export function featureFlowPromises(scan, req) {
|
|
14
16
|
if (scan.kind === "unreadable")
|
|
15
17
|
return { kind: "unreadable", errors: scan.errors };
|
|
16
18
|
// The ladder, and it is `ungraded` rather than an empty answer. Once the
|
|
@@ -88,6 +90,14 @@ export async function featureFlowPromises(req) {
|
|
|
88
90
|
* it a short list would resolve a tag that is genuinely ambiguous fleet-wide and
|
|
89
91
|
* attribute a whole flow to the wrong capability, silently. So the vocabulary is
|
|
90
92
|
* the fleet's whole one, read once by the caller.
|
|
93
|
+
*
|
|
94
|
+
* AND THE SAME FLOWS ARE GRADED, against everything as the merge would leave it
|
|
95
|
+
* (`../grade/feature.ts`): the hops against the staged post-merge map, and the
|
|
96
|
+
* tags against the whole requirement corpus — not the narrowed index above,
|
|
97
|
+
* because a `#req-` tag naming a capability this feature does not touch is
|
|
98
|
+
* exactly as broken after the archive as one naming a capability it does.
|
|
99
|
+
* There REMOVED does subtract: a tag naming a promise this feature retires
|
|
100
|
+
* resolves to nothing the moment the archive lands.
|
|
91
101
|
*/
|
|
92
102
|
export async function flowCoverage(req) {
|
|
93
103
|
const vocab = withFeatureCapabilities(req.vocabulary, req.capabilities.map((doc) => doc.id));
|
|
@@ -103,14 +113,28 @@ export async function flowCoverage(req) {
|
|
|
103
113
|
ids.add(r.id);
|
|
104
114
|
byCapability.set(doc.id, ids);
|
|
105
115
|
}
|
|
106
|
-
|
|
116
|
+
const scan = await readFeatureFlows({
|
|
107
117
|
docsDir: req.docsDir,
|
|
108
118
|
featureDir: req.featureDir,
|
|
109
119
|
featureId: req.featureId,
|
|
110
120
|
known: req.known,
|
|
111
|
-
declared: gradableCapabilityIds(vocab),
|
|
112
|
-
requirementsOf: (capability) => byCapability.get(capability),
|
|
113
121
|
...(req.load === undefined ? {} : { load: req.load }),
|
|
122
|
+
...(req.models === undefined ? {} : { models: req.models }),
|
|
114
123
|
});
|
|
124
|
+
const declared = gradableCapabilityIds(vocab);
|
|
125
|
+
const promises = featureFlowPromises(scan, { declared, requirementsOf: (capability) => byCapability.get(capability) });
|
|
126
|
+
if (scan.kind === "unreadable" || scan.views.length === 0)
|
|
127
|
+
return { promises, issues: [] };
|
|
128
|
+
const after = new Map((await capabilityRequirementIndex(req.vocabulary, req.read)).byCapability);
|
|
129
|
+
for (const doc of req.capabilities) {
|
|
130
|
+
const retired = new Set(doc.reqs
|
|
131
|
+
.filter((r) => r.kind === "REMOVED")
|
|
132
|
+
.flatMap((r) => (r.id !== undefined ? [r.id] : doc.living.filter((l) => l.name === r.name).flatMap((l) => (l.id === undefined ? [] : [l.id])))));
|
|
133
|
+
after.set(doc.id, new Set([...(byCapability.get(doc.id) ?? [])].filter((id) => !retired.has(id))));
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
promises,
|
|
137
|
+
issues: featureFlowIssues({ docsDir: req.docsDir, scan, capabilities: declared, requirementsOf: (capability) => after.get(capability) }),
|
|
138
|
+
};
|
|
115
139
|
}
|
|
116
140
|
//# sourceMappingURL=claims.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claims.js","sourceRoot":"","sources":["../../../../src/core/usecases/delta/claims.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which of this feature's own promises its own flows keep.\n *\n * A cross-service criterion — \"I enter a login and a password and I am in\" —\n * belongs to no single service's spec, because each service promises only its\n * own part. A `dynamic view` can carry it, because it IS the hop sequence, and\n * that is the whole reason `#req-` exists. Until `./flows.ts` gave a feature a\n * slot for one, this answer could only ever be empty inside a feature window:\n * the flow was a fleet document, so a tag naming a requirement the same change\n * was adding named something that was not living yet.\n *\n * WHAT THIS FEEDS. `capability.uncovered` — the archive gate that refuses a\n * promise nothing keeps. Its message used to end by telling authors to archive\n * with `--approve` and tag the flow AFTERWARDS, which was honest advice about a\n * hole rather than a workflow anybody wanted: it lands the promise first, kept\n * by nothing, and relies on somebody coming back.\n *\n * THE RULE IS `../capability.ts`'s AND MUST STAY SO. Only a RESOLVED claim\n * counts, and both halves must resolve — the `#cap-` tag to exactly one declared\n * capability, the `#req-` tag to exactly one of its requirements. A broken tag\n * of either kind is already an error (`usecase.capability-unresolved`,\n * `usecase.requirement-unresolved`) and must never also mark a promise kept: a\n * typo that silenced the gate would turn a mistake into a green archive. The two\n * resolvers are called directly rather than through\n * `useCaseRequirementClaims` for one reason, and it is not a difference of\n * opinion about the rule — that function flattens its answer to a `<cap>#<id>`\n * string, and `capabilities/delta/uncovered.ts` keys this same join on NUL\n * precisely because a capability id has no grammar forbidding `#`. Handing it a\n * key it has written down a reason not to use would be the wrong kind of reuse.\n *\n * THE LADDER TRAVELS. `declared === null` — an absent or unreadable\n * `architecture/capabilities.yaml` — is an empty answer, not a suspended one, and\n * the caller is already suppressing the whole family behind it. An `unreadable`\n * overlay is different and must NOT be flattened to \"keeps nothing\": loam did not\n * look, which is never the same answer as \"there is nothing there\", and a gate\n * that refused on it would blame the author for a map it could not read.\n */\nimport type { LoadedDoc } from \"../../c4/likec4.js\";\nimport type { CapabilityVocabulary } from \"../../capabilities/capabilities.js\";\nimport { withFeatureCapabilities } from \"../../capabilities/delta/overlay.js\";\nimport type { CapabilityDeltaDoc } from \"../../capabilities/delta/uncovered.js\";\nimport { gradableCapabilityIds } from \"../../capabilities/findings.js\";\nimport { resolveCapabilityTags, resolveRequirementTags } from \"../../capabilities/usecase-join.js\";\nimport type { DocsDir, FeatureDir } from \"../../kernel/ids/dirs.js\";\nimport { compareIds } from \"../../repo/entries.js\";\nimport { readFeatureFlows, type FlowOverlayRequest } from \"./overlay.js\";\n\n/** One promise this feature's flows keep, with the flows that keep it. */\nexport interface FlowPromise {\n /** The capability the keeping view's `#cap-` tag resolved to. */\n capability: string;\n /** The `Requirement-ID` its `#req-` tag resolved to, inside that capability. */\n requirement: string;\n /** The view ids keeping it, deduplicated and sorted — message text, never identity. */\n flows: readonly string[];\n}\n\n/**\n * What the feature's flows keep, or one of the two honest refusals to say.\n *\n * THREE ARMS, because the two failures are not the same failure and a caller\n * that merged them would say the wrong thing. `unreadable` is loam unable to\n * READ the flows — a parse error, a hop naming an element the merge does not\n * land — and it earns `usecase.flow-invalid` and a refusal. `ungraded` is loam\n * able to read them and unable to RESOLVE their tags, because the capability\n * vocabulary this feature's merge would leave behind does not parse; the flows\n * are fine, the file they point into is not, and refusing over the flows would\n * name the wrong document.\n *\n * `read` with an empty `kept` is the ordinary answer and a real one: loam\n * looked, and no flow of this feature keeps a promise it adds.\n */\nexport type FlowPromises =\n | { kind: \"read\"; kept: readonly FlowPromise[] }\n | { kind: \"ungraded\"; why: string }\n | { kind: \"unreadable\"; errors: string[] };\n\n/** Everything the join needs, on top of what the overlay already asks for. */\nexport interface FlowPromisesRequest extends FlowOverlayRequest {\n /**\n * The capability ids to resolve `#cap-` against — the LIVING vocabulary\n * widened with the ids this feature's own capability deltas introduce, which\n * is the same both-corpora rule `Realizes:` follows\n * (`core/capabilities/delta/overlay.ts`). `null` suspends the axis.\n */\n declared: readonly string[] | null;\n /**\n * The requirement ids per capability, widened the same way\n * (`withFeatureRequirements`). `undefined` for a capability with no document\n * at all is a different answer from an empty set, and both are real here.\n */\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined;\n}\n\n/**\n * The promises this feature's own flows keep, read over the post-merge map.\n *\n * A feature with no flow is `read` with nothing kept, which is the ordinary\n * answer and costs one walk over a directory that is not there.\n */\nexport async function featureFlowPromises(req: FlowPromisesRequest): Promise<FlowPromises> {\n const scan = await readFeatureFlows(req);\n if (scan.kind === \"unreadable\") return { kind: \"unreadable\", errors: scan.errors };\n // The ladder, and it is `ungraded` rather than an empty answer. Once the\n // feature's own capability deltas are unioned in, `declared` is null for\n // exactly one reason — `architecture/capabilities.yaml` exists and does not\n // read as a vocabulary — so no `#cap-` tag can resolve and no claim can be\n // made either way. Returning \"keeps nothing\" here is what let\n // `capability.uncovered` convict a flow that does resolve, over a file the\n // feature gate reports nothing else about.\n if (req.declared === null) {\n return {\n kind: \"ungraded\",\n why: \"architecture/capabilities.yaml does not read as a capability vocabulary (`loam validate --all` reports it as capability.invalid)\",\n };\n }\n // A real, empty answer: loam read the flows and none of them claims anything.\n if (scan.views.length === 0) return { kind: \"read\", kept: [] };\n\n const byPromise = new Map<string, { promise: FlowPromise; flows: Set<string> }>();\n for (const view of scan.views) {\n const scope = resolveCapabilityTags(view.tags, req.declared).flatMap((claim) =>\n claim.kind === \"resolved\" ? [claim.id] : [],\n );\n for (const claim of resolveRequirementTags(view.tags, scope, req.requirementsOf)) {\n if (claim.kind !== \"resolved\") continue;\n // Keyed on the pair, never on a joined string: see the header. The Map is\n // only here to collapse two views keeping one promise into one row.\n const key = JSON.stringify([claim.capability, claim.id]);\n const found = byPromise.get(key);\n if (found === undefined) {\n byPromise.set(key, {\n promise: { capability: claim.capability, requirement: claim.id, flows: [] },\n flows: new Set([view.id]),\n });\n } else {\n found.flows.add(view.id);\n }\n }\n }\n // Sorted, and the view ids inside each row sorted too: nothing in loam has\n // measured that LikeC4 preserves view declaration order, so a message built\n // from it would reorder under a dependency bump.\n const kept = [...byPromise.values()]\n .map(({ promise, flows }) => ({ ...promise, flows: [...flows].sort(compareIds) }))\n .sort((a, b) => compareIds(a.capability, b.capability) || compareIds(a.requirement, b.requirement));\n return { kind: \"read\", kept };\n}\n\n/** Everything the coverage join needs, and nothing it would have to read twice. */\nexport interface FlowCoverageRequest {\n docsDir: DocsDir;\n featureDir: FeatureDir;\n featureId: string;\n /**\n * The feature's capability deltas, ALREADY PARSED by the caller's walk. They\n * arrive rather than being read because the one caller has just produced them,\n * and re-reading a document to answer a question about it is how two answers\n * about one file appear.\n */\n capabilities: readonly CapabilityDeltaDoc[];\n /** The LIVING capability vocabulary, read by the caller through its own index. */\n vocabulary: CapabilityVocabulary;\n /** The enumerated fleet, for `readFeatureFlows`' resolver and for its reason. */\n known: ReadonlySet<string>;\n /** The caller's memoised LikeC4 read; see `./overlay.ts`. */\n load?: (path: string) => Promise<LoadedDoc>;\n}\n\n/**\n * What this feature's own flows keep, resolved against the vocabulary and the\n * requirement ids its own merge would leave behind.\n *\n * THE WIDENING IS THE POINT, and it is `capabilities/delta/overlay.ts`'s\n * judgement applied to the other carrier: an analyst adds `NOTIFY-ONCE` to a\n * capability delta and an architect answers it with a flow in the same change,\n * so a `#req-` tag resolved against the LIVING tree alone names a promise that\n * does not exist yet — and the same archive that would land the flow lands the\n * promise it points at.\n *\n * `requirementsOf` KNOWS ONLY THE CAPABILITIES THIS FEATURE HAS A DELTA FOR, and\n * within those it carries living ∪ ADDED ∪ MODIFIED ids. The narrowing changes\n * no answer, for a reason stronger than \"it is probably fine\":\n * `uncoveredIssues` grades `r.kind === \"ADDED\"` and nothing else, and every\n * ADDED requirement it grades lives in one of these documents by construction —\n * so no id outside this map can reach a verdict. The LIVING half is in there\n * because a widening of the graded set must not silently need a second edit\n * here; today it decides nothing. A flow keeping a promise of a capability this\n * feature does NOT touch resolves to `undocumented` and counts for nothing,\n * which is correct: the fleet-scope `capability.requirement-unrealized` grades\n * that join over the whole corpus, and answering it here from a partial index is\n * how two surfaces start disagreeing.\n *\n * `declared` IS NOT NARROWED, and that asymmetry is load-bearing. It is what\n * `resolveCapabilityTags` compares a `#cap-` slug against, and a slug is not\n * injective — `payments/refunds` and `payments-refunds` flatten to one. Handing\n * it a short list would resolve a tag that is genuinely ambiguous fleet-wide and\n * attribute a whole flow to the wrong capability, silently. So the vocabulary is\n * the fleet's whole one, read once by the caller.\n */\nexport async function flowCoverage(req: FlowCoverageRequest): Promise<FlowPromises> {\n const vocab = withFeatureCapabilities(req.vocabulary, req.capabilities.map((doc) => doc.id));\n const byCapability = new Map<string, ReadonlySet<string>>();\n for (const doc of req.capabilities) {\n const ids = new Set<string>();\n // REMOVED is excluded on both sides — a promise on its way out keeps nothing.\n for (const r of doc.living) if (r.kind !== \"REMOVED\" && r.id !== undefined) ids.add(r.id);\n for (const r of doc.reqs) if ((r.kind === \"ADDED\" || r.kind === \"MODIFIED\") && r.id !== undefined) ids.add(r.id);\n byCapability.set(doc.id, ids);\n }\n return featureFlowPromises({\n docsDir: req.docsDir,\n featureDir: req.featureDir,\n featureId: req.featureId,\n known: req.known,\n declared: gradableCapabilityIds(vocab),\n requirementsOf: (capability) => byCapability.get(capability),\n ...(req.load === undefined ? {} : { load: req.load }),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"claims.js","sourceRoot":"","sources":["../../../../src/core/usecases/delta/claims.ts"],"names":[],"mappings":"AAyCA,OAAO,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAE9E,OAAO,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACnG,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAEnG,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAiDhD;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAiB,EAAE,GAAwB;IAC7E,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACnF,yEAAyE;IACzE,yEAAyE;IACzE,4EAA4E;IAC5E,2EAA2E;IAC3E,8DAA8D;IAC9D,2EAA2E;IAC3E,2CAA2C;IAC3C,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,kIAAkI;SACxI,CAAC;IACJ,CAAC;IACD,8EAA8E;IAC9E,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAE/D,MAAM,SAAS,GAAG,IAAI,GAAG,EAAwD,CAAC;IAClF,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAC7E,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAC5C,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;YACjF,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;gBAAE,SAAS;YACxC,0EAA0E;YAC1E,oEAAoE;YACpE,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YACzD,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE;oBACjB,OAAO,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;oBAC3E,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;iBAC1B,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IACD,2EAA2E;IAC3E,4EAA4E;IAC5E,iDAAiD;IACjD,MAAM,IAAI,GAAG,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;SACjC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;SACjF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IACtG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAChC,CAAC;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAwB;IACzD,MAAM,KAAK,GAAG,uBAAuB,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7F,MAAM,YAAY,GAAG,IAAI,GAAG,EAA+B,CAAC;IAC5D,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;QAC9B,8EAA8E;QAC9E,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM;YAAE,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,EAAE,KAAK,SAAS;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1F,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI;YAAE,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,SAAS;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACjH,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC;QAClC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;QACrD,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;KAC5D,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IACvH,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC3F,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,0BAA0B,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACjG,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,GAAG,CAAC,IAAI;aACL,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC;aACnC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAClJ,CAAC;QACF,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrG,CAAC;IACD,OAAO;QACL,QAAQ;QACR,MAAM,EAAE,iBAAiB,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;KACzI,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Which of this feature's own promises its own flows keep.\n *\n * A cross-service criterion — \"I enter a login and a password and I am in\" —\n * belongs to no single service's spec, because each service promises only its\n * own part. A `dynamic view` can carry it, because it IS the hop sequence, and\n * that is the whole reason `#req-` exists. Until `./flows.ts` gave a feature a\n * slot for one, this answer could only ever be empty inside a feature window:\n * the flow was a fleet document, so a tag naming a requirement the same change\n * was adding named something that was not living yet.\n *\n * WHAT THIS FEEDS. `capability.uncovered` — the archive gate that refuses a\n * promise nothing keeps. Its message used to end by telling authors to archive\n * with `--approve` and tag the flow AFTERWARDS, which was honest advice about a\n * hole rather than a workflow anybody wanted: it lands the promise first, kept\n * by nothing, and relies on somebody coming back.\n *\n * THE RULE IS `../capability.ts`'s AND MUST STAY SO. Only a RESOLVED claim\n * counts, and both halves must resolve — the `#cap-` tag to exactly one declared\n * capability, the `#req-` tag to exactly one of its requirements. A broken tag\n * of either kind is already an error (`usecase.capability-unresolved`,\n * `usecase.requirement-unresolved`) and must never also mark a promise kept: a\n * typo that silenced the gate would turn a mistake into a green archive. The two\n * resolvers are called directly rather than through\n * `useCaseRequirementClaims` for one reason, and it is not a difference of\n * opinion about the rule — that function flattens its answer to a `<cap>#<id>`\n * string, and `capabilities/delta/uncovered.ts` keys this same join on NUL\n * precisely because a capability id has no grammar forbidding `#`. Handing it a\n * key it has written down a reason not to use would be the wrong kind of reuse.\n *\n * THE LADDER TRAVELS. `declared === null` — an absent or unreadable\n * `architecture/capabilities.yaml` — is an empty answer, not a suspended one, and\n * the caller is already suppressing the whole family behind it. An `unreadable`\n * overlay is different and must NOT be flattened to \"keeps nothing\": loam did not\n * look, which is never the same answer as \"there is nothing there\", and a gate\n * that refused on it would blame the author for a map it could not read.\n */\nimport type { LoadedDoc } from \"../../c4/likec4.js\";\nimport type { ExtendingModel } from \"../../c4/splice/contract.js\";\nimport type { CapabilityVocabulary } from \"../../capabilities/capabilities.js\";\nimport type { Requirement } from \"../../document/spec.js\";\nimport { withFeatureCapabilities } from \"../../capabilities/delta/overlay.js\";\nimport type { CapabilityDeltaDoc } from \"../../capabilities/delta/uncovered.js\";\nimport { capabilityRequirementIndex, gradableCapabilityIds } from \"../../capabilities/findings.js\";\nimport { resolveCapabilityTags, resolveRequirementTags } from \"../../capabilities/usecase-join.js\";\nimport type { DocsDir, FeatureDir } from \"../../kernel/ids/dirs.js\";\nimport { compareIds } from \"../../repo/entries.js\";\nimport type { Issue } from \"../../vocabulary/issue.js\";\nimport type { UseCaseScan } from \"../fleet.js\";\nimport { featureFlowIssues } from \"../grade/feature.js\";\nimport { readFeatureFlows } from \"./overlay.js\";\n\n/** One promise this feature's flows keep, with the flows that keep it. */\nexport interface FlowPromise {\n /** The capability the keeping view's `#cap-` tag resolved to. */\n capability: string;\n /** The `Requirement-ID` its `#req-` tag resolved to, inside that capability. */\n requirement: string;\n /** The view ids keeping it, deduplicated and sorted — message text, never identity. */\n flows: readonly string[];\n}\n\n/**\n * What the feature's flows keep, or one of the two honest refusals to say.\n *\n * THREE ARMS, because the two failures are not the same failure and a caller\n * that merged them would say the wrong thing. `unreadable` is loam unable to\n * READ the flows — a parse error, a hop naming an element the merge does not\n * land — and it earns `usecase.flow-invalid` and a refusal. `ungraded` is loam\n * able to read them and unable to RESOLVE their tags, because the capability\n * vocabulary this feature's merge would leave behind does not parse; the flows\n * are fine, the file they point into is not, and refusing over the flows would\n * name the wrong document.\n *\n * `read` with an empty `kept` is the ordinary answer and a real one: loam\n * looked, and no flow of this feature keeps a promise it adds.\n */\nexport type FlowPromises =\n | { kind: \"read\"; kept: readonly FlowPromise[] }\n | { kind: \"ungraded\"; why: string }\n | { kind: \"unreadable\"; errors: string[] };\n\n/** Everything the join needs beside the flows themselves. */\nexport interface FlowPromisesRequest {\n /**\n * The capability ids to resolve `#cap-` against — the LIVING vocabulary\n * widened with the ids this feature's own capability deltas introduce, which\n * is the same both-corpora rule `Realizes:` follows\n * (`core/capabilities/delta/overlay.ts`). `null` suspends the axis.\n */\n declared: readonly string[] | null;\n /**\n * The requirement ids per capability, widened the same way\n * (`withFeatureRequirements`). `undefined` for a capability with no document\n * at all is a different answer from an empty set, and both are real here.\n */\n requirementsOf: (capability: string) => ReadonlySet<string> | undefined;\n}\n\n/**\n * The promises this feature's own flows keep, read over the post-merge map.\n *\n * A feature with no flow is `read` with nothing kept, which is the ordinary\n * answer and costs one walk over a directory that is not there. The scan is\n * read by the caller (`flowCoverage`), which grades the same flows too — one\n * staged parse for both questions, because it is a Langium workspace spin.\n */\nexport function featureFlowPromises(scan: UseCaseScan, req: FlowPromisesRequest): FlowPromises {\n if (scan.kind === \"unreadable\") return { kind: \"unreadable\", errors: scan.errors };\n // The ladder, and it is `ungraded` rather than an empty answer. Once the\n // feature's own capability deltas are unioned in, `declared` is null for\n // exactly one reason — `architecture/capabilities.yaml` exists and does not\n // read as a vocabulary — so no `#cap-` tag can resolve and no claim can be\n // made either way. Returning \"keeps nothing\" here is what let\n // `capability.uncovered` convict a flow that does resolve, over a file the\n // feature gate reports nothing else about.\n if (req.declared === null) {\n return {\n kind: \"ungraded\",\n why: \"architecture/capabilities.yaml does not read as a capability vocabulary (`loam validate --all` reports it as capability.invalid)\",\n };\n }\n // A real, empty answer: loam read the flows and none of them claims anything.\n if (scan.views.length === 0) return { kind: \"read\", kept: [] };\n\n const byPromise = new Map<string, { promise: FlowPromise; flows: Set<string> }>();\n for (const view of scan.views) {\n const scope = resolveCapabilityTags(view.tags, req.declared).flatMap((claim) =>\n claim.kind === \"resolved\" ? [claim.id] : [],\n );\n for (const claim of resolveRequirementTags(view.tags, scope, req.requirementsOf)) {\n if (claim.kind !== \"resolved\") continue;\n // Keyed on the pair, never on a joined string: see the header. The Map is\n // only here to collapse two views keeping one promise into one row.\n const key = JSON.stringify([claim.capability, claim.id]);\n const found = byPromise.get(key);\n if (found === undefined) {\n byPromise.set(key, {\n promise: { capability: claim.capability, requirement: claim.id, flows: [] },\n flows: new Set([view.id]),\n });\n } else {\n found.flows.add(view.id);\n }\n }\n }\n // Sorted, and the view ids inside each row sorted too: nothing in loam has\n // measured that LikeC4 preserves view declaration order, so a message built\n // from it would reorder under a dependency bump.\n const kept = [...byPromise.values()]\n .map(({ promise, flows }) => ({ ...promise, flows: [...flows].sort(compareIds) }))\n .sort((a, b) => compareIds(a.capability, b.capability) || compareIds(a.requirement, b.requirement));\n return { kind: \"read\", kept };\n}\n\n/** Everything the coverage join needs, and nothing it would have to read twice. */\nexport interface FlowCoverageRequest {\n docsDir: DocsDir;\n featureDir: FeatureDir;\n featureId: string;\n /**\n * The feature's capability deltas, ALREADY PARSED by the caller's walk. They\n * arrive rather than being read because the one caller has just produced them,\n * and re-reading a document to answer a question about it is how two answers\n * about one file appear.\n */\n capabilities: readonly CapabilityDeltaDoc[];\n /** The LIVING capability vocabulary, read by the caller through its own index. */\n vocabulary: CapabilityVocabulary;\n /** The enumerated fleet, for `readFeatureFlows`' resolver and for its reason. */\n known: ReadonlySet<string>;\n /** The caller's memoised LikeC4 read; see `./overlay.ts`. */\n load?: (path: string) => Promise<LoadedDoc>;\n /**\n * How a LIVING capability document's requirements are read — the caller's\n * memo where it has one. Asked only for the grade below, and only once a\n * feature really brings a flow: a `#req-` tag may name ANY capability, so\n * the post-merge index it is resolved in is the whole corpus.\n */\n read: (path: string) => Promise<Requirement[]>;\n /**\n * The fleet's extending models for the merge preview, as a thunk the overlay\n * calls only when this feature really brings a flow; see `./overlay.ts` for\n * why the preview is wrong without them and why it arrives as a function.\n */\n models?: () => Promise<readonly ExtendingModel[]>;\n}\n\n/**\n * What this feature's own flows keep, resolved against the vocabulary and the\n * requirement ids its own merge would leave behind.\n *\n * THE WIDENING IS THE POINT, and it is `capabilities/delta/overlay.ts`'s\n * judgement applied to the other carrier: an analyst adds `NOTIFY-ONCE` to a\n * capability delta and an architect answers it with a flow in the same change,\n * so a `#req-` tag resolved against the LIVING tree alone names a promise that\n * does not exist yet — and the same archive that would land the flow lands the\n * promise it points at.\n *\n * `requirementsOf` KNOWS ONLY THE CAPABILITIES THIS FEATURE HAS A DELTA FOR, and\n * within those it carries living ∪ ADDED ∪ MODIFIED ids. The narrowing changes\n * no answer, for a reason stronger than \"it is probably fine\":\n * `uncoveredIssues` grades `r.kind === \"ADDED\"` and nothing else, and every\n * ADDED requirement it grades lives in one of these documents by construction —\n * so no id outside this map can reach a verdict. The LIVING half is in there\n * because a widening of the graded set must not silently need a second edit\n * here; today it decides nothing. A flow keeping a promise of a capability this\n * feature does NOT touch resolves to `undocumented` and counts for nothing,\n * which is correct: the fleet-scope `capability.requirement-unrealized` grades\n * that join over the whole corpus, and answering it here from a partial index is\n * how two surfaces start disagreeing.\n *\n * `declared` IS NOT NARROWED, and that asymmetry is load-bearing. It is what\n * `resolveCapabilityTags` compares a `#cap-` slug against, and a slug is not\n * injective — `payments/refunds` and `payments-refunds` flatten to one. Handing\n * it a short list would resolve a tag that is genuinely ambiguous fleet-wide and\n * attribute a whole flow to the wrong capability, silently. So the vocabulary is\n * the fleet's whole one, read once by the caller.\n *\n * AND THE SAME FLOWS ARE GRADED, against everything as the merge would leave it\n * (`../grade/feature.ts`): the hops against the staged post-merge map, and the\n * tags against the whole requirement corpus — not the narrowed index above,\n * because a `#req-` tag naming a capability this feature does not touch is\n * exactly as broken after the archive as one naming a capability it does.\n * There REMOVED does subtract: a tag naming a promise this feature retires\n * resolves to nothing the moment the archive lands.\n */\nexport async function flowCoverage(req: FlowCoverageRequest): Promise<{ promises: FlowPromises; issues: Issue[] }> {\n const vocab = withFeatureCapabilities(req.vocabulary, req.capabilities.map((doc) => doc.id));\n const byCapability = new Map<string, ReadonlySet<string>>();\n for (const doc of req.capabilities) {\n const ids = new Set<string>();\n // REMOVED is excluded on both sides — a promise on its way out keeps nothing.\n for (const r of doc.living) if (r.kind !== \"REMOVED\" && r.id !== undefined) ids.add(r.id);\n for (const r of doc.reqs) if ((r.kind === \"ADDED\" || r.kind === \"MODIFIED\") && r.id !== undefined) ids.add(r.id);\n byCapability.set(doc.id, ids);\n }\n const scan = await readFeatureFlows({\n docsDir: req.docsDir,\n featureDir: req.featureDir,\n featureId: req.featureId,\n known: req.known,\n ...(req.load === undefined ? {} : { load: req.load }),\n ...(req.models === undefined ? {} : { models: req.models }),\n });\n const declared = gradableCapabilityIds(vocab);\n const promises = featureFlowPromises(scan, { declared, requirementsOf: (capability) => byCapability.get(capability) });\n if (scan.kind === \"unreadable\" || scan.views.length === 0) return { promises, issues: [] };\n const after = new Map((await capabilityRequirementIndex(req.vocabulary, req.read)).byCapability);\n for (const doc of req.capabilities) {\n const retired = new Set(\n doc.reqs\n .filter((r) => r.kind === \"REMOVED\")\n .flatMap((r) => (r.id !== undefined ? [r.id] : doc.living.filter((l) => l.name === r.name).flatMap((l) => (l.id === undefined ? [] : [l.id])))),\n );\n after.set(doc.id, new Set([...(byCapability.get(doc.id) ?? [])].filter((id) => !retired.has(id))));\n }\n return {\n promises,\n issues: featureFlowIssues({ docsDir: req.docsDir, scan, capabilities: declared, requirementsOf: (capability) => after.get(capability) }),\n };\n}\n"]}
|
|
@@ -33,15 +33,16 @@
|
|
|
33
33
|
* `core/c4/splice/delta-blocks.ts` goes on refusing a `dynamic view` inside
|
|
34
34
|
* `delta.likec4` and now names this slot as the place it belongs.
|
|
35
35
|
*
|
|
36
|
-
* ONE CALL for the walk
|
|
36
|
+
* ONE CALL for the walk (`relativeDocuments`, which the topology axis in
|
|
37
|
+
* `core/deployment/delta.ts` shares). `architectureDocuments` already implements "every
|
|
37
38
|
* `.likec4` under a directory, recursively, in a stable order, and an absent
|
|
38
39
|
* directory is an empty list" — which is the whole of what a feature's flow
|
|
39
40
|
* directory is. A second walk would be two implementations free to disagree
|
|
40
41
|
* about what a flow file is, and they would disagree first about nesting.
|
|
41
42
|
*/
|
|
42
43
|
import { existsSync } from "node:fs";
|
|
43
|
-
import { join
|
|
44
|
-
import {
|
|
44
|
+
import { join } from "node:path";
|
|
45
|
+
import { relativeDocuments } from "../../c4/project/documents.js";
|
|
45
46
|
import { architectureDir } from "../../c4/project/architecture.js";
|
|
46
47
|
import { featureUseCasesDir } from "../../repo/authored/paths.js";
|
|
47
48
|
import {} from "../../vocabulary/issue.js";
|
|
@@ -55,10 +56,7 @@ export const USECASE_SUBDIR = "usecases";
|
|
|
55
56
|
* answers an absent directory with an empty list rather than a throw.
|
|
56
57
|
*/
|
|
57
58
|
export async function featureFlows(featureDir) {
|
|
58
|
-
|
|
59
|
-
const paths = await architectureDocuments(dir);
|
|
60
|
-
const base = resolve(dir);
|
|
61
|
-
return paths.map((path) => ({ rel: relative(base, path).split(/[\\/]/).join("/"), path }));
|
|
59
|
+
return relativeDocuments(featureUseCasesDir(featureDir));
|
|
62
60
|
}
|
|
63
61
|
/**
|
|
64
62
|
* The LIVING path a feature-local flow addresses. Spelled here because three
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flows.js","sourceRoot":"","sources":["../../../../src/core/usecases/delta/flows.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"flows.js","sourceRoot":"","sources":["../../../../src/core/usecases/delta/flows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,EAAc,MAAM,2BAA2B,CAAC;AAEvD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AAezC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAAsB;IACvD,OAAO,iBAAiB,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgB,EAAE,GAAW;IAC1D,OAAO,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAgB,EAAE,UAAsB;IAC5E,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,CAAC;IAC7C,OAAO,KAAK;SACT,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;SAC/D,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,QAAQ,EAAE,OAAgB;QAC1B,IAAI,EAAE,qBAA8B;QACpC,OAAO,EAAE,IAAI,CAAC,GAAG;QACjB,OAAO,EACL,gBAAgB,cAAc,IAAI,IAAI,CAAC,GAAG,oFAAoF;YAC9H,uEAAuE;YACvE,0BAA0B,cAAc,IAAI,IAAI,CAAC,GAAG,0BAA0B,cAAc,IAAI,IAAI,CAAC,GAAG,iCAAiC;YACzI,6EAA6E;KAChF,CAAC,CAAC,CAAC;AACR,CAAC","sourcesContent":["/**\n * A feature's own flows: `features/<FEAT>/usecases/<name>.likec4`.\n *\n * A `dynamic view` is the only realizer a cross-service promise has, and until\n * this slot existed it was the one realizer no feature could carry. Every other\n * axis had a delta — a C4 delta, a requirement delta on both spec axes, both\n * contract deltas, a capability delta, a glossary term — so the business axis's\n * own headline case had to be written backwards: an analyst adds a capability\n * requirement, the architect answers it with a flow, and the flow could not ship\n * in that change. It merged only after the promise was already living, which\n * meant the promise landed first with nothing keeping it.\n *\n * CREATE-ONLY, for `core/glossary/delta.ts`'s reasons rather than a weaker\n * version of them. A capability document is a DELTA because it merges INTO a\n * living document and two features editing one requirement would otherwise\n * overwrite each other silently. A flow has none of that: the file is one\n * ordered hop sequence, the merge is a whole-file copy, and there is nothing to\n * merge partially. Rewriting a living flow through a feature would be a silent\n * whole-file replacement with no pin to collide on, where editing\n * `architecture/usecases/<name>.likec4` directly in the same pull request\n * produces an ordinary git conflict a human resolves. `usecase.flow-exists`\n * refuses the first and names the second — the same shape, and the same\n * severity, as `glossary.term-exists`.\n *\n * WHY A FILE OF ITS OWN AND NOT `delta.likec4`. The C4 delta is a MODEL delta:\n * it re-declares the landscape's own identifiers to anchor its new edges, and it\n * carries its own `specification` block because LikeC4 parses it standalone.\n * Both make it unstageable beside the landscape in one project — a duplicate\n * specification is an error blamed on both files, and a re-declared element is a\n * duplicate id. A views-only document declares neither, which is exactly why\n * living use cases already live in files of their own; the feature-local slot\n * mirrors that layout so the merge is a copy and the overlay is a stage.\n * `core/c4/splice/delta-blocks.ts` goes on refusing a `dynamic view` inside\n * `delta.likec4` and now names this slot as the place it belongs.\n *\n * ONE CALL for the walk (`relativeDocuments`, which the topology axis in\n * `core/deployment/delta.ts` shares). `architectureDocuments` already implements \"every\n * `.likec4` under a directory, recursively, in a stable order, and an absent\n * directory is an empty list\" — which is the whole of what a feature's flow\n * directory is. A second walk would be two implementations free to disagree\n * about what a flow file is, and they would disagree first about nesting.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { relativeDocuments } from \"../../c4/project/documents.js\";\nimport { architectureDir } from \"../../c4/project/architecture.js\";\nimport { featureUseCasesDir } from \"../../repo/authored/paths.js\";\nimport type { DocsDir, FeatureDir } from \"../../kernel/ids/dirs.js\";\nimport { type Issue } from \"../../vocabulary/issue.js\";\n\n/** The directory living flows are filed under, inside `architecture/`. */\nexport const USECASE_SUBDIR = \"usecases\";\n\n/** One flow document a feature carries. */\nexport interface FeatureFlow {\n /**\n * The path INSIDE the feature's `usecases/` directory, `/`-separated — the id\n * this axis has, and the same path the merge writes under\n * `architecture/usecases/`. Nesting is spelled by the tree, as it is for every\n * other authored tree in the repo.\n */\n rel: string;\n /** The absolute path of the authored file. */\n path: string;\n}\n\n/**\n * The flows one feature carries, ordered by path.\n *\n * Zero readdirs when the feature has no `usecases/` directory, which is every\n * feature in a fleet that has not adopted the axis: `architectureDocuments`\n * answers an absent directory with an empty list rather than a throw.\n */\nexport async function featureFlows(featureDir: FeatureDir): Promise<FeatureFlow[]> {\n return relativeDocuments(featureUseCasesDir(featureDir));\n}\n\n/**\n * The LIVING path a feature-local flow addresses. Spelled here because three\n * readers need it — the refusal below, the archive merge, and the overlay that\n * grades the flow before either — and a reader resolving it differently would\n * refuse a flow the merge then wrote anyway.\n *\n * `rel` is joined un-split, which `node:path` normalizes. What holds the join is\n * PROVENANCE, exactly as it holds `livingTermPath` one axis over: every `rel`\n * here came out of an `architectureDocuments` walk, so each component is a\n * directory entry that exists. No caller takes one from argv.\n */\nexport function livingFlowPath(docsDir: DocsDir, rel: string): string {\n return join(architectureDir(docsDir), USECASE_SUBDIR, rel);\n}\n\n/**\n * `usecase.flow-exists` — a flow this feature introduces that the living\n * `architecture/` already holds.\n *\n * An ERROR and never a warning-that-gates, for `glossary.term-exists`'s reason:\n * there is no legal reading of it. The merge would replace an authored flow\n * wholesale, and no `--approve` makes that a different act than it is. Two\n * features introducing the same flow is the case that makes it load-bearing\n * rather than pedantic — whichever archives first creates the living document,\n * and the second is told before it merges that its own file is the one to\n * delete.\n */\nexport async function flowDeltaIssues(docsDir: DocsDir, featureDir: FeatureDir): Promise<Issue[]> {\n const flows = await featureFlows(featureDir);\n return flows\n .filter((flow) => existsSync(livingFlowPath(docsDir, flow.rel)))\n .map((flow) => ({\n severity: \"error\" as const,\n code: \"usecase.flow-exists\" as const,\n subject: flow.rel,\n message:\n `architecture/${USECASE_SUBDIR}/${flow.rel} already exists, so this feature's flow would replace an authored one wholesale — ` +\n \"a feature-local flow INTRODUCES a use case, it does not rewrite one. \" +\n `Delete features/<FEAT>/${USECASE_SUBDIR}/${flow.rel} and edit architecture/${USECASE_SUBDIR}/${flow.rel} directly in this same change, ` +\n \"where git produces an ordinary conflict if somebody else is editing it too.\",\n }));\n}\n"]}
|