@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.1
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 +2548 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
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 { 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"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fleet question the event axis asks: who produces what this service
|
|
3
|
+
* consumes. Split out of `./events.js` along the seam its own header always
|
|
4
|
+
* named — the local axis grades one service's files, while this is the only
|
|
5
|
+
* check on either spine that reads OTHER services' contracts, and the two
|
|
6
|
+
* subjects share nothing but the consumed-message list the axis hands over.
|
|
7
|
+
*/
|
|
8
|
+
import {} from "../../../../core/kernel/ids/service.js";
|
|
9
|
+
import {} from "../../../../core/c4/likec4.js";
|
|
10
|
+
import {} from "../../../../core/vocabulary/report.js";
|
|
11
|
+
import { producersByMessage } from "../../../../core/asyncapi/producers.js";
|
|
12
|
+
import {} from "../../../../core/asyncapi/model.js";
|
|
13
|
+
import { FleetContext } from "../../../../core/fleet-context.js";
|
|
14
|
+
import { externalProducerOf } from "../../checks/fleet-shape.js";
|
|
15
|
+
// The fleet question, and the one check on either spine that a single
|
|
16
|
+
// repository cannot answer: a consumer joins to a message whose schema lives
|
|
17
|
+
// in the producer's contract, in the producer's repo. Paid only when something
|
|
18
|
+
// actually consumes — a fleet where nobody has adopted the axis walks nothing.
|
|
19
|
+
export async function producerFindings(input) {
|
|
20
|
+
const { docsDir, service, land, landSvcOf, known, events, consumed, fleet } = input;
|
|
21
|
+
const findings = [];
|
|
22
|
+
if (consumed.length === 0)
|
|
23
|
+
return findings;
|
|
24
|
+
const producers = await producersByMessage(docsDir, [...known], fleet);
|
|
25
|
+
for (const message of consumed) {
|
|
26
|
+
const who = producers.byMessage.get(message) ?? [];
|
|
27
|
+
// "Nobody produces this" is an argument from absence, and it is only
|
|
28
|
+
// sound over a fleet loam could actually read. One unreadable contract
|
|
29
|
+
// anywhere suspends it — that service may be the producer, and the
|
|
30
|
+
// alternative is blaming every consumer for somebody else's broken YAML.
|
|
31
|
+
// The external answer and the contested answer below rest on positive
|
|
32
|
+
// evidence and need no such guard.
|
|
33
|
+
if (who.length === 0) {
|
|
34
|
+
// A message produced OUTSIDE the fleet used to be inexpressible: the
|
|
35
|
+
// landscape said the producer was #external, and this error fired
|
|
36
|
+
// anyway — so honesty cost a red build, and the exit ramp people took
|
|
37
|
+
// was deleting the link. The landscape already carries the answer; an
|
|
38
|
+
// external producer shifts the contract question to the one file that
|
|
39
|
+
// can settle it, this service's own asyncapi.yaml.
|
|
40
|
+
const ext = externalProducerOf(message, land, landSvcOf, known);
|
|
41
|
+
if (ext !== null) {
|
|
42
|
+
const local = events.messages.find((m) => m.name === message);
|
|
43
|
+
if (local === undefined || local.payloadEmpty === true) {
|
|
44
|
+
findings.push({
|
|
45
|
+
severity: "warn",
|
|
46
|
+
code: "spine.message-external",
|
|
47
|
+
subject: service,
|
|
48
|
+
message: `${service}: consumes '${message}' from '${ext}', a system outside the fleet — the ` +
|
|
49
|
+
`producer declares no contract here, so the only payload definition is this service's ` +
|
|
50
|
+
`own asyncapi.yaml, and it currently defines no shape for it; declare the message with ` +
|
|
51
|
+
`its payload`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// A carried contract closes the question: the landscape states the
|
|
55
|
+
// external dependency, the consumer owns the schema — nothing to say.
|
|
56
|
+
}
|
|
57
|
+
else if (producers.unreadable.length === 0) {
|
|
58
|
+
findings.push({
|
|
59
|
+
severity: "error",
|
|
60
|
+
code: "spine.message-unproduced",
|
|
61
|
+
subject: service,
|
|
62
|
+
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`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (who.length > 1) {
|
|
67
|
+
findings.push({
|
|
68
|
+
severity: "warn",
|
|
69
|
+
code: "asyncapi.message-contested",
|
|
70
|
+
subject: service,
|
|
71
|
+
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`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return findings;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=message-producers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 \"../../checks/fleet-shape.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"]}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The evidence-pin re-check as validate findings: every ACTIVE feature record
|
|
3
|
+
* this service has attested, its pins graded against THIS working tree.
|
|
4
|
+
*
|
|
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 the
|
|
7
|
+
* whole family is silent by design: every pin would grade `unresolved` there,
|
|
8
|
+
* which would be a lie about the code rather than about the record, and
|
|
9
|
+
* `sources.unverifiable-from-here` already names the blind spot once per
|
|
10
|
+
* service.
|
|
11
|
+
*
|
|
12
|
+
* ARCHIVED records are deliberately not linted (a v1 non-goal, recorded here):
|
|
13
|
+
* a frozen record is history, post-ship code drift is normal evolution, and
|
|
14
|
+
* grading it would turn every service's CI permanently yellow the week after
|
|
15
|
+
* every ship. If audit-time re-checks of shipped records are ever wanted, the
|
|
16
|
+
* same `lintEvidencePins` call over `listFeatures` include-archived is the
|
|
17
|
+
* whole change — behind an explicit flag, so the default stays quiet.
|
|
18
|
+
*
|
|
19
|
+
* Everything here demotes reviewer confidence only: warns never flip `valid`,
|
|
20
|
+
* `--strict` may gate on them as it gates on any warning, and no grade ever
|
|
21
|
+
* moves a verdict — `attested` never silently becomes `verified` and a drifted
|
|
22
|
+
* pin never un-confirms a claim.
|
|
23
|
+
*/
|
|
24
|
+
import { repoPath } from "../../../core/envelope/json.js";
|
|
25
|
+
import { inOrder } from "../../../core/kernel/concurrency.js";
|
|
26
|
+
import {} from "../../../core/kernel/ids/service.js";
|
|
27
|
+
import { listFeatures } from "../../../core/repo/repo.js";
|
|
28
|
+
import { readVerificationState } from "../../../core/verify/file.js";
|
|
29
|
+
import { verificationPath } from "../../../core/verify/record.js";
|
|
30
|
+
import { lintEvidencePins } from "../../../core/verify/pins/lint.js";
|
|
31
|
+
import {} from "../../../core/vocabulary/report.js";
|
|
32
|
+
export async function evidencePinFindings(docsDir, service, repoDir, fleet) {
|
|
33
|
+
if (repoDir === undefined)
|
|
34
|
+
return [];
|
|
35
|
+
// Membership questions cross the provenance line through plain strings by
|
|
36
|
+
// design (kernel/ids/service.ts) — record subjects are document text.
|
|
37
|
+
const me = service;
|
|
38
|
+
// ACTIVE features only — see the header for why archived records stay frozen.
|
|
39
|
+
const features = await listFeatures(docsDir, {}, fleet);
|
|
40
|
+
const lints = await inOrder(features, async (feature) => {
|
|
41
|
+
// Three states, graded apart on purpose. ABSENT is a feature nobody has
|
|
42
|
+
// verified — legitimate silence. UNREADABLE is somebody's record this run
|
|
43
|
+
// could not use, and it must be a finding here rather than a skip: the
|
|
44
|
+
// damage that most plausibly breaks a record in a service repo's CI is a
|
|
45
|
+
// hand edit to the very pins this family grades, and a lint that went
|
|
46
|
+
// silent over it would turn "someone broke the record" into a green run —
|
|
47
|
+
// `verify` names the same file `record-unreadable`, but this CI job never
|
|
48
|
+
// runs verify. An unreadable record also cannot say whether it attests
|
|
49
|
+
// this service, so the finding fires regardless of attestation.
|
|
50
|
+
const read = await readVerificationState(feature.dir);
|
|
51
|
+
if (read.state === "unreadable") {
|
|
52
|
+
return { feature: feature.id, path: repoPath(docsDir, verificationPath(feature.dir)), unreadable: read.reason };
|
|
53
|
+
}
|
|
54
|
+
if (read.state === "absent" || read.verification.schema !== 2)
|
|
55
|
+
return null;
|
|
56
|
+
if (!(read.verification.attestations ?? []).some((a) => a.service === me))
|
|
57
|
+
return null;
|
|
58
|
+
return { feature: feature.id, lint: await lintEvidencePins(read.verification.claims, me, repoDir) };
|
|
59
|
+
});
|
|
60
|
+
const findings = [];
|
|
61
|
+
let checked = 0;
|
|
62
|
+
let pinnedRecords = 0;
|
|
63
|
+
let drifted = false;
|
|
64
|
+
for (const entry of lints) {
|
|
65
|
+
if (entry === null)
|
|
66
|
+
continue;
|
|
67
|
+
if ("unreadable" in entry) {
|
|
68
|
+
findings.push({
|
|
69
|
+
severity: "warn",
|
|
70
|
+
code: "evidence.record-unreadable",
|
|
71
|
+
subject: service,
|
|
72
|
+
message: `${me}: ${entry.feature}: ${entry.path} exists but cannot be read as a verification record (${entry.unreadable}) — ` +
|
|
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`,
|
|
75
|
+
});
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const { feature, lint } = entry;
|
|
79
|
+
if (lint.checked > 0 || lint.drifts.length > 0)
|
|
80
|
+
pinnedRecords += 1;
|
|
81
|
+
checked += lint.checked;
|
|
82
|
+
// The code is spelled as a literal at each call below, and the grade rides
|
|
83
|
+
// beside it rather than being sliced back out of the code string — the
|
|
84
|
+
// codes-drift collector resolves the `code` slot, and a derivation would
|
|
85
|
+
// be a second definition of the pairing free to drift from the first.
|
|
86
|
+
const drift = (grade, code, sentence) => {
|
|
87
|
+
const drifts = lint.drifts.filter((d) => d.grade === grade);
|
|
88
|
+
if (drifts.length === 0)
|
|
89
|
+
return;
|
|
90
|
+
drifted = true;
|
|
91
|
+
findings.push({
|
|
92
|
+
severity: "warn",
|
|
93
|
+
code,
|
|
94
|
+
subject: service,
|
|
95
|
+
message: `${me}: ${feature}: ${drifts.length} recorded evidence pin(s) ${sentence} — ` +
|
|
96
|
+
"a reading priority for the reviewer, never a verdict change; re-answer and re-record " +
|
|
97
|
+
`(\`loam verify ${feature} --service ${me} --record answers.json\`) once the evidence is re-read`,
|
|
98
|
+
details: drifts.map(detailLine),
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
drift("unresolved", "evidence.unresolved", "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");
|
|
102
|
+
drift("moved", "evidence.moved", "cite a file that changed since the record, though each cited line survives");
|
|
103
|
+
drift("line-changed", "evidence.line-changed", "cite line(s) that no longer say what was recorded");
|
|
104
|
+
drift("token-missing", "evidence.token-missing", "cite file(s) that no longer contain the literal string the claim asserts");
|
|
105
|
+
if (lint.unpinned > 0) {
|
|
106
|
+
findings.push({
|
|
107
|
+
severity: "ok",
|
|
108
|
+
// The pre-content_digest-vouch precedent: a record from before the
|
|
109
|
+
// capability existed is not wrong, it is unfindable-by-this-check.
|
|
110
|
+
code: "evidence.unpinned",
|
|
111
|
+
subject: service,
|
|
112
|
+
message: `${me}: ${feature}: ${lint.unpinned} agent-confirmed citation(s) carry no evidence pin — ` +
|
|
113
|
+
`recorded before pins existed; the next \`loam verify ${feature} --service ${me} --record\` makes them drift-checkable`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (checked > 0 && !drifted) {
|
|
118
|
+
findings.push({
|
|
119
|
+
severity: "ok",
|
|
120
|
+
code: "evidence.checked",
|
|
121
|
+
subject: service,
|
|
122
|
+
message: `${me}: ${checked} evidence pin(s) across ${pinnedRecords} feature record(s) re-checked against this working tree`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return findings;
|
|
126
|
+
}
|
|
127
|
+
/** One drift as a detail line: which claim, which citation, what changed. */
|
|
128
|
+
function detailLine(d) {
|
|
129
|
+
return `${d.claim} ${d.evidence} — ${d.what}`;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=evidence-pins.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The service target: one adopted service graded across every axis it owns.
|
|
3
|
+
*
|
|
4
|
+
* This module is the ORDER, not the checks. It reads what more than one axis
|
|
5
|
+
* joins through — the C4 model, the living landscape and its resolver, the two
|
|
6
|
+
* requirement documents — hands each of those to the axis that grades it, and
|
|
7
|
+
* pushes the findings in the sequence the report is read in. The axes
|
|
8
|
+
* themselves cannot see each other, which is the point: `./api.js` decides once
|
|
9
|
+
* whether there is a contract to read, and `./spine.js` is handed the answer
|
|
10
|
+
* instead of re-deriving it against a file it would open a second time.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { relative } from "node:path";
|
|
14
|
+
import { loadFile } from "../../../core/c4/likec4.js";
|
|
15
|
+
import { serviceResolver } from "../../../core/c4/resolve/service.js";
|
|
16
|
+
import {} from "../../../core/kernel/ids/service.js";
|
|
17
|
+
import { landscapePath as landscapeFile, permissionsPath } from "../../../core/repo/paths.js";
|
|
18
|
+
import { locateServicePaths } from "../../../core/repo/service-target.js";
|
|
19
|
+
import { readVocabulary } from "../../../core/permissions/permissions.js";
|
|
20
|
+
import { readCapabilityVocabulary } from "../../../core/capabilities/capabilities.js";
|
|
21
|
+
import { capabilityRequirementIndex, capabilityUnknownFindings } from "../../../core/capabilities/findings.js";
|
|
22
|
+
import { realizesStaleFindings, realizesUnknownFindings } from "../../../core/capabilities/realizes/findings.js";
|
|
23
|
+
import { parseRequirements, readRequirementsDocument } from "../../../core/document/parse.js";
|
|
24
|
+
import { requiresUnknownFindings } from "../checks/requirements.js";
|
|
25
|
+
import { listServices } from "../../../core/repo/repo.js";
|
|
26
|
+
import {} from "../../../core/c4/likec4.js";
|
|
27
|
+
import {} from "../../../core/vocabulary/report.js";
|
|
28
|
+
import { closeIds } from "../../../core/c4/arch.js";
|
|
29
|
+
import { serviceProvenance } from "../../../core/provenance/findings.js";
|
|
30
|
+
import { gherkinFindings } from "../../../core/gherkin/stale.js";
|
|
31
|
+
import { gherkinRoot } from "../../../core/gherkin/stamp.js";
|
|
32
|
+
import { UnsafePathError } from "../../../core/kernel/path-safety.js";
|
|
33
|
+
import { interruptedCommitFinding } from "../../../core/staging/recovery/finding.js";
|
|
34
|
+
import { FleetContext } from "../../../core/fleet-context.js";
|
|
35
|
+
import { errorText } from "../checks/vocabulary.js";
|
|
36
|
+
import { serviceLinkFindings } from "../links/corpus.js";
|
|
37
|
+
import { sourceScopeFindings } from "../checks/sources.js";
|
|
38
|
+
import { apiAxisFindings, generatedContractFindings } from "./api.js";
|
|
39
|
+
import { evidencePinFindings } from "./evidence-pins.js";
|
|
40
|
+
import { spineFindings } from "./spine.js";
|
|
41
|
+
import { eventAxisFindings } from "./events/events.js";
|
|
42
|
+
import { archAxisFindings, readServiceSpecs } from "./specs.js";
|
|
43
|
+
import { unknownDirectiveFindings } from "../../../core/document/grammar/directives.js";
|
|
44
|
+
export async function validateService(check) {
|
|
45
|
+
const { docsDir, service, repoDir, preloaded, gherkinDir, contracts, fleet } = check;
|
|
46
|
+
// The spine and event joins below all ask the landscape's resolver one
|
|
47
|
+
// question: does this edge's endpoint resolve into the directory being
|
|
48
|
+
// validated? The resolver answers with DOCUMENT text and `service` is the
|
|
49
|
+
// repository's own name — disjoint brands, so `===` between them is TS2367.
|
|
50
|
+
// Widening to `string` is not a cast: membership questions cross the
|
|
51
|
+
// provenance line through plain-string containers by design (kernel/ids/service.ts),
|
|
52
|
+
// and nothing pathable ever comes back out of a comparison.
|
|
53
|
+
const me = service;
|
|
54
|
+
const findings = [];
|
|
55
|
+
const paths = await locateServicePaths(docsDir, service, fleet);
|
|
56
|
+
const report = {
|
|
57
|
+
kind: "service",
|
|
58
|
+
id: service,
|
|
59
|
+
path: relative(docsDir, paths.dir).replaceAll("\\", "/"),
|
|
60
|
+
findings,
|
|
61
|
+
};
|
|
62
|
+
// A directory that does not exist is a different fact from a directory with
|
|
63
|
+
// everything missing: validating a typo must say "typo", not "unadopted".
|
|
64
|
+
if (!existsSync(paths.dir)) {
|
|
65
|
+
const close = closeIds(service, (await listServices(docsDir, fleet)).map((s) => s.id));
|
|
66
|
+
findings.push({
|
|
67
|
+
severity: "error",
|
|
68
|
+
code: "service.unknown",
|
|
69
|
+
message: `No service directory at ${paths.dir}.` +
|
|
70
|
+
(close.length > 0
|
|
71
|
+
? ` Did you mean: ${close.join(", ")}?`
|
|
72
|
+
: " `loam list services` shows what exists."),
|
|
73
|
+
text: { marker: false },
|
|
74
|
+
});
|
|
75
|
+
return report;
|
|
76
|
+
}
|
|
77
|
+
// C4 model. Its absence is an error — this is where `adopt` comes in — but it
|
|
78
|
+
// must NOT silence the rest of the gate stack: `loam archive` of a feature
|
|
79
|
+
// introducing a new service creates exactly this state (spec.md, arch.spec.md,
|
|
80
|
+
// openapi.yaml, no model.likec4), and an early return here suspended arch
|
|
81
|
+
// coverage, health.uncovered, provenance (content_digest included) and the
|
|
82
|
+
// gherkin staleness chain for the very services vouch had just promised them
|
|
83
|
+
// to. So the finding is emitted and the walk CONTINUES; only the checks that
|
|
84
|
+
// read the model itself are guarded.
|
|
85
|
+
const hasModel = existsSync(paths.model);
|
|
86
|
+
let elements = [];
|
|
87
|
+
let relationships = [];
|
|
88
|
+
if (!hasModel) {
|
|
89
|
+
findings.push({
|
|
90
|
+
severity: "error",
|
|
91
|
+
code: "service.no-model",
|
|
92
|
+
message: `No C4 model at ${paths.model}. Run \`loam adopt\` for '${service}' first.`,
|
|
93
|
+
text: { marker: false },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const model = fleet === undefined ? await loadFile(paths.model) : await fleet.loadLikeC4(paths.model);
|
|
98
|
+
elements = model.elements;
|
|
99
|
+
relationships = model.relationships;
|
|
100
|
+
if (model.errors.length > 0) {
|
|
101
|
+
findings.push({
|
|
102
|
+
severity: "error",
|
|
103
|
+
code: "c4.invalid",
|
|
104
|
+
message: `${service}: C4 model has ${model.errors.length} error(s)`,
|
|
105
|
+
details: model.errors.map(errorText),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
findings.push({
|
|
110
|
+
severity: "ok",
|
|
111
|
+
code: "c4.valid",
|
|
112
|
+
message: `${service}: C4 model valid (${elements.length} elements · ${relationships.length} relationships)`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// The living landscape, parsed at most once per run: under --all the caller
|
|
117
|
+
// hands in the doc it already loaded, single-service runs load on demand. It
|
|
118
|
+
// serves two checks below — the no-openapi grace and the spine.
|
|
119
|
+
const land = preloaded ??
|
|
120
|
+
(existsSync(landscapeFile(docsDir))
|
|
121
|
+
? fleet === undefined
|
|
122
|
+
? await loadFile(landscapeFile(docsDir))
|
|
123
|
+
: await fleet.loadLikeC4(landscapeFile(docsDir))
|
|
124
|
+
: null);
|
|
125
|
+
// Which service directories actually exist — the positive evidence
|
|
126
|
+
// `serviceOf` needs to resolve an edge drawn into a modelled CONTAINER
|
|
127
|
+
// (`paymentService.api`) back to the service that owns it. Without it every
|
|
128
|
+
// such edge resolves to the container's own title, i.e. to a service nobody
|
|
129
|
+
// has ever adopted, and drops out of the spine unnoticed.
|
|
130
|
+
const known = new Set((await listServices(docsDir, fleet)).map((s) => s.id));
|
|
131
|
+
// The living landscape's element→service resolver, container-aware and
|
|
132
|
+
// memoized. Every question below that asks "does this edge point at me?"
|
|
133
|
+
// asks it through this one function, so the no-openapi grace, the HTTP spine
|
|
134
|
+
// and the event spine can never disagree about which edges are inbound.
|
|
135
|
+
const landSvcOf = land === null ? null : serviceResolver(land.elements, known);
|
|
136
|
+
const specs = await readServiceSpecs({ service, paths, fleet });
|
|
137
|
+
findings.push(...specs.findings);
|
|
138
|
+
const { reqs, livingReqs, archText, archReqs } = specs;
|
|
139
|
+
const api = await apiAxisFindings({
|
|
140
|
+
service,
|
|
141
|
+
me,
|
|
142
|
+
paths,
|
|
143
|
+
reqs,
|
|
144
|
+
livingReqs,
|
|
145
|
+
land,
|
|
146
|
+
landSvcOf,
|
|
147
|
+
fleet,
|
|
148
|
+
});
|
|
149
|
+
findings.push(...api.findings);
|
|
150
|
+
findings.push(...spineFindings({
|
|
151
|
+
service,
|
|
152
|
+
me,
|
|
153
|
+
land,
|
|
154
|
+
landSvcOf,
|
|
155
|
+
contract: api.contract,
|
|
156
|
+
landscapeReported: check.landscapeReported,
|
|
157
|
+
}));
|
|
158
|
+
findings.push(...(await eventAxisFindings({
|
|
159
|
+
docsDir,
|
|
160
|
+
service,
|
|
161
|
+
me,
|
|
162
|
+
paths,
|
|
163
|
+
reqs,
|
|
164
|
+
livingReqs,
|
|
165
|
+
archReqs,
|
|
166
|
+
land,
|
|
167
|
+
landSvcOf,
|
|
168
|
+
known,
|
|
169
|
+
fleet,
|
|
170
|
+
})));
|
|
171
|
+
findings.push(...(await archAxisFindings({
|
|
172
|
+
service,
|
|
173
|
+
paths,
|
|
174
|
+
archText,
|
|
175
|
+
archReqs,
|
|
176
|
+
elements,
|
|
177
|
+
relationships,
|
|
178
|
+
land,
|
|
179
|
+
known,
|
|
180
|
+
})));
|
|
181
|
+
// The authorization axis. Both requirement documents are graded against one
|
|
182
|
+
// fleet vocabulary — a permission is a fleet fact, and an arch requirement
|
|
183
|
+
// gates on one exactly as a business requirement does. The capability axis
|
|
184
|
+
// rides the same loop against ITS fleet vocabulary; under --all the fleet
|
|
185
|
+
// memo makes that one parse for the whole run.
|
|
186
|
+
const vocabulary = await readVocabulary(permissionsPath(docsDir));
|
|
187
|
+
const capabilities = fleet === undefined ? await readCapabilityVocabulary(docsDir) : await fleet.capabilities(docsDir);
|
|
188
|
+
// The requirement-level index behind `Realizes:`. Built once for both axes,
|
|
189
|
+
// and behind the same ladder `capabilityUnknownFindings` applies — an absent
|
|
190
|
+
// or unreadable vocabulary means no reads and no grades at all, so a fleet
|
|
191
|
+
// that has not adopted the axis pays nothing for a line it never wrote.
|
|
192
|
+
const capabilityReqs = await capabilityRequirementIndex(capabilities, fleet === undefined ? async (p) => parseRequirements(await readRequirementsDocument(p)) : (p) => fleet.readRequirements(p));
|
|
193
|
+
for (const [label, docReqs] of [
|
|
194
|
+
["spec.md", reqs],
|
|
195
|
+
["arch.spec.md", archReqs],
|
|
196
|
+
]) {
|
|
197
|
+
const target = { where: `${service}: ${label}`, subject: service };
|
|
198
|
+
findings.push(...requiresUnknownFindings(docReqs, target, vocabulary));
|
|
199
|
+
findings.push(...capabilityUnknownFindings(docReqs, target, capabilities));
|
|
200
|
+
findings.push(...realizesUnknownFindings(docReqs, target, capabilityReqs));
|
|
201
|
+
findings.push(...realizesStaleFindings(docReqs, target, capabilityReqs));
|
|
202
|
+
// The grammar guard, last in the loop: it grades the KEYS the three checks
|
|
203
|
+
// above resolve values from, so a line none of them saw is reported here
|
|
204
|
+
// rather than staying invisible for having produced nothing to check.
|
|
205
|
+
findings.push(...unknownDirectiveFindings(docReqs, target));
|
|
206
|
+
}
|
|
207
|
+
// The links this service's own documents write. Its place in the order is
|
|
208
|
+
// deliberate: every axis above joins through a LINE loam defined, and this one
|
|
209
|
+
// grades the join an author writes in prose — so it reads last among the
|
|
210
|
+
// document checks and adds no input any of them depend on.
|
|
211
|
+
findings.push(...(await serviceLinkFindings({ docsDir, subject: service, fleet }, paths)));
|
|
212
|
+
// Provenance last: who vouched for this, and what code it was written from.
|
|
213
|
+
findings.push(...(await serviceProvenance(docsDir, service, { repoDir, fleet })));
|
|
214
|
+
findings.push(...(await sourceScopeFindings(docsDir, service, repoDir, fleet)));
|
|
215
|
+
// The build's own contract against the committed copy. Service-repo only:
|
|
216
|
+
// from the docs repo there is no build output to read, and the family stays
|
|
217
|
+
// silent rather than guessing — the same rule `sources` provenance follows.
|
|
218
|
+
if (repoDir !== undefined) {
|
|
219
|
+
findings.push(...(await generatedContractFindings(service, repoDir, contracts?.openapi, paths.openapi)));
|
|
220
|
+
}
|
|
221
|
+
// The generated-gherkin freshness chain, service-repo-scoped like sources.*:
|
|
222
|
+
// it needs the repo (the suite lives there), and it stays quiet until
|
|
223
|
+
// <gherkinDir>/loam/ exists — a service that never generated has not opted in.
|
|
224
|
+
findings.push(...(await gherkinFindings({ docsDir, service, repoDir, gherkinDir, fleet })));
|
|
225
|
+
// The evidence-pin re-check, the same service-repo-scoped family: recorded
|
|
226
|
+
// verification citations graded against THIS working tree, quiet from the
|
|
227
|
+
// docs repo — sources.unverifiable-from-here already names that blind spot.
|
|
228
|
+
findings.push(...(await evidencePinFindings(docsDir, service, repoDir, fleet)));
|
|
229
|
+
// The one journal that does NOT live in the docs repo: gherkin commits into
|
|
230
|
+
// the service repo's emission root. A half-committed suite graded by the
|
|
231
|
+
// freshness chain above reads as merely stale — warn-severity, which never
|
|
232
|
+
// gates — while doctor calls the same state a blocker. Same finding, same
|
|
233
|
+
// code, led rather than appended, exactly as validate leads with the docs
|
|
234
|
+
// repo's own journal.
|
|
235
|
+
if (repoDir !== undefined) {
|
|
236
|
+
try {
|
|
237
|
+
const interrupted = await interruptedCommitFinding(gherkinRoot(repoDir, gherkinDir));
|
|
238
|
+
if (interrupted !== null)
|
|
239
|
+
findings.unshift(interrupted);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
// An unsafe gherkinDir is `loam gherkin`'s refusal to make, with its
|
|
243
|
+
// own sentence; grading declines to scan what it cannot trust.
|
|
244
|
+
if (!(err instanceof UnsafePathError))
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return report;
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The service target: one adopted service graded across every axis it owns.\n *\n * This module is the ORDER, not the checks. It reads what more than one axis\n * joins through — the C4 model, the living landscape and its resolver, the two\n * requirement documents — hands each of those to the axis that grades it, and\n * pushes the findings in the sequence the report is read in. The axes\n * themselves cannot see each other, which is the point: `./api.js` decides once\n * whether there is a contract to read, and `./spine.js` is handed the answer\n * instead of re-deriving it against a file it would open a second time.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { loadFile, type Elem, type Rel } from \"../../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../../core/c4/resolve/service.js\";\nimport { type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { landscapePath as landscapeFile, permissionsPath } from \"../../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../../core/repo/service-target.js\";\nimport { readVocabulary } from \"../../../core/permissions/permissions.js\";\nimport { readCapabilityVocabulary } from \"../../../core/capabilities/capabilities.js\";\nimport { capabilityRequirementIndex, capabilityUnknownFindings } from \"../../../core/capabilities/findings.js\";\nimport { realizesStaleFindings, realizesUnknownFindings } from \"../../../core/capabilities/realizes/findings.js\";\nimport { parseRequirements, readRequirementsDocument } from \"../../../core/document/parse.js\";\nimport { requiresUnknownFindings } from \"../checks/requirements.js\";\nimport { listServices } from \"../../../core/repo/repo.js\";\nimport { type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { type Finding, type TargetReport } from \"../../../core/vocabulary/report.js\";\nimport { closeIds } from \"../../../core/c4/arch.js\";\nimport { serviceProvenance } from \"../../../core/provenance/findings.js\";\nimport { gherkinFindings } from \"../../../core/gherkin/stale.js\";\nimport { gherkinRoot } from \"../../../core/gherkin/stamp.js\";\nimport { UnsafePathError } from \"../../../core/kernel/path-safety.js\";\nimport { interruptedCommitFinding } from \"../../../core/staging/recovery/finding.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { errorText } from \"../checks/vocabulary.js\";\nimport { serviceLinkFindings } from \"../links/corpus.js\";\nimport { sourceScopeFindings } from \"../checks/sources.js\";\nimport { apiAxisFindings, generatedContractFindings } from \"./api.js\";\nimport { evidencePinFindings } from \"./evidence-pins.js\";\nimport { spineFindings } from \"./spine.js\";\nimport { eventAxisFindings } from \"./events/events.js\";\nimport { archAxisFindings, readServiceSpecs } from \"./specs.js\";\nimport { unknownDirectiveFindings } from \"../../../core/document/grammar/directives.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\n/**\n * A service's absences are graded by what each one proves.\n *\n * `service.unknown` (error): the directory itself does not exist — the id is a\n * typo until proven otherwise, so the hint names ids that DO exist and never\n * `loam adopt`, which would faithfully document the misspelling.\n * `service.no-model` (error): the directory is real but the C4 center is not —\n * adopt is the right hint. Every check that does not read the model still runs\n * (spec coverage, the arch axis, provenance, gherkin staleness): archive\n * creates exactly this state for a new service, and those signals must not go\n * quiet there.\n * `service.no-spec` / `service.no-openapi` (warn): the adopt brief marks both\n * required, but a fleet mid-rollout legitimately has part-adopted services —\n * the absence must stay visible without gating CI for months. The openapi warn\n * keeps quiet when the landscape proves nobody calls an operation on this\n * service: a worker with no API is not missing one.\n * `api.ops-unlinked` (warn): an OpenAPI and requirements that never name each\n * other pass every cross-axis check vacuously — a repo migrated from OpenSpec\n * does exactly that by default, and vacuous is not the same as checked.\n */\ninterface ServiceCheck {\n docsDir: DocsDir;\n service: PathableService;\n /** The service's own repo, when loam is standing in it. Undefined from the docs repo. */\n repoDir?: string;\n /** The build outputs `loam.json` names, when this repo declares any. */\n contracts?: { openapi?: string };\n /** The living landscape under --all; undefined means \"load it if you need it\", null means \"there is none\". */\n preloaded?: LoadedDoc | null;\n gherkinDir?: string;\n fleet?: FleetContext;\n /**\n * True when this run ALSO emits a `landscape` target. It decides one thing:\n * whether a landscape parse error's details are repeated here. Under --all\n * they are not — the landscape target already carries them once, and N copies\n * of one parser's cascade is the whole report.\n */\n landscapeReported?: boolean;\n}\n\n\nexport async function validateService(check: ServiceCheck): Promise<TargetReport> {\n const { docsDir, service, repoDir, preloaded, gherkinDir, contracts, fleet } = check;\n // The spine and event joins below all ask the landscape's resolver one\n // question: does this edge's endpoint resolve into the directory being\n // validated? The resolver answers with DOCUMENT text and `service` is the\n // repository's own name — disjoint brands, so `===` between them is TS2367.\n // Widening to `string` is not a cast: membership questions cross the\n // provenance line through plain-string containers by design (kernel/ids/service.ts),\n // and nothing pathable ever comes back out of a comparison.\n const me: string = service;\n const findings: Finding[] = [];\n const paths = await locateServicePaths(docsDir, service, fleet);\n const report: TargetReport = {\n kind: \"service\",\n id: service,\n path: relative(docsDir, paths.dir).replaceAll(\"\\\\\", \"/\"),\n findings,\n };\n\n // A directory that does not exist is a different fact from a directory with\n // everything missing: validating a typo must say \"typo\", not \"unadopted\".\n if (!existsSync(paths.dir)) {\n const close = closeIds(service, (await listServices(docsDir, fleet)).map((s) => s.id));\n findings.push({\n severity: \"error\",\n code: \"service.unknown\",\n message:\n `No service directory at ${paths.dir}.` +\n (close.length > 0\n ? ` Did you mean: ${close.join(\", \")}?`\n : \" `loam list services` shows what exists.\"),\n text: { marker: false },\n });\n return report;\n }\n\n // C4 model. Its absence is an error — this is where `adopt` comes in — but it\n // must NOT silence the rest of the gate stack: `loam archive` of a feature\n // introducing a new service creates exactly this state (spec.md, arch.spec.md,\n // openapi.yaml, no model.likec4), and an early return here suspended arch\n // coverage, health.uncovered, provenance (content_digest included) and the\n // gherkin staleness chain for the very services vouch had just promised them\n // to. So the finding is emitted and the walk CONTINUES; only the checks that\n // read the model itself are guarded.\n const hasModel = existsSync(paths.model);\n let elements: Elem[] = [];\n let relationships: Rel[] = [];\n if (!hasModel) {\n findings.push({\n severity: \"error\",\n code: \"service.no-model\",\n message: `No C4 model at ${paths.model}. Run \\`loam adopt\\` for '${service}' first.`,\n text: { marker: false },\n });\n } else {\n const model = fleet === undefined ? await loadFile(paths.model) : await fleet.loadLikeC4(paths.model);\n elements = model.elements;\n relationships = model.relationships;\n if (model.errors.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"c4.invalid\",\n message: `${service}: C4 model has ${model.errors.length} error(s)`,\n details: model.errors.map(errorText),\n });\n } else {\n findings.push({\n severity: \"ok\",\n code: \"c4.valid\",\n message: `${service}: C4 model valid (${elements.length} elements · ${relationships.length} relationships)`,\n });\n }\n }\n\n // The living landscape, parsed at most once per run: under --all the caller\n // hands in the doc it already loaded, single-service runs load on demand. It\n // serves two checks below — the no-openapi grace and the spine.\n const land =\n preloaded ??\n (existsSync(landscapeFile(docsDir))\n ? fleet === undefined\n ? await loadFile(landscapeFile(docsDir))\n : await fleet.loadLikeC4(landscapeFile(docsDir))\n : null);\n\n // Which service directories actually exist — the positive evidence\n // `serviceOf` needs to resolve an edge drawn into a modelled CONTAINER\n // (`paymentService.api`) back to the service that owns it. Without it every\n // such edge resolves to the container's own title, i.e. to a service nobody\n // has ever adopted, and drops out of the spine unnoticed.\n const known = new Set((await listServices(docsDir, fleet)).map((s) => s.id));\n\n\n // The living landscape's element→service resolver, container-aware and\n // memoized. Every question below that asks \"does this edge point at me?\"\n // asks it through this one function, so the no-openapi grace, the HTTP spine\n // and the event spine can never disagree about which edges are inbound.\n const landSvcOf = land === null ? null : serviceResolver(land.elements, known);\n\n const specs = await readServiceSpecs({ service, paths, fleet });\n findings.push(...specs.findings);\n const { reqs, livingReqs, archText, archReqs } = specs;\n\n const api = await apiAxisFindings({\n service,\n me,\n paths,\n reqs,\n livingReqs,\n land,\n landSvcOf,\n fleet,\n });\n findings.push(...api.findings);\n findings.push(\n ...spineFindings({\n service,\n me,\n land,\n landSvcOf,\n contract: api.contract,\n landscapeReported: check.landscapeReported,\n }),\n );\n findings.push(\n ...(await eventAxisFindings({\n docsDir,\n service,\n me,\n paths,\n reqs,\n livingReqs,\n archReqs,\n land,\n landSvcOf,\n known,\n fleet,\n })),\n );\n findings.push(\n ...(await archAxisFindings({\n service,\n paths,\n archText,\n archReqs,\n elements,\n relationships,\n land,\n known,\n })),\n );\n\n // The authorization axis. Both requirement documents are graded against one\n // fleet vocabulary — a permission is a fleet fact, and an arch requirement\n // gates on one exactly as a business requirement does. The capability axis\n // rides the same loop against ITS fleet vocabulary; under --all the fleet\n // memo makes that one parse for the whole run.\n const vocabulary = await readVocabulary(permissionsPath(docsDir));\n const capabilities =\n fleet === undefined ? await readCapabilityVocabulary(docsDir) : await fleet.capabilities(docsDir);\n // The requirement-level index behind `Realizes:`. Built once for both axes,\n // and behind the same ladder `capabilityUnknownFindings` applies — an absent\n // or unreadable vocabulary means no reads and no grades at all, so a fleet\n // that has not adopted the axis pays nothing for a line it never wrote.\n const capabilityReqs = await capabilityRequirementIndex(\n capabilities,\n fleet === undefined ? async (p) => parseRequirements(await readRequirementsDocument(p)) : (p) => fleet.readRequirements(p),\n );\n for (const [label, docReqs] of [\n [\"spec.md\", reqs],\n [\"arch.spec.md\", archReqs],\n ] as const) {\n const target = { where: `${service}: ${label}`, subject: service };\n findings.push(...requiresUnknownFindings(docReqs, target, vocabulary));\n findings.push(...capabilityUnknownFindings(docReqs, target, capabilities));\n findings.push(...realizesUnknownFindings(docReqs, target, capabilityReqs));\n findings.push(...realizesStaleFindings(docReqs, target, capabilityReqs));\n // The grammar guard, last in the loop: it grades the KEYS the three checks\n // above resolve values from, so a line none of them saw is reported here\n // rather than staying invisible for having produced nothing to check.\n findings.push(...unknownDirectiveFindings(docReqs, target));\n }\n\n // The links this service's own documents write. Its place in the order is\n // deliberate: every axis above joins through a LINE loam defined, and this one\n // grades the join an author writes in prose — so it reads last among the\n // document checks and adds no input any of them depend on.\n findings.push(...(await serviceLinkFindings({ docsDir, subject: service, fleet }, paths)));\n\n // Provenance last: who vouched for this, and what code it was written from.\n findings.push(...(await serviceProvenance(docsDir, service, { repoDir, fleet })));\n findings.push(...(await sourceScopeFindings(docsDir, service, repoDir, fleet)));\n // The build's own contract against the committed copy. Service-repo only:\n // from the docs repo there is no build output to read, and the family stays\n // silent rather than guessing — the same rule `sources` provenance follows.\n if (repoDir !== undefined) {\n findings.push(...(await generatedContractFindings(service, repoDir, contracts?.openapi, paths.openapi)));\n }\n\n // The generated-gherkin freshness chain, service-repo-scoped like sources.*:\n // it needs the repo (the suite lives there), and it stays quiet until\n // <gherkinDir>/loam/ exists — a service that never generated has not opted in.\n findings.push(...(await gherkinFindings({ docsDir, service, repoDir, gherkinDir, fleet })));\n\n // The evidence-pin re-check, the same service-repo-scoped family: recorded\n // verification citations graded against THIS working tree, quiet from the\n // docs repo — sources.unverifiable-from-here already names that blind spot.\n findings.push(...(await evidencePinFindings(docsDir, service, repoDir, fleet)));\n\n // The one journal that does NOT live in the docs repo: gherkin commits into\n // the service repo's emission root. A half-committed suite graded by the\n // freshness chain above reads as merely stale — warn-severity, which never\n // gates — while doctor calls the same state a blocker. Same finding, same\n // code, led rather than appended, exactly as validate leads with the docs\n // repo's own journal.\n if (repoDir !== undefined) {\n try {\n const interrupted = await interruptedCommitFinding(gherkinRoot(repoDir, gherkinDir));\n if (interrupted !== null) findings.unshift(interrupted);\n } catch (err) {\n // An unsafe gherkinDir is `loam gherkin`'s refusal to make, with its\n // own sentence; grading declines to scan what it cannot trust.\n if (!(err instanceof UnsafePathError)) throw err;\n }\n }\n\n return report;\n}\n"]}
|