@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../../../src/commands/validate/service/events/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAIN,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAkB,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAqB,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAgB,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAoB,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAyB1D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAe;IACrD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,GAC9F,IAAI,CAAC;IACP,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,0EAA0E;IAC1E,2EAA2E;IAC3E,gFAAgF;IAChF,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC;IACxC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE9C,yEAAyE;IACzE,6EAA6E;IAC7E,8EAA8E;IAC9E,gFAAgF;IAChF,MAAM,UAAU,GACd,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QACrC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YAC/B,MAAM,KAAK,GAA8E,EAAE,CAAC;YAC5F,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,IAAI,SAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;gBAC7D,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,SAAU,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC5F,CAAC;YACD,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,SAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;gBAC5D,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAU,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1F,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACT,yEAAyE;IACzE,2EAA2E;IAC3E,MAAM,UAAU,GAAG,CAAC,GAAG,UAAU,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACjG,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,WAAoB,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACjG,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,UAAmB,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;KAChG,CAAC,CAAC;IAEH,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,wEAAwE;QACxE,wEAAwE;QACxE,0EAA0E;QAC1E,qEAAqE;QACrE,2EAA2E;QAC3E,sEAAsE;QACtE,4DAA4D;QAC5D,EAAE;QACF,gEAAgE;QAChE,MAAM,QAAQ,GAAG;YACf,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;SACvF,CAAC,IAAI,EAAE,CAAC;QACT,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,qBAAqB;gBAC3B,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,wBAAwB,KAAK,CAAC,QAAQ,SAAS,QAAQ,CAAC,MAAM,2CAA2C;oBACzG,oGAAoG;gBACtG,OAAO,EAAE,QAAQ;aAClB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC7B,2EAA2E;QAC3E,2EAA2E;QAC3E,8EAA8E;QAC9E,yDAAyD;QACzD,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,kBAAkB;YACxB,OAAO,EAAE,OAAO;YAChB,OAAO,EAAE,GAAG,OAAO,gFAAgF;YACnG,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;SACnE,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,uEAAuE;QACvE,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,+BAA+B;gBACrC,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,GAAG,OAAO,yDAAyD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,8GAA8G;aACpN,CAAC,CAAC;QACL,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,4BAA4B;gBAClC,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,GAAG,OAAO,qCAAqC,IAAI,QAAQ,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,oIAAoI;aAC5O,CAAC,CAAC;QACL,CAAC;QACD,yEAAyE;QACzE,yEAAyE;QACzE,0EAA0E;QAC1E,iEAAiE;QACjE,+CAA+C;QAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1F,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,8BAA8B;gBACpC,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,KAAK,OAAO,CAAC,MAAM,yCAAyC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;oBAC9F,4EAA4E;oBAC5E,6DAA6D;aAChE,CAAC,CAAC;QACL,CAAC;QACD,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnC,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,yBAAyB;gBAC/B,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,4BAA4B,MAAM,CAAC,YAAY,CAAC,MAAM,iCAAiC;oBACjG,6FAA6F;oBAC7F,+BAA+B;gBACjC,OAAO,EAAE,MAAM,CAAC,YAAY;aAC7B,CAAC,CAAC;QACL,CAAC;QACD,6EAA6E;QAC7E,yEAAyE;QACzE,yDAAyD;QACzD,sEAAsE;QACtE,6EAA6E;QAC7E,MAAM,QAAQ,GAAG,CAAC,SAAmC,EAAE,OAAe,EAAW,EAAE,CACjF,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAChF,MAAM,MAAM,GAAG,CAAC,SAAmC,EAAU,EAAE,CAC7D,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;QACjD,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,IAAI,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC;gBAAE,SAAS;YAC/C,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,yBAAyB;gBAC/B,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,oBAAoB,CAAC,CAAC,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM,OAAO,EAAE,GAAG;oBACpH,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,OAAO,UAAU,OAAO,uDAAuD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS;aACnI,CAAC,CAAC;QACL,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,IAAI,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC;gBAAE,SAAS;YAC/C,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,8BAA8B;gBACpC,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,GAAG,OAAO,kBAAkB,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,OAAO,UAAU,OAAO,uDAAuD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS;aACrL,CAAC,CAAC;QACL,CAAC;QACD,qEAAqE;QACrE,2EAA2E;QAC3E,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/F,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,yBAAyB;gBAC/B,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,GAAG,OAAO,4BAA4B,MAAM,CAAC,QAAQ,CAAC,MAAM,yFAAyF;aAC/J,CAAC,CAAC;QACL,CAAC;QACD,0EAA0E;QAC1E,0EAA0E;QAC1E,oEAAoE;QACpE,wEAAwE;QACxE,yEAAyE;QACzE,uEAAuE;QACvE,+CAA+C;QAC/C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QACvG,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,kBAAkB;gBACxB,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC,MAAM,6EAA6E,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aACxI,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,yEAAyE;IACzE,MAAM,QAAQ,GAAG;QACf,GAAG,IAAI,GAAG,CAAC;YACT,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YAC7E,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;SAC9E,CAAC;KACH,CAAC,IAAI,EAAE,CAAC;IACT,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,gBAAgB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAElH,4EAA4E;IAC5E,4EAA4E;IAC5E,+EAA+E;IAC/E,6CAA6C;IAC7C,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC1D,IAAI,WAAW,GAAG,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;QAC3D,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,GAAG,OAAO,kBAAkB,WAAW,4CAA4C;SAC7F,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["/**\n * The async contract axis — AsyncAPI messages on the landscape spine.\n *\n * The HTTP spine (`./spine.js`) and this one are mirror images, and the mirror\n * is the whole reason this is a second axis rather than a second `metadata`\n * key. On the HTTP axis the PROVIDER owns the contract: an edge names an\n * operation and the target's openapi.yaml settles it, locally, in one file.\n * Here the PRODUCER owns the message and the consumer joins to it from another\n * repository — so \"is this message declared\" is local, while \"does anybody\n * publish it\" is a fleet question with no local answer at all. That second\n * question is the only check on either spine that reads other services'\n * contracts; it lives in `./message-producers.js`, and this axis only decides\n * when it is worth paying for — exactly when something actually consumes.\n */\nimport { existsSync } from \"node:fs\";\nimport {\n type DeclaredService,\n type PathableService,\n type RawServiceId,\n} from \"../../../../core/kernel/ids/service.js\";\nimport { type LoadedDoc } from \"../../../../core/c4/likec4.js\";\nimport { type ServicePaths } from \"../../../../core/repo/paths.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\nimport { type Requirement } from \"../../../../core/document/spec.js\";\nimport { readAsyncapi, slotsOf } from \"../../../../core/asyncapi/read.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport { producerFindings } from \"./message-producers.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\n\n/** What the service target hands the event axis. */\nexport interface EventAxis {\n docsDir: DocsDir;\n service: PathableService;\n /** `service` widened for `===` against the resolver's DOCUMENT text — see service.ts. */\n me: string;\n paths: ServicePaths;\n /** Every requirement the living spec declares; `event.messages-unlinked` counts what is written. */\n reqs: Requirement[];\n /** The ones that still govern something — a REMOVED requirement links nothing. */\n livingReqs: Requirement[];\n /** arch.spec.md's requirements, which carry as many event links as spec.md's do. */\n archReqs: Requirement[];\n /** The living landscape, or null when the repo has none. */\n land: LoadedDoc | null;\n /** Its element→service resolver; null exactly when `land` is. */\n landSvcOf: ((id: string) => DeclaredService) | null;\n /** Service directories that exist — the fleet the producer question is asked over. */\n known: ReadonlySet<RawServiceId>;\n fleet?: FleetContext;\n}\n\nexport async function eventAxisFindings(axis: EventAxis): Promise<Finding[]> {\n const { docsDir, service, me, paths, reqs, livingReqs, archReqs, land, landSvcOf, known, fleet } =\n axis;\n const findings: Finding[] = [];\n\n // Where this axis started, so the closing ok-finding can be \"nothing here\n // broke\" rather than a second, independently-computed claim about the same\n // facts. A positive finding derived from the same walk cannot disagree with it.\n const eventFindingsAt = findings.length;\n const events = await readAsyncapi(paths.asyncapi, fleet);\n const sentHere = new Set(events.sent);\n const receivedHere = new Set(events.received);\n\n // Every edge that binds THIS service to a message: `publishes` binds the\n // edge's source (a service produces what it declares an `action: send` for),\n // `consumes` binds its target. One edge may legitimately carry both — a relay\n // that reads one topic and writes another is a single arrow in most fleet maps.\n const eventEdges =\n land === null || land.errors.length > 0\n ? []\n : land.relationships.flatMap((r) => {\n const bound: { direction: \"publishes\" | \"consumes\"; message: string; other: string }[] = [];\n if (r.publishes !== undefined && landSvcOf!(r.source) === me) {\n bound.push({ direction: \"publishes\", message: r.publishes, other: landSvcOf!(r.target) });\n }\n if (r.consumes !== undefined && landSvcOf!(r.target) === me) {\n bound.push({ direction: \"consumes\", message: r.consumes, other: landSvcOf!(r.source) });\n }\n return bound;\n });\n // Both requirement namespaces, and a REMOVED requirement links nothing —\n // the `livingReqs` rule the API axis already follows, applied to the pair.\n const eventLinks = [...livingReqs, ...archReqs.filter((r) => r.kind !== \"REMOVED\")].flatMap((r) => [\n ...r.publishes.map((m) => ({ direction: \"publishes\" as const, message: m, requirement: r.name })),\n ...r.consumes.map((m) => ({ direction: \"consumes\" as const, message: m, requirement: r.name })),\n ]);\n\n if (!existsSync(paths.asyncapi)) {\n // Deliberately TWO grades, not the three `service.no-openapi` uses. Its\n // middle grade — warn when the landscape cannot prove nobody calls this\n // service — rests on HTTP being the default: most services expose one, so\n // \"I could not look\" is worth saying. An event contract is genuinely\n // optional; most services in a legacy fleet touch no topic at all. Warning\n // every one of them the moment a landscape fails to parse would put a\n // finding on the whole fleet that names a file nobody owes.\n //\n // So: something already joins into the absent file, or silence.\n const dangling = [\n ...new Set([...eventEdges.map((e) => e.message), ...eventLinks.map((e) => e.message)]),\n ].sort();\n if (dangling.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"service.no-asyncapi\",\n subject: service,\n message:\n `No async contract at ${paths.asyncapi}, and ${dangling.length} message link(s) already point into it — ` +\n `every landscape edge and requirement naming one of them resolves to nothing until the file is back`,\n details: dangling,\n });\n }\n } else if (events.unreadable) {\n // The openapi.invalid discipline: a contract that EXISTS but does not read\n // is a broken source of truth, not an empty one. Grading the links against\n // an empty message set would turn one file into one `spine.message-undefined`\n // per edge, every one of them pointing at the landscape.\n findings.push({\n severity: \"error\",\n code: \"asyncapi.invalid\",\n subject: service,\n message: `${service}: asyncapi.yaml does not parse — the event spine is unchecked for this service`,\n ...(events.error === undefined ? {} : { details: [events.error] }),\n });\n } else {\n // Feature-only bookkeeping published to the fleet — the openapi axis's\n // `openapi.remove-marker-living` discipline, one code for every depth the\n // reader collects markers at (slots and inline channel messages alike).\n if (events.markers.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"asyncapi.remove-marker-living\",\n subject: service,\n message: `${service}: living asyncapi.yaml carries x-loam-remove: true at ${events.markers.join(\", \")} — removal markers are valid only in feature deltas; retire a slot through a feature's asyncapi.yaml instead`,\n });\n }\n for (const name of events.duplicateNames) {\n findings.push({\n severity: \"warn\",\n code: \"asyncapi.duplicate-message\",\n subject: service,\n message: `${service}: asyncapi.yaml declares message '${name}' in ${slotsOf(events, name).join(\" and \")} — every join on the name (an edge's metadata { publishes }, a requirement's Publishes: line) picks one of those slots arbitrarily`,\n });\n }\n // The contract-depth probes. Form validated and depth did not: a payload\n // of bare `type: object` and a `$ref` to nothing both read as cleanly as\n // a full schema, while a reader still cannot rebuild the payload from the\n // green contract. Presence probes only — a non-JSON schemaFormat\n // is skipped, so Avro stays a document change.\n const empties = events.messages.filter((m) => m.payloadEmpty === true).map((m) => m.name);\n if (empties.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"asyncapi.payload-undescribed\",\n subject: service,\n message:\n `${service}: ${empties.length} message(s) declare no payload shape (${empties.join(\", \")}) — ` +\n `the name joins the spine, but nothing defines what a consumer would read; ` +\n `declare properties, or a schemaFormat for a non-JSON schema`,\n });\n }\n if (events.danglingRefs.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"asyncapi.ref-unresolved\",\n subject: service,\n message:\n `${service}: asyncapi.yaml contains ${events.danglingRefs.length} internal $ref(s) that resolve ` +\n `to nothing in the document — whatever they were meant to carry is silently absent from the ` +\n `spine's view of this contract`,\n details: events.danglingRefs,\n });\n }\n // Local resolution, both directions and both sources of a claim. An edge and\n // a requirement making the same broken claim get the same sentence under\n // different codes, matching how `spine.op-undefined` and\n // `spec-api.op-undefined` split the HTTP axis: one is the fleet map's\n // mistake, the other is this document's, and the fix is in a different file.\n const declares = (direction: \"publishes\" | \"consumes\", message: string): boolean =>\n direction === \"publishes\" ? sentHere.has(message) : receivedHere.has(message);\n const action = (direction: \"publishes\" | \"consumes\"): string =>\n direction === \"publishes\" ? \"send\" : \"receive\";\n for (const e of eventEdges) {\n if (declares(e.direction, e.message)) continue;\n findings.push({\n severity: \"error\",\n code: \"spine.message-undefined\",\n subject: service,\n message:\n `${service}: landscape edge ${e.direction === \"publishes\" ? `${service} → ${e.other}` : `${e.other} → ${service}`} ` +\n `${e.direction} '${e.message}', but ${service}'s asyncapi.yaml declares no operation with action: ${action(e.direction)} for it`,\n });\n }\n for (const e of eventLinks) {\n if (declares(e.direction, e.message)) continue;\n findings.push({\n severity: \"error\",\n code: \"spec-event.message-undefined\",\n subject: service,\n message: `${service}: requirement '${e.requirement}' ${e.direction} '${e.message}', but ${service}'s asyncapi.yaml declares no operation with action: ${action(e.direction)} for it`,\n });\n }\n // The migration-debt case, `api.ops-unlinked`'s twin: a contract and\n // requirements that never name each other leave this axis vacuously green.\n if (events.messages.length > 0 && reqs.length + archReqs.length > 0 && eventLinks.length === 0) {\n findings.push({\n severity: \"warn\",\n code: \"event.messages-unlinked\",\n subject: service,\n message: `${service}: asyncapi.yaml declares ${events.messages.length} message(s) but no requirement links any — the event axis is unchecked for this service`,\n });\n }\n // `api.ungoverned`'s event twin, and the per-message half the check above\n // cannot see: `event.messages-unlinked` fires only when ZERO links exist,\n // so a contract with five declared messages and three governed ones\n // reported nothing about the other two. The join is against requirement\n // lines ONLY, deliberately ignoring landscape edges — an edge is a claim\n // about traffic, a requirement is governance — exactly as the API axis\n // ignores inbound edges when grading coverage.\n const governed = new Set(eventLinks.map((e) => e.message));\n const orphans = [...new Set(events.messages.map((m) => m.name))].filter((name) => !governed.has(name));\n if (orphans.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"event.ungoverned\",\n subject: service,\n message: `${service}: ${orphans.length} message(s) not governed by any requirement's Publishes:/Consumes: line — ${orphans.join(\", \")}`,\n });\n }\n }\n\n // The consumed-message list is the handover to the fleet question in\n // ./message-producers.js — a fleet where nothing consumes walks nothing.\n const consumed = [\n ...new Set([\n ...eventEdges.filter((e) => e.direction === \"consumes\").map((e) => e.message),\n ...eventLinks.filter((e) => e.direction === \"consumes\").map((e) => e.message),\n ]),\n ].sort();\n findings.push(...(await producerFindings({ docsDir, service, land, landSvcOf, known, events, consumed, fleet })));\n\n // Positive confirmation, on the same rule `spine.resolved` follows: claimed\n // only where the axis actually checked something. A service that touches no\n // topic says nothing here — an \"event axis clean\" on every worker in the fleet\n // would be a green tick for work nobody did.\n const eventChecks = eventEdges.length + eventLinks.length;\n if (eventChecks > 0 && findings.length === eventFindingsAt) {\n findings.push({\n severity: \"ok\",\n code: \"event.covered\",\n message: `${service}: event spine (${eventChecks} message link(s) resolve to asyncapi.yaml)`,\n });\n }\n\n return findings;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../../../src/commands/validate/service/events/events.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The async contract axis — AsyncAPI messages on the landscape spine.\n *\n * The HTTP spine (`./spine.js`) and this one are mirror images, and the mirror\n * is the whole reason this is a second axis rather than a second `metadata`\n * key. On the HTTP axis the PROVIDER owns the contract: an edge names an\n * operation and the target's openapi.yaml settles it, locally, in one file.\n * Here the PRODUCER owns the message and the consumer joins to it from another\n * repository — so \"is this message declared\" is local, while \"does anybody\n * publish it\" is a fleet question with no local answer at all. That second\n * question is the only check on either spine that reads other services'\n * contracts; it lives in `./message-producers.js`, and this axis only decides\n * when it is worth paying for — exactly when something actually consumes.\n */\nimport { existsSync } from \"node:fs\";\nimport {\n type DeclaredService,\n type PathableService,\n type RawServiceId,\n} from \"../../../../core/kernel/ids/service.js\";\nimport { type LoadedDoc } from \"../../../../core/c4/likec4.js\";\nimport { eventKeys, infrastructureKind, unboundSentence } from \"../../../../core/c4/direction/direction.js\";\nimport { type ServicePaths } from \"../../../../core/repo/paths.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\nimport { type Requirement } from \"../../../../core/document/spec.js\";\nimport { readAsyncapi, slotsOf } from \"../../../../core/asyncapi/read.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport { repoPath } from \"../../../../core/envelope/json.js\";\nimport { producerFindings } from \"./message-producers.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\n\n/** What the service target hands the event axis. */\nexport interface EventAxis {\n docsDir: DocsDir;\n service: PathableService;\n /** `service` widened for `===` against the resolver's DOCUMENT text — see service.ts. */\n me: string;\n paths: ServicePaths;\n /** Every requirement the living spec declares; `event.messages-unlinked` counts what is written. */\n reqs: Requirement[];\n /** The ones that still govern something — a REMOVED requirement links nothing. */\n livingReqs: Requirement[];\n /** arch.spec.md's requirements, which carry as many event links as spec.md's do. */\n archReqs: Requirement[];\n /** The living landscape, or null when the repo has none. */\n land: LoadedDoc | null;\n /** Its element→service resolver; null exactly when `land` is. */\n landSvcOf: ((id: string) => DeclaredService) | null;\n /** Service directories that exist — the fleet the producer question is asked over. */\n known: ReadonlySet<RawServiceId>;\n fleet: FleetContext;\n}\n\nexport async function eventAxisFindings(axis: EventAxis): Promise<Finding[]> {\n const { docsDir, service, me, paths, reqs, livingReqs, archReqs, land, landSvcOf, known, fleet } =\n axis;\n const findings: Finding[] = [];\n\n // Where this axis started, so the closing ok-finding can be \"nothing here\n // broke\" rather than a second, independently-computed claim about the same\n // facts. A positive finding derived from the same walk cannot disagree with it.\n const eventFindingsAt = findings.length;\n const events = await readAsyncapi(paths.asyncapi, fleet);\n const sentHere = new Set(events.sent);\n const receivedHere = new Set(events.received);\n\n // Every edge that binds THIS service to a message: `publishes` binds the\n // edge's source (a service produces what it declares an `action: send` for),\n // `consumes` binds its target — `eventKeys` is where that rule is written,\n // once, for this scope and the feature gate alike. One edge may legitimately\n // carry both: a producer drawn straight to its consumer is a single arrow.\n const readable = land !== null && land.errors.length === 0 && landSvcOf !== null;\n const eventEdges = !readable\n ? []\n : land.relationships.flatMap((r) =>\n eventKeys(r)\n .filter((k) => landSvcOf(k.bound) === me)\n .map((k) => ({ direction: k.direction, message: k.message, other: landSvcOf(k.other) })),\n );\n // The edges the filter above drops that THIS service drew the wrong way\n // round: a key bound to infrastructure (a topic, which neither publishes nor\n // consumes) while this service sits at the other end. It used to fall out of\n // `eventEdges` without a word, so a consume edge drawn from the consumer to\n // the topic graded nothing and said nothing. A key bound to an `#external`\n // system is NOT this case — an outside producer or consumer, which the\n // drawing states correctly (core/c4/direction/direction.ts says why). Raised on\n // the service at the unbound end, because it is the one with a drawing to fix.\n const knownIds: ReadonlySet<string> = known;\n if (readable) {\n const infraKindOf = infrastructureKind(land.elements);\n for (const r of land.relationships) {\n for (const key of eventKeys(r)) {\n const kind = infraKindOf(key.bound);\n if (kind === undefined || landSvcOf(key.other) !== me || knownIds.has(landSvcOf(key.bound))) continue;\n findings.push({\n severity: \"warn\",\n code: \"spine.message-unbound\",\n subject: service,\n message: `${service}: landscape ${unboundSentence(r, key, kind)}`,\n });\n }\n }\n }\n // Both requirement namespaces, and a REMOVED requirement links nothing —\n // the `livingReqs` rule the API axis already follows, applied to the pair.\n const eventLinks = [...livingReqs, ...archReqs.filter((r) => r.kind !== \"REMOVED\")].flatMap((r) => [\n ...r.publishes.map((m) => ({ direction: \"publishes\" as const, message: m, requirement: r.name })),\n ...r.consumes.map((m) => ({ direction: \"consumes\" as const, message: m, requirement: r.name })),\n ]);\n\n if (!existsSync(paths.asyncapi)) {\n // Deliberately TWO grades, not the three `service.no-openapi` uses. Its\n // middle grade — warn when the landscape cannot prove nobody calls this\n // service — rests on HTTP being the default: most services expose one, so\n // \"I could not look\" is worth saying. An event contract is genuinely\n // optional; most services in a legacy fleet touch no topic at all. Warning\n // every one of them the moment a landscape fails to parse would put a\n // finding on the whole fleet that names a file nobody owes.\n //\n // So: something already joins into the absent file, or silence.\n const dangling = [\n ...new Set([...eventEdges.map((e) => e.message), ...eventLinks.map((e) => e.message)]),\n ].sort();\n if (dangling.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"service.no-asyncapi\",\n subject: service,\n message:\n `No async contract at ${repoPath(docsDir, paths.asyncapi)}, and ${dangling.length} message link(s) already point into it — ` +\n `every landscape edge and requirement naming one of them resolves to nothing until the file is back`,\n details: dangling,\n });\n }\n } else if (events.unreadable) {\n // The openapi.invalid discipline: a contract that EXISTS but does not read\n // is a broken source of truth, not an empty one. Grading the links against\n // an empty message set would turn one file into one `spine.message-undefined`\n // per edge, every one of them pointing at the landscape.\n findings.push({\n severity: \"error\",\n code: \"asyncapi.invalid\",\n subject: service,\n message: `${service}: asyncapi.yaml does not read as AsyncAPI 3.0 (it does not parse, or it declares an older version) — the event spine is unchecked for this service`,\n ...(events.error === undefined ? {} : { details: [events.error] }),\n });\n } else {\n // Feature-only bookkeeping published to the fleet — the openapi axis's\n // `openapi.remove-marker-living` discipline, one code for every depth the\n // reader collects markers at (slots and inline channel messages alike).\n if (events.markers.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"asyncapi.remove-marker-living\",\n subject: service,\n message: `${service}: living asyncapi.yaml carries x-loam-remove: true at ${events.markers.join(\", \")} — removal markers are valid only in feature deltas; retire a slot through a feature's asyncapi.yaml instead`,\n });\n }\n for (const name of events.duplicateNames) {\n findings.push({\n severity: \"warn\",\n code: \"asyncapi.duplicate-message\",\n subject: service,\n message: `${service}: asyncapi.yaml declares message '${name}' in ${slotsOf(events, name).join(\" and \")} — every join on the name (an edge's metadata { publishes }, a requirement's Publishes: line) picks one of those slots arbitrarily`,\n });\n }\n // The contract-depth probes. Form validated and depth did not: a payload\n // of bare `type: object` and a `$ref` to nothing both read as cleanly as\n // a full schema, while a reader still cannot rebuild the payload from the\n // green contract. Presence probes only — a non-JSON schemaFormat\n // is skipped, so Avro stays a document change.\n const empties = events.messages.filter((m) => m.payloadEmpty === true).map((m) => m.name);\n if (empties.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"asyncapi.payload-undescribed\",\n subject: service,\n message:\n `${service}: ${empties.length} message(s) declare no payload shape (${empties.join(\", \")}) — ` +\n `the name joins the spine, but nothing defines what a consumer would read; ` +\n `declare properties, or a schemaFormat for a non-JSON schema`,\n });\n }\n if (events.danglingRefs.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"asyncapi.ref-unresolved\",\n subject: service,\n message:\n `${service}: asyncapi.yaml contains ${events.danglingRefs.length} internal $ref(s) that resolve ` +\n `to nothing in the document — whatever they were meant to carry is silently absent from the ` +\n `spine's view of this contract`,\n details: events.danglingRefs,\n });\n }\n // Local resolution, both directions and both sources of a claim. An edge and\n // a requirement making the same broken claim get the same sentence under\n // different codes, matching how `spine.op-undefined` and\n // `spec-api.op-undefined` split the HTTP axis: one is the fleet map's\n // mistake, the other is this document's, and the fix is in a different file.\n const declares = (direction: \"publishes\" | \"consumes\", message: string): boolean =>\n direction === \"publishes\" ? sentHere.has(message) : receivedHere.has(message);\n const action = (direction: \"publishes\" | \"consumes\"): string =>\n direction === \"publishes\" ? \"send\" : \"receive\";\n for (const e of eventEdges) {\n if (declares(e.direction, e.message)) continue;\n findings.push({\n severity: \"error\",\n code: \"spine.message-undefined\",\n subject: service,\n message:\n `${service}: landscape edge ${e.direction === \"publishes\" ? `${service} → ${e.other}` : `${e.other} → ${service}`} ` +\n `${e.direction} '${e.message}', but ${service}'s asyncapi.yaml declares no operation with action: ${action(e.direction)} for it`,\n });\n }\n for (const e of eventLinks) {\n if (declares(e.direction, e.message)) continue;\n findings.push({\n severity: \"error\",\n code: \"spec-event.message-undefined\",\n subject: service,\n message: `${service}: requirement '${e.requirement}' ${e.direction} '${e.message}', but ${service}'s asyncapi.yaml declares no operation with action: ${action(e.direction)} for it`,\n });\n }\n // The migration-debt case, `api.ops-unlinked`'s twin: a contract and\n // requirements that never name each other leave this axis vacuously green.\n if (events.messages.length > 0 && reqs.length + archReqs.length > 0 && eventLinks.length === 0) {\n findings.push({\n severity: \"warn\",\n code: \"event.messages-unlinked\",\n subject: service,\n message: `${service}: asyncapi.yaml declares ${events.messages.length} message(s) but no requirement links any — the event axis is unchecked for this service`,\n });\n }\n // `api.ungoverned`'s event twin, and the per-message half the check above\n // cannot see: `event.messages-unlinked` fires only when ZERO links exist,\n // so a contract with five declared messages and three governed ones\n // reported nothing about the other two. The join is against requirement\n // lines ONLY, deliberately ignoring landscape edges — an edge is a claim\n // about traffic, a requirement is governance — exactly as the API axis\n // ignores inbound edges when grading coverage.\n const governed = new Set(eventLinks.map((e) => e.message));\n const orphans = [...new Set(events.messages.map((m) => m.name))].filter((name) => !governed.has(name));\n if (orphans.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"event.ungoverned\",\n subject: service,\n message: `${service}: ${orphans.length} message(s) not governed by any requirement's Publishes:/Consumes: line — ${orphans.join(\", \")}`,\n });\n }\n }\n\n // The consumed-message list is the handover to the fleet question in\n // ./message-producers.js — a fleet where nothing consumes walks nothing.\n const consumed = [\n ...new Set([\n ...eventEdges.filter((e) => e.direction === \"consumes\").map((e) => e.message),\n ...eventLinks.filter((e) => e.direction === \"consumes\").map((e) => e.message),\n ]),\n ].sort();\n findings.push(...(await producerFindings({ docsDir, service, land, landSvcOf, known, events, consumed, fleet })));\n\n // Positive confirmation, on the same rule `spine.resolved` follows: claimed\n // only where the axis actually checked something. A service that touches no\n // topic says nothing here — an \"event axis clean\" on every worker in the fleet\n // would be a green tick for work nobody did.\n const eventChecks = eventEdges.length + eventLinks.length;\n if (eventChecks > 0 && findings.length === eventFindingsAt) {\n findings.push({\n severity: \"ok\",\n code: \"event.covered\",\n message: `${service}: event spine (${eventChecks} message link(s) resolve to asyncapi.yaml)`,\n });\n }\n\n return findings;\n}\n"]}
|
|
@@ -11,7 +11,7 @@ import {} from "../../../../core/vocabulary/report.js";
|
|
|
11
11
|
import { producersByMessage } from "../../../../core/asyncapi/producers.js";
|
|
12
12
|
import {} from "../../../../core/asyncapi/model.js";
|
|
13
13
|
import { FleetContext } from "../../../../core/fleet-context.js";
|
|
14
|
-
import { externalProducerOf } from "
|
|
14
|
+
import { externalProducerOf } from "../../../../core/c4/resolve/external.js";
|
|
15
15
|
// The fleet question, and the one check on either spine that a single
|
|
16
16
|
// repository cannot answer: a consumer joins to a message whose schema lives
|
|
17
17
|
// in the producer's contract, in the producer's repo. Paid only when something
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-producers.js","sourceRoot":"","sources":["../../../../../src/commands/validate/service/events/message-producers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAiE,MAAM,wCAAwC,CAAC;AACvH,OAAO,EAAkB,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAgB,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAoB,MAAM,oCAAoC,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"message-producers.js","sourceRoot":"","sources":["../../../../../src/commands/validate/service/events/message-producers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet question the event axis asks: who produces what this service\n * consumes. Split out of `./events.js` along the seam its own header always\n * named — the local axis grades one service's files, while this is the only\n * check on either spine that reads OTHER services' contracts, and the two\n * subjects share nothing but the consumed-message list the axis hands over.\n */\nimport { type DeclaredService, type PathableService, type RawServiceId } from \"../../../../core/kernel/ids/service.js\";\nimport { type LoadedDoc } from \"../../../../core/c4/likec4.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\nimport { producersByMessage } from \"../../../../core/asyncapi/producers.js\";\nimport { type AsyncapiDoc } from \"../../../../core/asyncapi/model.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport { externalProducerOf } from \"../../../../core/c4/resolve/external.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\n\n/**\n * What the producer question is asked over. One record rather than a parameter\n * list: everything here is already bound together as one service's view of the\n * fleet, and the 4-parameter limit says so too.\n */\nexport interface ProducerQuestion {\n docsDir: DocsDir;\n service: PathableService;\n /** The living landscape, or null when the repo has none. */\n land: LoadedDoc | null;\n /** Its element→service resolver; null exactly when `land` is. */\n landSvcOf: ((id: string) => DeclaredService) | null;\n /** Service directories that exist — the fleet the producer question is asked over. */\n known: ReadonlySet<RawServiceId>;\n /** This service's own parsed contract — the local-shape half of the external answer. */\n events: AsyncapiDoc;\n /** The deduped message names this service consumes, from edges and requirement lines alike. */\n consumed: readonly string[];\n fleet: FleetContext;\n}\n\n// The fleet question, and the one check on either spine that a single\n// repository cannot answer: a consumer joins to a message whose schema lives\n// in the producer's contract, in the producer's repo. Paid only when something\n// actually consumes — a fleet where nobody has adopted the axis walks nothing.\nexport async function producerFindings(input: ProducerQuestion): Promise<Finding[]> {\n const { docsDir, service, land, landSvcOf, known, events, consumed, fleet } = input;\n const findings: Finding[] = [];\n if (consumed.length === 0) return findings;\n const producers = await producersByMessage(docsDir, [...known], fleet);\n for (const message of consumed) {\n const who = producers.byMessage.get(message) ?? [];\n // \"Nobody produces this\" is an argument from absence, and it is only\n // sound over a fleet loam could actually read. One unreadable contract\n // anywhere suspends it — that service may be the producer, and the\n // alternative is blaming every consumer for somebody else's broken YAML.\n // The external answer and the contested answer below rest on positive\n // evidence and need no such guard.\n if (who.length === 0) {\n // A message produced OUTSIDE the fleet used to be inexpressible: the\n // landscape said the producer was #external, and this error fired\n // anyway — so honesty cost a red build, and the exit ramp people took\n // was deleting the link. The landscape already carries the answer; an\n // external producer shifts the contract question to the one file that\n // can settle it, this service's own asyncapi.yaml.\n const ext = externalProducerOf(message, land, landSvcOf, known);\n if (ext !== null) {\n const local = events.messages.find((m) => m.name === message);\n if (local === undefined || local.payloadEmpty === true) {\n findings.push({\n severity: \"warn\",\n code: \"spine.message-external\",\n subject: service,\n message:\n `${service}: consumes '${message}' from '${ext}', a system outside the fleet — the ` +\n `producer declares no contract here, so the only payload definition is this service's ` +\n `own asyncapi.yaml, and it currently defines no shape for it; declare the message with ` +\n `its payload`,\n });\n }\n // A carried contract closes the question: the landscape states the\n // external dependency, the consumer owns the schema — nothing to say.\n } else if (producers.unreadable.length === 0) {\n findings.push({\n severity: \"error\",\n code: \"spine.message-unproduced\",\n subject: service,\n message: `${service}: consumes '${message}', but no service in the fleet and no #external element declares it is sent — the message has no producer anywhere, so nothing defines its payload`,\n });\n }\n } else if (who.length > 1) {\n findings.push({\n severity: \"warn\",\n code: \"asyncapi.message-contested\",\n subject: service,\n message: `${service}: consumes '${message}', which ${who.length} services declare they send (${who.join(\", \")}) — every consumer's join picks one of them arbitrarily, so the payload this service reads is whichever one happens to win`,\n });\n }\n }\n return findings;\n}\n"]}
|
|
@@ -3,11 +3,19 @@
|
|
|
3
3
|
* this service has attested, its pins graded against THIS working tree.
|
|
4
4
|
*
|
|
5
5
|
* Service-repo-scoped like `sources.*` and the gherkin chain — the citations
|
|
6
|
-
* resolve only inside the service's own repository. From the docs repo
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* resolve only inside the service's own repository. From the docs repo this
|
|
7
|
+
* TARGET is silent by design: every pin would grade `unresolved` there, which
|
|
8
|
+
* would be a lie about the code rather than about the record, and
|
|
9
9
|
* `sources.unverifiable-from-here` already names the blind spot once per
|
|
10
|
-
* service.
|
|
10
|
+
* service. One code of the family is not about the code at all —
|
|
11
|
+
* `evidence.record-unreadable` is about the record, which lives in the docs
|
|
12
|
+
* repo — so the FEATURE target (`../feature.ts`) raises it too, from anywhere,
|
|
13
|
+
* through `featureRecordFindings` below:
|
|
14
|
+
* with only this target raising it, `validate --all` in the docs repo stayed
|
|
15
|
+
* green over a conflict-marked record that `verify` refused and `status`,
|
|
16
|
+
* `gate` and `tasks` all reported. In a service repo's `--all` the same file
|
|
17
|
+
* is therefore named twice, once per target: here as this service's pins
|
|
18
|
+
* going unchecked, there as the feature's record itself.
|
|
11
19
|
*
|
|
12
20
|
* ARCHIVED records are deliberately not linted (a v1 non-goal, recorded here):
|
|
13
21
|
* a frozen record is history, post-ship code drift is normal evolution, and
|
|
@@ -24,9 +32,9 @@
|
|
|
24
32
|
import { repoPath } from "../../../core/envelope/json.js";
|
|
25
33
|
import { inOrder } from "../../../core/kernel/concurrency.js";
|
|
26
34
|
import {} from "../../../core/kernel/ids/service.js";
|
|
35
|
+
import {} from "../../../core/repo/entries.js";
|
|
27
36
|
import { listFeatures } from "../../../core/repo/repo.js";
|
|
28
|
-
import { readVerificationState } from "../../../core/verify/file.js";
|
|
29
|
-
import { verificationPath } from "../../../core/verify/record.js";
|
|
37
|
+
import { readVerificationState } from "../../../core/verify/read/file.js";
|
|
30
38
|
import { lintEvidencePins } from "../../../core/verify/pins/lint.js";
|
|
31
39
|
import {} from "../../../core/vocabulary/report.js";
|
|
32
40
|
export async function evidencePinFindings(docsDir, service, repoDir, fleet) {
|
|
@@ -49,7 +57,7 @@ export async function evidencePinFindings(docsDir, service, repoDir, fleet) {
|
|
|
49
57
|
// this service, so the finding fires regardless of attestation.
|
|
50
58
|
const read = await readVerificationState(feature.dir);
|
|
51
59
|
if (read.state === "unreadable") {
|
|
52
|
-
return { feature: feature.id, path: repoPath(docsDir,
|
|
60
|
+
return { feature: feature.id, path: repoPath(docsDir, read.path), unreadable: read.reason, advice: read.advice };
|
|
53
61
|
}
|
|
54
62
|
if (read.state === "absent" || read.verification.schema !== 2)
|
|
55
63
|
return null;
|
|
@@ -69,9 +77,7 @@ export async function evidencePinFindings(docsDir, service, repoDir, fleet) {
|
|
|
69
77
|
severity: "warn",
|
|
70
78
|
code: "evidence.record-unreadable",
|
|
71
79
|
subject: service,
|
|
72
|
-
message: `${me}: ${entry.feature}
|
|
73
|
-
"none of its evidence was checked. It is plain YAML: repair it by hand, or re-record " +
|
|
74
|
-
`(\`loam verify ${entry.feature} --service ${me} --record answers.json\`); loam never overwrites a record it could not read`,
|
|
80
|
+
message: recordUnreadableMessage(`${me}: ${entry.feature}`, entry),
|
|
75
81
|
});
|
|
76
82
|
continue;
|
|
77
83
|
}
|
|
@@ -124,6 +130,46 @@ export async function evidencePinFindings(docsDir, service, repoDir, fleet) {
|
|
|
124
130
|
}
|
|
125
131
|
return findings;
|
|
126
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* `evidence.record-unreadable` on the FEATURE target (`../feature.ts`), from
|
|
135
|
+
* anywhere — the docs repo included, which is where the record lives. Kept
|
|
136
|
+
* here, beside the service target's emission, so one module owns the code and
|
|
137
|
+
* its words: the record read is the same `readVerificationState`, the path is
|
|
138
|
+
* made docs-relative the same way, and the message is the same sentence.
|
|
139
|
+
* Readability only — an `ok` record contributes nothing here, its verdict is
|
|
140
|
+
* `verify`'s and `status`'s to state — and a warn for the reasons the header
|
|
141
|
+
* gives: `valid` and `loam archive` are untouched, `--strict` is the lever.
|
|
142
|
+
*/
|
|
143
|
+
export async function featureRecordFindings(docsDir, feature) {
|
|
144
|
+
const read = await readVerificationState(feature.dir);
|
|
145
|
+
if (read.state !== "unreadable")
|
|
146
|
+
return [];
|
|
147
|
+
const path = repoPath(docsDir, read.path);
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
severity: "warn",
|
|
151
|
+
code: "evidence.record-unreadable",
|
|
152
|
+
message: recordUnreadableMessage(feature.id, { path, unreadable: read.reason, advice: read.advice }),
|
|
153
|
+
locations: [{ path, role: "primary" }],
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The sentence `evidence.record-unreadable` carries, for both targets that
|
|
159
|
+
* raise it. Each emission spells the code literally (the codes-drift
|
|
160
|
+
* collector resolves the `code` slot, as `drift` above says); only the words
|
|
161
|
+
* are shared, so the two cannot drift into two descriptions of one file.
|
|
162
|
+
*
|
|
163
|
+
* The reader's own recipe for the one file that failed, never a fixed one:
|
|
164
|
+
* "re-record" alone sent this finding's reader into `--record`'s refusal of
|
|
165
|
+
* the very same file, and the safe repair differs between a service's own
|
|
166
|
+
* attestation file and the one-file record other services' evidence may sit
|
|
167
|
+
* in. `path` is docs-relative.
|
|
168
|
+
*/
|
|
169
|
+
function recordUnreadableMessage(label, file) {
|
|
170
|
+
return (`${label}: ${file.path} exists but cannot be read as a verification record (${file.unreadable}) — ` +
|
|
171
|
+
`none of its evidence was checked. ${file.advice} loam never overwrites a record it could not read`);
|
|
172
|
+
}
|
|
127
173
|
/** One drift as a detail line: which claim, which citation, what changed. */
|
|
128
174
|
function detailLine(d) {
|
|
129
175
|
return `${d.claim} ${d.evidence} — ${d.what}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence-pins.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/evidence-pins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The evidence-pin re-check as validate findings: every ACTIVE feature record\n * this service has attested, its pins graded against THIS working tree.\n *\n * Service-repo-scoped like `sources.*` and the gherkin chain — the citations\n * resolve only inside the service's own repository. From the docs repo the\n * whole family is silent by design: every pin would grade `unresolved` there,\n * which would be a lie about the code rather than about the record, and\n * `sources.unverifiable-from-here` already names the blind spot once per\n * service.\n *\n * ARCHIVED records are deliberately not linted (a v1 non-goal, recorded here):\n * a frozen record is history, post-ship code drift is normal evolution, and\n * grading it would turn every service's CI permanently yellow the week after\n * every ship. If audit-time re-checks of shipped records are ever wanted, the\n * same `lintEvidencePins` call over `listFeatures` include-archived is the\n * whole change — behind an explicit flag, so the default stays quiet.\n *\n * Everything here demotes reviewer confidence only: warns never flip `valid`,\n * `--strict` may gate on them as it gates on any warning, and no grade ever\n * moves a verdict — `attested` never silently becomes `verified` and a drifted\n * pin never un-confirms a claim.\n */\nimport { repoPath } from \"../../../core/envelope/json.js\";\nimport { inOrder } from \"../../../core/kernel/concurrency.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { listFeatures } from \"../../../core/repo/repo.js\";\nimport { readVerificationState } from \"../../../core/verify/file.js\";\nimport { verificationPath } from \"../../../core/verify/record.js\";\nimport { lintEvidencePins, type PinDrift, type PinLint } from \"../../../core/verify/pins/lint.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport async function evidencePinFindings(\n docsDir: DocsDir,\n service: PathableService,\n repoDir: string | undefined,\n fleet?: FleetContext,\n): Promise<Finding[]> {\n if (repoDir === undefined) return [];\n // Membership questions cross the provenance line through plain strings by\n // design (kernel/ids/service.ts) — record subjects are document text.\n const me: string = service;\n // ACTIVE features only — see the header for why archived records stay frozen.\n const features = await listFeatures(docsDir, {}, fleet);\n type Linted =\n | null\n | { feature: string; path: string; unreadable: string }\n | { feature: string; lint: PinLint };\n const lints = await inOrder(features, async (feature): Promise<Linted> => {\n // Three states, graded apart on purpose. ABSENT is a feature nobody has\n // verified — legitimate silence. UNREADABLE is somebody's record this run\n // could not use, and it must be a finding here rather than a skip: the\n // damage that most plausibly breaks a record in a service repo's CI is a\n // hand edit to the very pins this family grades, and a lint that went\n // silent over it would turn \"someone broke the record\" into a green run —\n // `verify` names the same file `record-unreadable`, but this CI job never\n // runs verify. An unreadable record also cannot say whether it attests\n // this service, so the finding fires regardless of attestation.\n const read = await readVerificationState(feature.dir);\n if (read.state === \"unreadable\") {\n return { feature: feature.id, path: repoPath(docsDir, verificationPath(feature.dir)), unreadable: read.reason };\n }\n if (read.state === \"absent\" || read.verification.schema !== 2) return null;\n if (!(read.verification.attestations ?? []).some((a) => a.service === me)) return null;\n return { feature: feature.id, lint: await lintEvidencePins(read.verification.claims, me, repoDir) };\n });\n\n const findings: Finding[] = [];\n let checked = 0;\n let pinnedRecords = 0;\n let drifted = false;\n for (const entry of lints) {\n if (entry === null) continue;\n if (\"unreadable\" in entry) {\n findings.push({\n severity: \"warn\",\n code: \"evidence.record-unreadable\",\n subject: service,\n message:\n `${me}: ${entry.feature}: ${entry.path} exists but cannot be read as a verification record (${entry.unreadable}) — ` +\n \"none of its evidence was checked. It is plain YAML: repair it by hand, or re-record \" +\n `(\\`loam verify ${entry.feature} --service ${me} --record answers.json\\`); loam never overwrites a record it could not read`,\n });\n continue;\n }\n const { feature, lint } = entry;\n if (lint.checked > 0 || lint.drifts.length > 0) pinnedRecords += 1;\n checked += lint.checked;\n // The code is spelled as a literal at each call below, and the grade rides\n // beside it rather than being sliced back out of the code string — the\n // codes-drift collector resolves the `code` slot, and a derivation would\n // be a second definition of the pairing free to drift from the first.\n const drift = (grade: PinDrift[\"grade\"], code: string, sentence: string): void => {\n const drifts = lint.drifts.filter((d) => d.grade === grade);\n if (drifts.length === 0) return;\n drifted = true;\n findings.push({\n severity: \"warn\",\n code,\n subject: service,\n message:\n `${me}: ${feature}: ${drifts.length} recorded evidence pin(s) ${sentence} — ` +\n \"a reading priority for the reviewer, never a verdict change; re-answer and re-record \" +\n `(\\`loam verify ${feature} --service ${me} --record answers.json\\`) once the evidence is re-read`,\n details: drifts.map(detailLine),\n });\n };\n drift(\n \"unresolved\",\n \"evidence.unresolved\",\n \"cannot be re-checked in this working tree: the cited file is gone, unreadable, not a regular file, or the cited line is past its end\",\n );\n drift(\"moved\", \"evidence.moved\", \"cite a file that changed since the record, though each cited line survives\");\n drift(\"line-changed\", \"evidence.line-changed\", \"cite line(s) that no longer say what was recorded\");\n drift(\n \"token-missing\",\n \"evidence.token-missing\",\n \"cite file(s) that no longer contain the literal string the claim asserts\",\n );\n if (lint.unpinned > 0) {\n findings.push({\n severity: \"ok\",\n // The pre-content_digest-vouch precedent: a record from before the\n // capability existed is not wrong, it is unfindable-by-this-check.\n code: \"evidence.unpinned\",\n subject: service,\n message:\n `${me}: ${feature}: ${lint.unpinned} agent-confirmed citation(s) carry no evidence pin — ` +\n `recorded before pins existed; the next \\`loam verify ${feature} --service ${me} --record\\` makes them drift-checkable`,\n });\n }\n }\n if (checked > 0 && !drifted) {\n findings.push({\n severity: \"ok\",\n code: \"evidence.checked\",\n subject: service,\n message: `${me}: ${checked} evidence pin(s) across ${pinnedRecords} feature record(s) re-checked against this working tree`,\n });\n }\n return findings;\n}\n\n/** One drift as a detail line: which claim, which citation, what changed. */\nfunction detailLine(d: PinDrift): string {\n return `${d.claim} ${d.evidence} — ${d.what}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"evidence-pins.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/evidence-pins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The evidence-pin re-check as validate findings: every ACTIVE feature record\n * this service has attested, its pins graded against THIS working tree.\n *\n * Service-repo-scoped like `sources.*` and the gherkin chain — the citations\n * resolve only inside the service's own repository. From the docs repo this\n * TARGET is silent by design: every pin would grade `unresolved` there, which\n * would be a lie about the code rather than about the record, and\n * `sources.unverifiable-from-here` already names the blind spot once per\n * service. One code of the family is not about the code at all —\n * `evidence.record-unreadable` is about the record, which lives in the docs\n * repo — so the FEATURE target (`../feature.ts`) raises it too, from anywhere,\n * through `featureRecordFindings` below:\n * with only this target raising it, `validate --all` in the docs repo stayed\n * green over a conflict-marked record that `verify` refused and `status`,\n * `gate` and `tasks` all reported. In a service repo's `--all` the same file\n * is therefore named twice, once per target: here as this service's pins\n * going unchecked, there as the feature's record itself.\n *\n * ARCHIVED records are deliberately not linted (a v1 non-goal, recorded here):\n * a frozen record is history, post-ship code drift is normal evolution, and\n * grading it would turn every service's CI permanently yellow the week after\n * every ship. If audit-time re-checks of shipped records are ever wanted, the\n * same `lintEvidencePins` call over `listFeatures` include-archived is the\n * whole change — behind an explicit flag, so the default stays quiet.\n *\n * Everything here demotes reviewer confidence only: warns never flip `valid`,\n * `--strict` may gate on them as it gates on any warning, and no grade ever\n * moves a verdict — `attested` never silently becomes `verified` and a drifted\n * pin never un-confirms a claim.\n */\nimport { repoPath } from \"../../../core/envelope/json.js\";\nimport { inOrder } from \"../../../core/kernel/concurrency.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { type FeatureEntry } from \"../../../core/repo/entries.js\";\nimport { listFeatures } from \"../../../core/repo/repo.js\";\nimport { readVerificationState } from \"../../../core/verify/read/file.js\";\nimport { lintEvidencePins, type PinDrift, type PinLint } from \"../../../core/verify/pins/lint.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport async function evidencePinFindings(\n docsDir: DocsDir,\n service: PathableService,\n repoDir: string | undefined,\n fleet: FleetContext,\n): Promise<Finding[]> {\n if (repoDir === undefined) return [];\n // Membership questions cross the provenance line through plain strings by\n // design (kernel/ids/service.ts) — record subjects are document text.\n const me: string = service;\n // ACTIVE features only — see the header for why archived records stay frozen.\n const features = await listFeatures(docsDir, {}, fleet);\n type Linted =\n | null\n | { feature: string; path: string; unreadable: string; advice: string }\n | { feature: string; lint: PinLint };\n const lints = await inOrder(features, async (feature): Promise<Linted> => {\n // Three states, graded apart on purpose. ABSENT is a feature nobody has\n // verified — legitimate silence. UNREADABLE is somebody's record this run\n // could not use, and it must be a finding here rather than a skip: the\n // damage that most plausibly breaks a record in a service repo's CI is a\n // hand edit to the very pins this family grades, and a lint that went\n // silent over it would turn \"someone broke the record\" into a green run —\n // `verify` names the same file `record-unreadable`, but this CI job never\n // runs verify. An unreadable record also cannot say whether it attests\n // this service, so the finding fires regardless of attestation.\n const read = await readVerificationState(feature.dir);\n if (read.state === \"unreadable\") {\n return { feature: feature.id, path: repoPath(docsDir, read.path), unreadable: read.reason, advice: read.advice };\n }\n if (read.state === \"absent\" || read.verification.schema !== 2) return null;\n if (!(read.verification.attestations ?? []).some((a) => a.service === me)) return null;\n return { feature: feature.id, lint: await lintEvidencePins(read.verification.claims, me, repoDir) };\n });\n\n const findings: Finding[] = [];\n let checked = 0;\n let pinnedRecords = 0;\n let drifted = false;\n for (const entry of lints) {\n if (entry === null) continue;\n if (\"unreadable\" in entry) {\n findings.push({\n severity: \"warn\",\n code: \"evidence.record-unreadable\",\n subject: service,\n message: recordUnreadableMessage(`${me}: ${entry.feature}`, entry),\n });\n continue;\n }\n const { feature, lint } = entry;\n if (lint.checked > 0 || lint.drifts.length > 0) pinnedRecords += 1;\n checked += lint.checked;\n // The code is spelled as a literal at each call below, and the grade rides\n // beside it rather than being sliced back out of the code string — the\n // codes-drift collector resolves the `code` slot, and a derivation would\n // be a second definition of the pairing free to drift from the first.\n const drift = (grade: PinDrift[\"grade\"], code: string, sentence: string): void => {\n const drifts = lint.drifts.filter((d) => d.grade === grade);\n if (drifts.length === 0) return;\n drifted = true;\n findings.push({\n severity: \"warn\",\n code,\n subject: service,\n message:\n `${me}: ${feature}: ${drifts.length} recorded evidence pin(s) ${sentence} — ` +\n \"a reading priority for the reviewer, never a verdict change; re-answer and re-record \" +\n `(\\`loam verify ${feature} --service ${me} --record answers.json\\`) once the evidence is re-read`,\n details: drifts.map(detailLine),\n });\n };\n drift(\n \"unresolved\",\n \"evidence.unresolved\",\n \"cannot be re-checked in this working tree: the cited file is gone, unreadable, not a regular file, or the cited line is past its end\",\n );\n drift(\"moved\", \"evidence.moved\", \"cite a file that changed since the record, though each cited line survives\");\n drift(\"line-changed\", \"evidence.line-changed\", \"cite line(s) that no longer say what was recorded\");\n drift(\n \"token-missing\",\n \"evidence.token-missing\",\n \"cite file(s) that no longer contain the literal string the claim asserts\",\n );\n if (lint.unpinned > 0) {\n findings.push({\n severity: \"ok\",\n // The pre-content_digest-vouch precedent: a record from before the\n // capability existed is not wrong, it is unfindable-by-this-check.\n code: \"evidence.unpinned\",\n subject: service,\n message:\n `${me}: ${feature}: ${lint.unpinned} agent-confirmed citation(s) carry no evidence pin — ` +\n `recorded before pins existed; the next \\`loam verify ${feature} --service ${me} --record\\` makes them drift-checkable`,\n });\n }\n }\n if (checked > 0 && !drifted) {\n findings.push({\n severity: \"ok\",\n code: \"evidence.checked\",\n subject: service,\n message: `${me}: ${checked} evidence pin(s) across ${pinnedRecords} feature record(s) re-checked against this working tree`,\n });\n }\n return findings;\n}\n\n/**\n * `evidence.record-unreadable` on the FEATURE target (`../feature.ts`), from\n * anywhere — the docs repo included, which is where the record lives. Kept\n * here, beside the service target's emission, so one module owns the code and\n * its words: the record read is the same `readVerificationState`, the path is\n * made docs-relative the same way, and the message is the same sentence.\n * Readability only — an `ok` record contributes nothing here, its verdict is\n * `verify`'s and `status`'s to state — and a warn for the reasons the header\n * gives: `valid` and `loam archive` are untouched, `--strict` is the lever.\n */\nexport async function featureRecordFindings(docsDir: DocsDir, feature: FeatureEntry): Promise<Finding[]> {\n const read = await readVerificationState(feature.dir);\n if (read.state !== \"unreadable\") return [];\n const path = repoPath(docsDir, read.path);\n return [\n {\n severity: \"warn\",\n code: \"evidence.record-unreadable\",\n message: recordUnreadableMessage(feature.id, { path, unreadable: read.reason, advice: read.advice }),\n locations: [{ path, role: \"primary\" }],\n },\n ];\n}\n\n/**\n * The sentence `evidence.record-unreadable` carries, for both targets that\n * raise it. Each emission spells the code literally (the codes-drift\n * collector resolves the `code` slot, as `drift` above says); only the words\n * are shared, so the two cannot drift into two descriptions of one file.\n *\n * The reader's own recipe for the one file that failed, never a fixed one:\n * \"re-record\" alone sent this finding's reader into `--record`'s refusal of\n * the very same file, and the safe repair differs between a service's own\n * attestation file and the one-file record other services' evidence may sit\n * in. `path` is docs-relative.\n */\nfunction recordUnreadableMessage(\n label: string,\n file: { path: string; unreadable: string; advice: string },\n): string {\n return (\n `${label}: ${file.path} exists but cannot be read as a verification record (${file.unreadable}) — ` +\n `none of its evidence was checked. ${file.advice} loam never overwrites a record it could not read`\n );\n}\n\n/** One drift as a detail line: which claim, which citation, what changed. */\nfunction detailLine(d: PinDrift): string {\n return `${d.claim} ${d.evidence} — ${d.what}`;\n}\n"]}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { ARTIFACT_FILES } from "../../../../core/repo/paths.js";
|
|
2
|
+
import { serviceLevelElements } from "../../fleet/census.js";
|
|
3
|
+
/**
|
|
4
|
+
* How the OTHER declaration is named.
|
|
5
|
+
*
|
|
6
|
+
* The document, not the file: `Elem` carries no source path, and the map is a
|
|
7
|
+
* PROJECT — `architecture/landscape.likec4` plus every other `.likec4` under
|
|
8
|
+
* `architecture/` — so naming the landscape file was a guess that sent an author
|
|
9
|
+
* to a document that does not declare the element at all.
|
|
10
|
+
*/
|
|
11
|
+
const MAP = "the fleet map (architecture/)";
|
|
12
|
+
export function divergedFindings(check) {
|
|
13
|
+
const { service, model, architecture, known } = check;
|
|
14
|
+
// An extending model declares each of these elements exactly once by
|
|
15
|
+
// construction — there is no copy to diverge — and the shapes' other grade
|
|
16
|
+
// (`c4.element-unowned`) is what asks its question.
|
|
17
|
+
if (model.shape !== "standalone")
|
|
18
|
+
return [];
|
|
19
|
+
if (model.doc.errors.length > 0)
|
|
20
|
+
return [];
|
|
21
|
+
if (architecture === null || architecture.errors.length > 0)
|
|
22
|
+
return [];
|
|
23
|
+
const index = joinIndex(architecture.elements, known);
|
|
24
|
+
const findings = [];
|
|
25
|
+
for (const mine of model.doc.elements) {
|
|
26
|
+
const theirs = counterpart(index, mine);
|
|
27
|
+
if (theirs === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
const fields = differing(mine, theirs);
|
|
30
|
+
if (fields.length === 0)
|
|
31
|
+
continue;
|
|
32
|
+
// Where "copy the map's declaration verbatim" is a no-op: the tag sets are
|
|
33
|
+
// all that differ (so the two kinds are equal, and the block to open can be
|
|
34
|
+
// named) and copying the map's literal tags across would leave them still
|
|
35
|
+
// differing. `fields` is checked first because copying DOES fix a title or a
|
|
36
|
+
// binding, so the verbatim remedy stays right whenever one of those differs.
|
|
37
|
+
const specs = { mine: model.doc.specification, theirs: architecture.specification };
|
|
38
|
+
const inherited = fields.length === 1 && fields[0] === "tags" && copyIsNoOp(mine, theirs, specs)
|
|
39
|
+
? declaringSide(mine.kind, specs)
|
|
40
|
+
: null;
|
|
41
|
+
findings.push({
|
|
42
|
+
severity: "warn",
|
|
43
|
+
code: "c4.declaration-diverged",
|
|
44
|
+
subject: service,
|
|
45
|
+
message: `${service}: ${ARTIFACT_FILES.model} declares '${mine.id}' as ${describe(mine)} and ` +
|
|
46
|
+
`${MAP} declares ${theirs.id === mine.id ? "it" : `'${theirs.id}'`} as ${describe(theirs)} — ` +
|
|
47
|
+
remedy(fields, mine.kind, inherited),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return findings;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* What the message says after the two declarations: which fields differ, and
|
|
54
|
+
* what to do about it. `inherited` is null when copying the map's declaration
|
|
55
|
+
* fixes this, and otherwise the clause naming which document's `specification`
|
|
56
|
+
* puts the tags on the kind — the side an author has to open.
|
|
57
|
+
*/
|
|
58
|
+
function remedy(fields, kind, inherited) {
|
|
59
|
+
// "the kind differs" / "the title differs" / "the tags differ": the verb
|
|
60
|
+
// agrees with the last field named, and `tags` is the one plural noun in the
|
|
61
|
+
// set. It shipped as "the kind differ", and a sentence a reader trips over is
|
|
62
|
+
// one they stop reading.
|
|
63
|
+
const plural = fields.length > 1 || fields[fields.length - 1] === "tags";
|
|
64
|
+
const named = `the ${fields.join(", ")} differ${plural ? "" : "s"}`;
|
|
65
|
+
// The two kinds are equal whenever `tags` is the only differing field, so the
|
|
66
|
+
// block to open can be named exactly.
|
|
67
|
+
if (inherited !== null) {
|
|
68
|
+
return (`${named}, and copying the map's declaration would not clear it: the sets come apart on ` +
|
|
69
|
+
`\`specification { element ${kind} { … } }\`, ${inherited}. Either match the two specification ` +
|
|
70
|
+
'blocks on that kind, or migrate the model to extend the map (SCHEMA.md, "Two shapes of a service ' +
|
|
71
|
+
'model") so there is one specification and one declaration');
|
|
72
|
+
}
|
|
73
|
+
return (`${named}, so two documents are two authorities on one element. Copy the map's declaration verbatim, or ` +
|
|
74
|
+
'migrate the model to extend the map (SCHEMA.md, "Two shapes of a service model") so it is declared once');
|
|
75
|
+
}
|
|
76
|
+
function joinIndex(elements, known) {
|
|
77
|
+
// Which map elements are the fleet's own boxes rather than somebody's
|
|
78
|
+
// interior, by the ONE predicate every other fleet check asks it with
|
|
79
|
+
// (`fleet/census.ts`): a binding or a title naming a real services/<id>/ makes
|
|
80
|
+
// an element a service at any depth, and what sits inside one is that
|
|
81
|
+
// service's container. Everything else is fleet-level — a top-level element,
|
|
82
|
+
// or a box the map draws inside a plain grouping element — see the header.
|
|
83
|
+
const fleetLevel = new Set(serviceLevelElements([...elements], known).map((e) => e.id));
|
|
84
|
+
return {
|
|
85
|
+
byId: unique(elements, (e) => [e.id]),
|
|
86
|
+
byBinding: unique(elements, (e) => (e.service === undefined ? [] : [e.service])),
|
|
87
|
+
// Every DOTTED tail of the id, so a model's `paymentService.api` reaches the
|
|
88
|
+
// map's `marketplace.paymentService.api`: a tail carrying a dot names the
|
|
89
|
+
// owner as well as the leaf, so the two documents are talking about one
|
|
90
|
+
// element.
|
|
91
|
+
//
|
|
92
|
+
// The bare LAST segment names only the leaf, and it is emitted for
|
|
93
|
+
// FLEET-LEVEL boxes alone. This shipped unconditional, and the comment
|
|
94
|
+
// here claimed it was not: a standalone model's own `db`, `api` or `agent`
|
|
95
|
+
// joined whatever `<something>.<that name>` the map happened to hold — one
|
|
96
|
+
// service's private cache told to copy another service's store, verbatim.
|
|
97
|
+
// loam's own meta/docs hit it (`agent` ↔ `loam.core.agent`, a container of
|
|
98
|
+
// src/core/agent/), which is the case that made the rung wrong rather than
|
|
99
|
+
// merely risky (verification 2026-09-04).
|
|
100
|
+
//
|
|
101
|
+
// A box the map draws inside a plain GROUP still emits its bare segment,
|
|
102
|
+
// and deliberately: `infra.cache` is the fleet's cache, so a model's own
|
|
103
|
+
// top-level `cache` is a second meaning for one name and worth the warning.
|
|
104
|
+
// Grouping is a drawing convenience, not ownership — only a SERVICE owns an
|
|
105
|
+
// interior — so the guard is "no ancestor stands for a service" and not
|
|
106
|
+
// "the id carries no dot".
|
|
107
|
+
bySuffix: unique(elements, (e) => {
|
|
108
|
+
const segments = e.id.split(".");
|
|
109
|
+
const tails = segments.map((_, i) => segments.slice(i).join("."));
|
|
110
|
+
return fleetLevel.has(e.id) ? tails : tails.slice(0, -1);
|
|
111
|
+
}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** An index holding only the keys exactly ONE element answers to — a shared key joins nothing. */
|
|
115
|
+
function unique(elements, keys) {
|
|
116
|
+
const claims = new Map();
|
|
117
|
+
for (const element of elements) {
|
|
118
|
+
for (const key of keys(element)) {
|
|
119
|
+
const held = claims.get(key);
|
|
120
|
+
if (held === undefined)
|
|
121
|
+
claims.set(key, [element]);
|
|
122
|
+
else
|
|
123
|
+
held.push(element);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const index = new Map();
|
|
127
|
+
for (const [key, held] of claims)
|
|
128
|
+
if (held.length === 1)
|
|
129
|
+
index.set(key, held[0]);
|
|
130
|
+
return index;
|
|
131
|
+
}
|
|
132
|
+
/** The map's declaration of the element this model declares, or undefined when nothing joins. */
|
|
133
|
+
function counterpart(index, mine) {
|
|
134
|
+
const byId = index.byId.get(mine.id);
|
|
135
|
+
if (byId !== undefined)
|
|
136
|
+
return byId;
|
|
137
|
+
// The binding is the strongest evidence after the id: both documents wrote
|
|
138
|
+
// `metadata { service '<id>' }`, so both mean the same directory.
|
|
139
|
+
const bound = mine.service === undefined ? undefined : index.byBinding.get(mine.service);
|
|
140
|
+
if (bound !== undefined)
|
|
141
|
+
return bound;
|
|
142
|
+
return index.bySuffix.get(mine.id);
|
|
143
|
+
}
|
|
144
|
+
/** Which of the four compared fields disagree, in a fixed order so two runs read alike. */
|
|
145
|
+
function differing(mine, theirs) {
|
|
146
|
+
const fields = [];
|
|
147
|
+
if (mine.kind !== theirs.kind)
|
|
148
|
+
fields.push("kind");
|
|
149
|
+
if (mine.title !== theirs.title)
|
|
150
|
+
fields.push("title");
|
|
151
|
+
// A SET, sorted: tag order is authoring order and means nothing, and a model
|
|
152
|
+
// that lists the map's two tags the other way round is the same declaration.
|
|
153
|
+
if (tagList(mine) !== tagList(theirs))
|
|
154
|
+
fields.push("tags");
|
|
155
|
+
if (mine.service !== theirs.service)
|
|
156
|
+
fields.push("service binding");
|
|
157
|
+
return fields;
|
|
158
|
+
}
|
|
159
|
+
function tagList(e) {
|
|
160
|
+
return [...new Set(e.tags)].sort().join(" ");
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Would "copy the map's declaration verbatim" leave the tag sets still
|
|
164
|
+
* differing? Answered by SIMULATING the copy, which is the only way to be right
|
|
165
|
+
* about it in both directions.
|
|
166
|
+
*
|
|
167
|
+
* `Elem.tags` is what LikeC4 hands over, and since 1.59.0 that includes every
|
|
168
|
+
* tag the element's kind declares (`core/c4/parsed/specification.ts` says why
|
|
169
|
+
* loam reads the block at all). Copying the map's DECLARATION copies its literal
|
|
170
|
+
* tags into the model and nothing else — the model keeps its own specification —
|
|
171
|
+
* so the model's element would come back carrying `mine's kind defaults ∪ the
|
|
172
|
+
* map's literal tags`, against the map's `its kind defaults ∪ the same literal
|
|
173
|
+
* tags`. Those differ exactly when the two kind defaults differ outside the
|
|
174
|
+
* copied literals, and then the remedy is a no-op.
|
|
175
|
+
*
|
|
176
|
+
* The first version compared the two LITERAL tag lists and switched arms only
|
|
177
|
+
* when they were equal, which caught one direction of the same case and missed
|
|
178
|
+
* its mirror: a model writing `#external` on an element whose map twin inherits
|
|
179
|
+
* `#external #platform` from `element topic { … }` was told to copy the map's
|
|
180
|
+
* bare line, and doing exactly that produced the OTHER arm of this same message
|
|
181
|
+
* (verification 2026-09-04, second pass, examples/docs order-service).
|
|
182
|
+
*/
|
|
183
|
+
function copyIsNoOp(mine, theirs, specs) {
|
|
184
|
+
const copied = literalTags(theirs, specs.theirs);
|
|
185
|
+
const after = union(kindTags(mine.kind, specs.mine), copied);
|
|
186
|
+
const before = union(kindTags(theirs.kind, specs.theirs), copied);
|
|
187
|
+
return after !== before;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Which document's `specification` puts the tags on the kind — the half of the
|
|
191
|
+
* message that tells an author which file to open. "Both, differently" is a real
|
|
192
|
+
* state (each block declares its own tag on the same kind) and gets its own
|
|
193
|
+
* words rather than a guess at one side.
|
|
194
|
+
*/
|
|
195
|
+
function declaringSide(kind, specs) {
|
|
196
|
+
const mine = kindTags(kind, specs.mine);
|
|
197
|
+
const theirs = kindTags(kind, specs.theirs);
|
|
198
|
+
if (mine.length > 0 && theirs.length > 0)
|
|
199
|
+
return "which the two documents declare different tags on";
|
|
200
|
+
if (theirs.length > 0)
|
|
201
|
+
return `which ${MAP} declares tags on and ${ARTIFACT_FILES.model} does not`;
|
|
202
|
+
if (mine.length > 0)
|
|
203
|
+
return `which ${ARTIFACT_FILES.model} declares tags on and ${MAP} does not`;
|
|
204
|
+
// No kind declares anything and the copy still fails: not reachable from the
|
|
205
|
+
// predicate above (with equal kinds and no defaults the copy always clears
|
|
206
|
+
// it), and a sentence that names no file beats one that names the wrong file.
|
|
207
|
+
return "which the two documents' blocks disagree on";
|
|
208
|
+
}
|
|
209
|
+
/** The tags `spec` puts on every element of `kind`, sorted — the defaults an element cannot see. */
|
|
210
|
+
function kindTags(kind, spec) {
|
|
211
|
+
return [...new Set(spec?.elementKindTags[kind] ?? [])].sort();
|
|
212
|
+
}
|
|
213
|
+
/** A tag SET as one comparable string; see `tagList` for why order is dropped. */
|
|
214
|
+
function union(a, b) {
|
|
215
|
+
return [...new Set([...a, ...b])].sort().join(" ");
|
|
216
|
+
}
|
|
217
|
+
function literalTags(e, spec) {
|
|
218
|
+
const inherited = new Set(kindTags(e.kind, spec));
|
|
219
|
+
return [...new Set(e.tags)].filter((tag) => !inherited.has(tag)).sort();
|
|
220
|
+
}
|
|
221
|
+
/** One element as the message names it — every compared field, in the order `differing` reports them. */
|
|
222
|
+
function describe(e) {
|
|
223
|
+
const tags = tagList(e);
|
|
224
|
+
return `${e.kind} '${e.title}' [${tags === "" ? "no tags" : tags}, bound to ${e.service ?? "unbound"}]`;
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=diverged.js.map
|