@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,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four checks behind `loam gate` — a can-i-deploy-shaped PURE QUERY over
|
|
3
|
+
* evidence other commands already recorded. Nothing here executes anything,
|
|
4
|
+
* writes anything, takes the docs lock, or re-derives a coherence plan: every
|
|
5
|
+
* answer is either already on disk (verification.yaml, vouch stamps, the
|
|
6
|
+
* commit journal) or a presence/landscape fact the enumeration carries.
|
|
7
|
+
*
|
|
8
|
+
* partners the landscape's direct joins, both ends graded on the ladder
|
|
9
|
+
* freshness content.stale / sources.stale over the target and partners,
|
|
10
|
+
* plus the one integrity error that makes freshness
|
|
11
|
+
* unjudgeable (frontmatter.malformed) and a refusing decode
|
|
12
|
+
* of the spec bytes themselves
|
|
13
|
+
* verification verificationState per active feature touching the service
|
|
14
|
+
* (its own module, `./verification.ts`)
|
|
15
|
+
* interrupted the `.loam-commit` journal — the one repo state that poisons
|
|
16
|
+
* every other answer above
|
|
17
|
+
*
|
|
18
|
+
* Every `gate.*` code is spelled as a literal `code:` property at its emit
|
|
19
|
+
* site, never through a code-taking factory parameter — verifyStep
|
|
20
|
+
* (core/status/verification.ts) records that test/codes-drift.test.ts's
|
|
21
|
+
* collector is blind to the parameter form, and a refactor toward a factory
|
|
22
|
+
* would silently undocument the family.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync } from "node:fs";
|
|
25
|
+
import { inOrder } from "../kernel/concurrency.js";
|
|
26
|
+
import { serviceProvenance } from "../provenance/findings.js";
|
|
27
|
+
import { interruptedCommitFinding } from "../staging/recovery/finding.js";
|
|
28
|
+
import { servicePathsAt, SPEC_AXES } from "../repo/paths.js";
|
|
29
|
+
import { serviceTreePath } from "../kernel/ids/dirs.js";
|
|
30
|
+
import { locateServicePaths } from "../repo/service-target.js";
|
|
31
|
+
import { atLeastDocumented, partnerScan } from "./partners.js";
|
|
32
|
+
import { verificationCheck } from "./verification.js";
|
|
33
|
+
import { unreadableSubject } from "./report.js";
|
|
34
|
+
export async function gateReport(req) {
|
|
35
|
+
// The scan first, alone: freshness walks the partner set it derives. The
|
|
36
|
+
// three remaining checks are independent of each other — different files,
|
|
37
|
+
// memoized reads — so they run concurrently; only the scan must be ordered.
|
|
38
|
+
const scan = await partnerScan({ docsDir: req.docsDir, service: req.service, fleet: req.fleet });
|
|
39
|
+
const [freshness, verification, interrupted] = await Promise.all([
|
|
40
|
+
freshnessFindings(req, scan),
|
|
41
|
+
verificationCheck({ docsDir: req.docsDir, service: req.service, fleet: req.fleet }),
|
|
42
|
+
interruptedCommitFinding(req.docsDir),
|
|
43
|
+
]);
|
|
44
|
+
const checks = [
|
|
45
|
+
{ check: "partners", findings: partnersFindings(scan, req.service) },
|
|
46
|
+
{ check: "freshness", findings: freshness },
|
|
47
|
+
{ check: "verification", findings: verification.findings },
|
|
48
|
+
{ check: "interrupted", findings: interrupted === null ? [] : [interrupted] },
|
|
49
|
+
];
|
|
50
|
+
return {
|
|
51
|
+
service: req.service,
|
|
52
|
+
landscape: scan.landscape,
|
|
53
|
+
partners: scan.partners,
|
|
54
|
+
features: verification.features,
|
|
55
|
+
checks,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/* ------------------------------------------------------------------ */
|
|
59
|
+
/* Check 1: partners */
|
|
60
|
+
/* ------------------------------------------------------------------ */
|
|
61
|
+
function partnersFindings(scan, service) {
|
|
62
|
+
const findings = [];
|
|
63
|
+
// "Could not look" must never read as "no partners" — the docsRepoReady
|
|
64
|
+
// doctrine one level down. `validate --all` owns the diagnosis of the
|
|
65
|
+
// landscape itself; this code only refuses the false silence. The two ways
|
|
66
|
+
// of not looking are graded apart, the way `landscape.missing` and
|
|
67
|
+
// `landscape.invalid` are: ABSENT is a warning — a repo before its first
|
|
68
|
+
// adopt legitimately has no map yet — but a map that EXISTS and cannot be
|
|
69
|
+
// parsed or read is an ERROR, because `validate --all` fails that repo
|
|
70
|
+
// (`landscape.invalid`) and a deploy gate that passed where the fleet gate
|
|
71
|
+
// fails would be the quieter of two contradictory verdicts.
|
|
72
|
+
if (scan.landscape === "absent") {
|
|
73
|
+
findings.push({
|
|
74
|
+
severity: "warn",
|
|
75
|
+
code: "gate.partners-unknown",
|
|
76
|
+
message: `architecture/landscape.likec4 does not exist, so the partner set for '${service}' could not be derived — an absent map means nobody could look, not that nothing joins this service. \`loam validate --all\` diagnoses the landscape itself.`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else if (scan.landscape === "invalid") {
|
|
80
|
+
findings.push({
|
|
81
|
+
severity: "error",
|
|
82
|
+
code: "gate.partners-unknown",
|
|
83
|
+
message: `architecture/landscape.likec4 exists but cannot be used — ${scan.landscapeProblem ?? "it did not parse"} — so the partner set for '${service}' could not be derived. ` +
|
|
84
|
+
`A broken map means nobody could look, not that nothing joins this service, and \`loam validate --all\` fails this repo for the same file. Fix the landscape, then re-run.`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// The rung depends on the landscape only through the api question (does
|
|
88
|
+
// anything call this service?). With no readable map that question is
|
|
89
|
+
// unanswerable, and the fail-closed grading — `landscapeEvidence`: no proof
|
|
90
|
+
// means an API is expected — would convict a worker or UI for a contract
|
|
91
|
+
// nobody may be owed. One unanswerable fact yields ONE finding (the
|
|
92
|
+
// landscape's own, above), so under an unreadable map this error fires only
|
|
93
|
+
// when the service is below `documented` REGARDLESS of the api question.
|
|
94
|
+
const target = scan.landscape === "read" ? scan.target : scan.targetWithoutApi;
|
|
95
|
+
if (!atLeastDocumented(target.maturity)) {
|
|
96
|
+
findings.push({
|
|
97
|
+
severity: "error",
|
|
98
|
+
code: "gate.service-undocumented",
|
|
99
|
+
subject: service,
|
|
100
|
+
message: `${serviceTreePath(scan.targetEntry)}/ sits at '${target.maturity}' on the adoption ladder — below 'documented', the docs cannot say what its joins even are, so no recorded evidence can answer the deploy question. ` +
|
|
101
|
+
`Author the baseline (\`loam adopt --service ${service}\` briefs it); this check passes once the required artifact set exists.`,
|
|
102
|
+
details: target.gaps,
|
|
103
|
+
text: { detailPrefix: "- " },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// The partners that HAVE a directory, by id: their findings name it, and it
|
|
107
|
+
// is `services/<subsystem>/<id>/` for a filed one. The no-directory branch
|
|
108
|
+
// below keeps the bare `services/<id>/` spelling deliberately — there is no
|
|
109
|
+
// directory to name, and the unfiled form is what the fix would create.
|
|
110
|
+
const partnerPath = new Map(scan.partnerEntries.map((e) => [e.id, serviceTreePath(e)]));
|
|
111
|
+
for (const p of scan.partners) {
|
|
112
|
+
// An `#external` partner is somebody else's system: its side of the join
|
|
113
|
+
// is unrecorded ON PURPOSE, the same exemption the landscape census grants
|
|
114
|
+
// (`landscape.service-undocumented` skips #external too).
|
|
115
|
+
if (p.external)
|
|
116
|
+
continue;
|
|
117
|
+
if (p.maturity === null) {
|
|
118
|
+
findings.push({
|
|
119
|
+
severity: "warn",
|
|
120
|
+
code: "gate.partner-undocumented",
|
|
121
|
+
subject: p.service,
|
|
122
|
+
message: `'${p.service}' joins '${service}' (${p.via.join(", ")}) but has no services/${p.service}/ directory — its side of the join is unrecorded. ` +
|
|
123
|
+
`Adopt it, bind its landscape element to the right service with metadata { service '<id>' }, or tag it #external if it is not ours.`,
|
|
124
|
+
details: p.via,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else if (!atLeastDocumented(p.maturity)) {
|
|
128
|
+
findings.push({
|
|
129
|
+
severity: "warn",
|
|
130
|
+
code: "gate.partner-undocumented",
|
|
131
|
+
subject: p.service,
|
|
132
|
+
message: `${partnerPath.get(p.service) ?? `services/${p.service}`}/ sits at '${p.maturity}' — below 'documented' — while it joins '${service}' (${p.via.join(", ")}): its side of the join is not fully recorded. \`loam adopt --service ${p.service}\` briefs what is missing.`,
|
|
133
|
+
details: p.via,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return findings;
|
|
138
|
+
}
|
|
139
|
+
/* ------------------------------------------------------------------ */
|
|
140
|
+
/* Check 2: freshness */
|
|
141
|
+
/* ------------------------------------------------------------------ */
|
|
142
|
+
/**
|
|
143
|
+
* `serviceProvenance` filtered to the two staleness codes plus the ONE
|
|
144
|
+
* integrity error that makes freshness unjudgeable: `frontmatter.malformed`.
|
|
145
|
+
* A header YAML refuses to parse cannot certify a `content_digest`, and
|
|
146
|
+
* dropping that error read "cannot judge" as "nothing stale" — the exact
|
|
147
|
+
* fail-open a deploy gate must not have. Everything else provenance grades
|
|
148
|
+
* (missing owners, unstamped sources, absent frontmatter) is `validate`'s
|
|
149
|
+
* report to make — a deploy gate that repeated it would drown the findings a
|
|
150
|
+
* pipeline can actually act on at deploy time.
|
|
151
|
+
*/
|
|
152
|
+
async function freshnessFindings(req, scan) {
|
|
153
|
+
const subjects = [
|
|
154
|
+
{
|
|
155
|
+
id: req.service,
|
|
156
|
+
repoDir: req.repoDir,
|
|
157
|
+
paths: await locateServicePaths(req.docsDir, req.service, req.fleet),
|
|
158
|
+
},
|
|
159
|
+
// Only the DOCUMENTED partners: a partner with no directory has nothing to
|
|
160
|
+
// grade, and already carries gate.partner-undocumented from check 1.
|
|
161
|
+
...scan.partnerEntries.map((e) => ({
|
|
162
|
+
id: e.id,
|
|
163
|
+
repoDir: undefined,
|
|
164
|
+
paths: servicePathsAt(e.dir),
|
|
165
|
+
})),
|
|
166
|
+
];
|
|
167
|
+
// Independent per-subject reads through the shared memo; inOrder keeps the
|
|
168
|
+
// findings in subject order (target first, partners sorted) so identical
|
|
169
|
+
// state yields identical bytes.
|
|
170
|
+
const graded = await inOrder(subjects, async (s) => {
|
|
171
|
+
try {
|
|
172
|
+
// The integrity gate serviceProvenance cannot give: its own reads decode
|
|
173
|
+
// with 'utf8', which silently turns UTF-16 bytes into replacement
|
|
174
|
+
// characters — the file then grades as having no frontmatter at all (a
|
|
175
|
+
// warning this check does not retain), and mangled bytes read as
|
|
176
|
+
// "nothing stale". So both spec axes are decoded FIRST through the
|
|
177
|
+
// invocation's read index, whose decoder refuses non-UTF-8 naming the
|
|
178
|
+
// file (NotUtf8DocumentError spells `path` the way Node does, so the
|
|
179
|
+
// containment below names it without knowing the type).
|
|
180
|
+
for (const axis of SPEC_AXES) {
|
|
181
|
+
const path = s.paths[axis.key];
|
|
182
|
+
if (existsSync(path))
|
|
183
|
+
await req.fleet.readText(path);
|
|
184
|
+
}
|
|
185
|
+
const all = await serviceProvenance(req.docsDir, s.id, { repoDir: s.repoDir, fleet: req.fleet });
|
|
186
|
+
return all
|
|
187
|
+
.filter((f) => f.code === "content.stale" ||
|
|
188
|
+
f.code === "sources.stale" ||
|
|
189
|
+
f.code === "frontmatter.malformed")
|
|
190
|
+
// The staleness emitters name the file in the message but set no
|
|
191
|
+
// subject; the payload's per-service attribution should not make a
|
|
192
|
+
// caller parse it back out of prose.
|
|
193
|
+
.map((f) => (f.subject === undefined ? { ...f, subject: s.id } : f));
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
return [unreadableSubject("service", s.id, err)];
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return graded.flat();
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../../src/core/gate/checks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The four checks behind `loam gate` — a can-i-deploy-shaped PURE QUERY over\n * evidence other commands already recorded. Nothing here executes anything,\n * writes anything, takes the docs lock, or re-derives a coherence plan: every\n * answer is either already on disk (verification.yaml, vouch stamps, the\n * commit journal) or a presence/landscape fact the enumeration carries.\n *\n * partners the landscape's direct joins, both ends graded on the ladder\n * freshness content.stale / sources.stale over the target and partners,\n * plus the one integrity error that makes freshness\n * unjudgeable (frontmatter.malformed) and a refusing decode\n * of the spec bytes themselves\n * verification verificationState per active feature touching the service\n * (its own module, `./verification.ts`)\n * interrupted the `.loam-commit` journal — the one repo state that poisons\n * every other answer above\n *\n * Every `gate.*` code is spelled as a literal `code:` property at its emit\n * site, never through a code-taking factory parameter — verifyStep\n * (core/status/verification.ts) records that test/codes-drift.test.ts's\n * collector is blind to the parameter form, and a refactor toward a factory\n * would silently undocument the family.\n */\nimport { existsSync } from \"node:fs\";\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport { serviceProvenance } from \"../provenance/findings.js\";\nimport { interruptedCommitFinding } from \"../staging/recovery/finding.js\";\nimport { servicePathsAt, SPEC_AXES, type ServicePaths } from \"../repo/paths.js\";\nimport { serviceTreePath } from \"../kernel/ids/dirs.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport { atLeastDocumented, partnerScan, type PartnerScan } from \"./partners.js\";\nimport { verificationCheck } from \"./verification.js\";\nimport { unreadableSubject, type GateCheck, type GateReport } from \"./report.js\";\n\nexport interface GateRequest {\n docsDir: DocsDir;\n /** The service being deployed — a name the enumeration approved. */\n service: RawServiceId;\n /**\n * The repository loam is standing in, passed ONLY when the gated service is\n * this repo's own binding — validate's `repoOf` rule: `sources` are paths\n * into a service's own repository, so `sources.stale` is honestly checkable\n * only there, while `content.stale` needs nothing but the document.\n */\n repoDir?: string;\n fleet: FleetContext;\n}\n\nexport async function gateReport(req: GateRequest): Promise<GateReport> {\n // The scan first, alone: freshness walks the partner set it derives. The\n // three remaining checks are independent of each other — different files,\n // memoized reads — so they run concurrently; only the scan must be ordered.\n const scan = await partnerScan({ docsDir: req.docsDir, service: req.service, fleet: req.fleet });\n const [freshness, verification, interrupted] = await Promise.all([\n freshnessFindings(req, scan),\n verificationCheck({ docsDir: req.docsDir, service: req.service, fleet: req.fleet }),\n interruptedCommitFinding(req.docsDir),\n ]);\n const checks: GateCheck[] = [\n { check: \"partners\", findings: partnersFindings(scan, req.service) },\n { check: \"freshness\", findings: freshness },\n { check: \"verification\", findings: verification.findings },\n { check: \"interrupted\", findings: interrupted === null ? [] : [interrupted] },\n ];\n return {\n service: req.service,\n landscape: scan.landscape,\n partners: scan.partners,\n features: verification.features,\n checks,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Check 1: partners */\n/* ------------------------------------------------------------------ */\n\nfunction partnersFindings(scan: PartnerScan, service: string): Finding[] {\n const findings: Finding[] = [];\n // \"Could not look\" must never read as \"no partners\" — the docsRepoReady\n // doctrine one level down. `validate --all` owns the diagnosis of the\n // landscape itself; this code only refuses the false silence. The two ways\n // of not looking are graded apart, the way `landscape.missing` and\n // `landscape.invalid` are: ABSENT is a warning — a repo before its first\n // adopt legitimately has no map yet — but a map that EXISTS and cannot be\n // parsed or read is an ERROR, because `validate --all` fails that repo\n // (`landscape.invalid`) and a deploy gate that passed where the fleet gate\n // fails would be the quieter of two contradictory verdicts.\n if (scan.landscape === \"absent\") {\n findings.push({\n severity: \"warn\",\n code: \"gate.partners-unknown\",\n message: `architecture/landscape.likec4 does not exist, so the partner set for '${service}' could not be derived — an absent map means nobody could look, not that nothing joins this service. \\`loam validate --all\\` diagnoses the landscape itself.`,\n });\n } else if (scan.landscape === \"invalid\") {\n findings.push({\n severity: \"error\",\n code: \"gate.partners-unknown\",\n message:\n `architecture/landscape.likec4 exists but cannot be used — ${scan.landscapeProblem ?? \"it did not parse\"} — so the partner set for '${service}' could not be derived. ` +\n `A broken map means nobody could look, not that nothing joins this service, and \\`loam validate --all\\` fails this repo for the same file. Fix the landscape, then re-run.`,\n });\n }\n // The rung depends on the landscape only through the api question (does\n // anything call this service?). With no readable map that question is\n // unanswerable, and the fail-closed grading — `landscapeEvidence`: no proof\n // means an API is expected — would convict a worker or UI for a contract\n // nobody may be owed. One unanswerable fact yields ONE finding (the\n // landscape's own, above), so under an unreadable map this error fires only\n // when the service is below `documented` REGARDLESS of the api question.\n const target = scan.landscape === \"read\" ? scan.target : scan.targetWithoutApi;\n if (!atLeastDocumented(target.maturity)) {\n findings.push({\n severity: \"error\",\n code: \"gate.service-undocumented\",\n subject: service,\n message:\n `${serviceTreePath(scan.targetEntry)}/ sits at '${target.maturity}' on the adoption ladder — below 'documented', the docs cannot say what its joins even are, so no recorded evidence can answer the deploy question. ` +\n `Author the baseline (\\`loam adopt --service ${service}\\` briefs it); this check passes once the required artifact set exists.`,\n details: target.gaps,\n text: { detailPrefix: \"- \" },\n });\n }\n // The partners that HAVE a directory, by id: their findings name it, and it\n // is `services/<subsystem>/<id>/` for a filed one. The no-directory branch\n // below keeps the bare `services/<id>/` spelling deliberately — there is no\n // directory to name, and the unfiled form is what the fix would create.\n const partnerPath = new Map(scan.partnerEntries.map((e) => [e.id as string, serviceTreePath(e)]));\n for (const p of scan.partners) {\n // An `#external` partner is somebody else's system: its side of the join\n // is unrecorded ON PURPOSE, the same exemption the landscape census grants\n // (`landscape.service-undocumented` skips #external too).\n if (p.external) continue;\n if (p.maturity === null) {\n findings.push({\n severity: \"warn\",\n code: \"gate.partner-undocumented\",\n subject: p.service,\n message:\n `'${p.service}' joins '${service}' (${p.via.join(\", \")}) but has no services/${p.service}/ directory — its side of the join is unrecorded. ` +\n `Adopt it, bind its landscape element to the right service with metadata { service '<id>' }, or tag it #external if it is not ours.`,\n details: p.via,\n });\n } else if (!atLeastDocumented(p.maturity)) {\n findings.push({\n severity: \"warn\",\n code: \"gate.partner-undocumented\",\n subject: p.service,\n message:\n `${partnerPath.get(p.service) ?? `services/${p.service}`}/ sits at '${p.maturity}' — below 'documented' — while it joins '${service}' (${p.via.join(\", \")}): its side of the join is not fully recorded. \\`loam adopt --service ${p.service}\\` briefs what is missing.`,\n details: p.via,\n });\n }\n }\n return findings;\n}\n\n/* ------------------------------------------------------------------ */\n/* Check 2: freshness */\n/* ------------------------------------------------------------------ */\n\n/**\n * `serviceProvenance` filtered to the two staleness codes plus the ONE\n * integrity error that makes freshness unjudgeable: `frontmatter.malformed`.\n * A header YAML refuses to parse cannot certify a `content_digest`, and\n * dropping that error read \"cannot judge\" as \"nothing stale\" — the exact\n * fail-open a deploy gate must not have. Everything else provenance grades\n * (missing owners, unstamped sources, absent frontmatter) is `validate`'s\n * report to make — a deploy gate that repeated it would drown the findings a\n * pipeline can actually act on at deploy time.\n */\nasync function freshnessFindings(req: GateRequest, scan: PartnerScan): Promise<Finding[]> {\n const subjects: { id: RawServiceId; repoDir: string | undefined; paths: ServicePaths }[] = [\n {\n id: req.service,\n repoDir: req.repoDir,\n paths: await locateServicePaths(req.docsDir, req.service, req.fleet),\n },\n // Only the DOCUMENTED partners: a partner with no directory has nothing to\n // grade, and already carries gate.partner-undocumented from check 1.\n ...scan.partnerEntries.map((e) => ({\n id: e.id,\n repoDir: undefined,\n paths: servicePathsAt(e.dir),\n })),\n ];\n // Independent per-subject reads through the shared memo; inOrder keeps the\n // findings in subject order (target first, partners sorted) so identical\n // state yields identical bytes.\n const graded = await inOrder(subjects, async (s) => {\n try {\n // The integrity gate serviceProvenance cannot give: its own reads decode\n // with 'utf8', which silently turns UTF-16 bytes into replacement\n // characters — the file then grades as having no frontmatter at all (a\n // warning this check does not retain), and mangled bytes read as\n // \"nothing stale\". So both spec axes are decoded FIRST through the\n // invocation's read index, whose decoder refuses non-UTF-8 naming the\n // file (NotUtf8DocumentError spells `path` the way Node does, so the\n // containment below names it without knowing the type).\n for (const axis of SPEC_AXES) {\n const path = s.paths[axis.key];\n if (existsSync(path)) await req.fleet.readText(path);\n }\n const all = await serviceProvenance(req.docsDir, s.id, { repoDir: s.repoDir, fleet: req.fleet });\n return all\n .filter(\n (f) =>\n f.code === \"content.stale\" ||\n f.code === \"sources.stale\" ||\n f.code === \"frontmatter.malformed\",\n )\n // The staleness emitters name the file in the message but set no\n // subject; the payload's per-service attribution should not make a\n // caller parse it back out of prose.\n .map((f) => (f.subject === undefined ? { ...f, subject: s.id } : f));\n } catch (err) {\n return [unreadableSubject(\"service\", s.id, err)];\n }\n });\n return graded.flat();\n}\n"]}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The direct join partners of one service, derived from the LIVING landscape,
|
|
3
|
+
* and the maturity grading of everything on either end of a join.
|
|
4
|
+
*
|
|
5
|
+
* The landscape is the only place the fleet's joins are drawn, so the partner
|
|
6
|
+
* set is exactly the edges whose resolved source or target is the gated
|
|
7
|
+
* service — resolved through `serviceResolver` with the ENUMERATED fleet
|
|
8
|
+
* riding in, the same ride that keeps container-modelling landscapes attached
|
|
9
|
+
* everywhere else (an edge into `paymentService.api` is an edge into the
|
|
10
|
+
* service that owns it). A landscape that is absent or does not parse yields
|
|
11
|
+
* NO partner set, never an empty one: `landscape` says which, and the check
|
|
12
|
+
* layer turns "could not look" into `gate.partners-unknown` rather than
|
|
13
|
+
* letting it read as "no partners".
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import {} from "../c4/likec4.js";
|
|
17
|
+
import { serviceResolver } from "../c4/resolve/service.js";
|
|
18
|
+
import { ACTOR_KINDS, EXTERNAL_TAG, landscapeEvidence, maturityGaps, serviceMaturity, MATURITY_LADDER, } from "../vocabulary/maturity.js";
|
|
19
|
+
import { compareIds } from "../repo/entries.js";
|
|
20
|
+
import { landscapePath, servicePathsAt } from "../repo/paths.js";
|
|
21
|
+
import { enumeratedServices } from "../repo/service-target.js";
|
|
22
|
+
/** Is this rung at or above `documented` — the fixed threshold check 1 grades against? */
|
|
23
|
+
export function atLeastDocumented(m) {
|
|
24
|
+
return MATURITY_LADDER.indexOf(m) >= MATURITY_LADDER.indexOf("documented");
|
|
25
|
+
}
|
|
26
|
+
/** The id itself, then each dotted ancestor — the same walk `serviceResolver` resolves through. */
|
|
27
|
+
function selfAndAncestors(id) {
|
|
28
|
+
const out = [id];
|
|
29
|
+
for (let dot = id.lastIndexOf("."); dot !== -1; dot = id.lastIndexOf(".", dot - 1)) {
|
|
30
|
+
out.push(id.slice(0, dot));
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* What the map says about one edge END: does it stand for a person, and is it
|
|
36
|
+
* tagged `#external`? Walked up the ancestor chain because both facts are
|
|
37
|
+
* usually written on the containing system while the edge is drawn from a
|
|
38
|
+
* nested element — but the walk STOPS at the first element that stands for a
|
|
39
|
+
* service (an explicit binding or a title naming a real directory,
|
|
40
|
+
* `serviceResolver`'s own first two rungs, checked after that element's own
|
|
41
|
+
* facts are read). What sits above the service boundary is a grouping, and a
|
|
42
|
+
* grouping's `#external` is the grouping's claim, not the service's: without
|
|
43
|
+
* the stop, an adopted service drawn inside an `#external` zone was exempted
|
|
44
|
+
* from `gate.partner-undocumented` and its real rung hidden.
|
|
45
|
+
*/
|
|
46
|
+
function endFacts(byId, known, id) {
|
|
47
|
+
let actor = false;
|
|
48
|
+
let external = false;
|
|
49
|
+
for (const candidate of selfAndAncestors(id)) {
|
|
50
|
+
const e = byId.get(candidate);
|
|
51
|
+
if (e === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
if (ACTOR_KINDS.has(e.kind.toLowerCase()))
|
|
54
|
+
actor = true;
|
|
55
|
+
if (e.tags.includes(EXTERNAL_TAG))
|
|
56
|
+
external = true;
|
|
57
|
+
if (e.service !== undefined || known.has(e.title))
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
return { actor, external };
|
|
61
|
+
}
|
|
62
|
+
/** How one edge joins the two ends, in the words the finding and the payload both use. */
|
|
63
|
+
function viaOf(r) {
|
|
64
|
+
const typed = [
|
|
65
|
+
...(r.op === undefined ? [] : [`operation ${r.op}`]),
|
|
66
|
+
...(r.publishes === undefined ? [] : [`message ${r.publishes}`]),
|
|
67
|
+
...(r.consumes === undefined ? [] : [`message ${r.consumes}`]),
|
|
68
|
+
];
|
|
69
|
+
if (typed.length > 0)
|
|
70
|
+
return typed;
|
|
71
|
+
return [r.title === undefined ? "untyped edge" : `edge '${r.title}'`];
|
|
72
|
+
}
|
|
73
|
+
export async function partnerScan(req) {
|
|
74
|
+
const { docsDir, service, fleet } = req;
|
|
75
|
+
// The same memoized enumeration the command's resolveServiceTarget already
|
|
76
|
+
// performed on this FleetContext — one walk answers both questions.
|
|
77
|
+
const entries = await enumeratedServices(docsDir, fleet);
|
|
78
|
+
// Keyed as plain strings on purpose: the lookups below arrive from the
|
|
79
|
+
// resolver, whose names are document-asserted, and only a hit proves a
|
|
80
|
+
// directory. The VALUES keep the enumeration's provenance.
|
|
81
|
+
const entryById = new Map(entries.map((e) => [e.id, e]));
|
|
82
|
+
const known = new Set(entries.map((e) => e.id));
|
|
83
|
+
const lp = landscapePath(docsDir);
|
|
84
|
+
let landscape = "absent";
|
|
85
|
+
let landscapeProblem = null;
|
|
86
|
+
let elements = [];
|
|
87
|
+
let relationships = [];
|
|
88
|
+
if (existsSync(lp)) {
|
|
89
|
+
try {
|
|
90
|
+
const doc = await fleet.loadLikeC4(lp);
|
|
91
|
+
if (doc.errors.length > 0) {
|
|
92
|
+
landscape = "invalid";
|
|
93
|
+
const first = doc.errors[0];
|
|
94
|
+
landscapeProblem =
|
|
95
|
+
`${doc.errors.length} parse error(s)` +
|
|
96
|
+
(first === undefined ? "" : ` — first: ${first.message}`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
landscape = "read";
|
|
100
|
+
elements = doc.elements;
|
|
101
|
+
relationships = doc.relationships;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
// A landscape that could not be READ, graded as one that did not PARSE —
|
|
106
|
+
// commands/validate/fleet/load.ts's containment doctrine, spelled here
|
|
107
|
+
// because core cannot import it: "could not be read" and "did not parse"
|
|
108
|
+
// have the same consequence (nothing may be concluded from this file).
|
|
109
|
+
// The reason travels into the finding, message-preserving as
|
|
110
|
+
// `unreadableLandscape` is; the read error must not kill the run — the
|
|
111
|
+
// other three checks still have answers.
|
|
112
|
+
landscape = "invalid";
|
|
113
|
+
landscapeProblem = err instanceof Error ? err.message : String(err);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const parses = landscape === "read";
|
|
117
|
+
const svcOf = parses
|
|
118
|
+
? serviceResolver(elements, known)
|
|
119
|
+
: (id) => id;
|
|
120
|
+
const elementIds = elements.map((e) => e.id);
|
|
121
|
+
// One service's ladder rung. `landscapeEvidence` is the ONE shared spelling
|
|
122
|
+
// of the positive-evidence apiExpected rule (core/vocabulary/maturity.ts);
|
|
123
|
+
// its own docblock warns against calling it per service inside a FLEET loop,
|
|
124
|
+
// and this loop is not one — it runs over the gated service and its direct
|
|
125
|
+
// partners, a handful, never the enumeration.
|
|
126
|
+
const gradeWith = (entry, apiExpected) => {
|
|
127
|
+
const input = {
|
|
128
|
+
entry,
|
|
129
|
+
archSpec: existsSync(servicePathsAt(entry.dir).archSpec),
|
|
130
|
+
apiExpected,
|
|
131
|
+
};
|
|
132
|
+
return { maturity: serviceMaturity(input), gaps: maturityGaps(input) };
|
|
133
|
+
};
|
|
134
|
+
const grade = (entry) => gradeWith(entry, landscapeEvidence({ id: entry.id, parses, relationships, elementIds, svcOf }).apiExpected);
|
|
135
|
+
// Unreachable by construction: the command layer resolved `service` against
|
|
136
|
+
// the same memoized enumeration this scan just read, and refused
|
|
137
|
+
// `unknown-service` when no directory answered. Failing closed here beats
|
|
138
|
+
// grading a service nobody enumerated as if its absence were a rung.
|
|
139
|
+
const targetEntry = entryById.get(service);
|
|
140
|
+
if (targetEntry === undefined) {
|
|
141
|
+
throw new Error(`gate: '${service}' vanished from the enumeration between resolve and scan`);
|
|
142
|
+
}
|
|
143
|
+
const byId = new Map(elements.map((e) => [e.id, e]));
|
|
144
|
+
const acc = new Map();
|
|
145
|
+
const join = (name, role, r, external) => {
|
|
146
|
+
const slot = acc.get(name) ?? { consumer: false, provider: false, external: false, via: new Set() };
|
|
147
|
+
if (role === "consumer")
|
|
148
|
+
slot.consumer = true;
|
|
149
|
+
else
|
|
150
|
+
slot.provider = true;
|
|
151
|
+
slot.external = slot.external || external;
|
|
152
|
+
for (const v of viaOf(r))
|
|
153
|
+
slot.via.add(v);
|
|
154
|
+
acc.set(name, slot);
|
|
155
|
+
};
|
|
156
|
+
for (const r of relationships) {
|
|
157
|
+
const source = svcOf(r.source);
|
|
158
|
+
const target = svcOf(r.target);
|
|
159
|
+
// A self-edge (both ends resolve to the gated service — its own containers
|
|
160
|
+
// talking to each other) names no partner.
|
|
161
|
+
if (source === target)
|
|
162
|
+
continue;
|
|
163
|
+
if (target === service) {
|
|
164
|
+
const facts = endFacts(byId, known, r.source);
|
|
165
|
+
// A person is never a deploy participant: the actor exemption every
|
|
166
|
+
// landscape census applies (drawnSystems, commands/validate/fleet/census.ts).
|
|
167
|
+
if (!facts.actor)
|
|
168
|
+
join(source, "consumer", r, facts.external);
|
|
169
|
+
}
|
|
170
|
+
else if (source === service) {
|
|
171
|
+
const facts = endFacts(byId, known, r.target);
|
|
172
|
+
if (!facts.actor)
|
|
173
|
+
join(target, "provider", r, facts.external);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Sorted by id so identical state yields identical bytes — the accumulator's
|
|
177
|
+
// order is the landscape's edge order, which a reformat may shuffle.
|
|
178
|
+
const joined = [...acc.entries()].sort((a, b) => compareIds(a[0], b[0]));
|
|
179
|
+
const partners = joined.map(([name, slot]) => {
|
|
180
|
+
const entry = entryById.get(name);
|
|
181
|
+
return {
|
|
182
|
+
service: name,
|
|
183
|
+
maturity: entry === undefined ? null : grade(entry).maturity,
|
|
184
|
+
role: slot.consumer && slot.provider ? "both" : slot.consumer ? "consumer" : "provider",
|
|
185
|
+
via: [...slot.via].sort(),
|
|
186
|
+
// An adopted service is OURS whatever a zone's tag says: `external` and
|
|
187
|
+
// a maturity rung together must be unrepresentable, or the rung the
|
|
188
|
+
// payload carries is one the exemption hides and the human view drops.
|
|
189
|
+
external: entry === undefined && slot.external,
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
landscape,
|
|
194
|
+
landscapeProblem,
|
|
195
|
+
targetEntry,
|
|
196
|
+
target: grade(targetEntry),
|
|
197
|
+
targetWithoutApi: gradeWith(targetEntry, false),
|
|
198
|
+
partners,
|
|
199
|
+
partnerEntries: joined.flatMap(([name]) => {
|
|
200
|
+
const entry = entryById.get(name);
|
|
201
|
+
return entry === undefined ? [] : [entry];
|
|
202
|
+
}),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=partners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partners.js","sourceRoot":"","sources":["../../../src/core/gate/partners.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAuB,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EACL,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,eAAe,GAEhB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,UAAU,EAAqB,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AA0D/D,0FAA0F;AAC1F,MAAM,UAAU,iBAAiB,CAAC,CAAW;IAC3C,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;AAC7E,CAAC;AAED,mGAAmG;AACnG,SAAS,gBAAgB,CAAC,EAAU;IAClC,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;IACjB,KAAK,IAAI,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;QACnF,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,QAAQ,CACf,IAAuB,EACvB,KAA0B,EAC1B,EAAU;IAEV,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,KAAK,MAAM,SAAS,IAAI,gBAAgB,CAAC,EAAE,CAAC,EAAE,CAAC;QAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,SAAS;YAAE,SAAS;QAC9B,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAAE,KAAK,GAAG,IAAI,CAAC;QACxD,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,QAAQ,GAAG,IAAI,CAAC;QACnD,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;YAAE,MAAM;IAC3D,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED,0FAA0F;AAC1F,SAAS,KAAK,CAAC,CAAM;IACnB,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpD,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAChE,GAAG,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;KAC/D,CAAC;IACF,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;AACxE,CAAC;AASD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAuB;IACvD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC;IACxC,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACzD,uEAAuE;IACvE,uEAAuE;IACvE,2DAA2D;IAC3D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAuB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAErE,MAAM,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,SAAS,GAAkB,QAAQ,CAAC;IACxC,IAAI,gBAAgB,GAAkB,IAAI,CAAC;IAC3C,IAAI,QAAQ,GAAW,EAAE,CAAC;IAC1B,IAAI,aAAa,GAAU,EAAE,CAAC;IAC9B,IAAI,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YACvC,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,SAAS,GAAG,SAAS,CAAC;gBACtB,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC5B,gBAAgB;oBACd,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,iBAAiB;wBACrC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9D,CAAC;iBAAM,CAAC;gBACN,SAAS,GAAG,MAAM,CAAC;gBACnB,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;gBACxB,aAAa,GAAG,GAAG,CAAC,aAAa,CAAC;YACpC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,yEAAyE;YACzE,uEAAuE;YACvE,yEAAyE;YACzE,uEAAuE;YACvE,6DAA6D;YAC7D,uEAAuE;YACvE,yCAAyC;YACzC,SAAS,GAAG,SAAS,CAAC;YACtB,gBAAgB,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,KAAK,MAAM,CAAC;IACpC,MAAM,KAAK,GAA2B,MAAM;QAC1C,CAAC,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC;QAClC,CAAC,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,EAAE,CAAC;IACvB,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAE7C,4EAA4E;IAC5E,2EAA2E;IAC3E,6EAA6E;IAC7E,2EAA2E;IAC3E,8CAA8C;IAC9C,MAAM,SAAS,GAAG,CAAC,KAAmB,EAAE,WAAoB,EAAiB,EAAE;QAC7E,MAAM,KAAK,GAAG;YACZ,KAAK;YACL,QAAQ,EAAE,UAAU,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;YACxD,WAAW;SACZ,CAAC;QACF,OAAO,EAAE,QAAQ,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;IACzE,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,KAAmB,EAAiB,EAAE,CACnD,SAAS,CACP,KAAK,EACL,iBAAiB,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,WAAW,CAC1F,CAAC;IAEJ,4EAA4E;IAC5E,iEAAiE;IACjE,0EAA0E;IAC1E,qEAAqE;IACrE,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,UAAU,OAAO,0DAA0D,CAAC,CAAC;IAC/F,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,IAAI,GAAG,EAA8B,CAAC;IAClD,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,IAAiB,EAAE,CAAM,EAAE,QAAiB,EAAQ,EAAE;QAChF,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,GAAG,EAAU,EAAE,CAAC;QAC5G,IAAI,IAAI,KAAK,UAAU;YAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;;YACzC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC1C,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACtB,CAAC,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC/B,2EAA2E;QAC3E,2CAA2C;QAC3C,IAAI,MAAM,KAAK,MAAM;YAAE,SAAS;QAChC,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;YAC9C,oEAAoE;YACpE,8EAA8E;YAC9E,IAAI,CAAC,KAAK,CAAC,KAAK;gBAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChE,CAAC;aAAM,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;YAC9C,IAAI,CAAC,KAAK,CAAC,KAAK;gBAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,qEAAqE;IACrE,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAkB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;QAC1D,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,OAAO;YACL,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ;YAC5D,IAAI,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;YACvF,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE;YACzB,wEAAwE;YACxE,oEAAoE;YACpE,uEAAuE;YACvE,QAAQ,EAAE,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ;SAC/C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,SAAS;QACT,gBAAgB;QAChB,WAAW;QACX,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC;QAC1B,gBAAgB,EAAE,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC;QAC/C,QAAQ;QACR,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE;YACxC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC,CAAC;KACH,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The direct join partners of one service, derived from the LIVING landscape,\n * and the maturity grading of everything on either end of a join.\n *\n * The landscape is the only place the fleet's joins are drawn, so the partner\n * set is exactly the edges whose resolved source or target is the gated\n * service — resolved through `serviceResolver` with the ENUMERATED fleet\n * riding in, the same ride that keeps container-modelling landscapes attached\n * everywhere else (an edge into `paymentService.api` is an edge into the\n * service that owns it). A landscape that is absent or does not parse yields\n * NO partner set, never an empty one: `landscape` says which, and the check\n * layer turns \"could not look\" into `gate.partners-unknown` rather than\n * letting it read as \"no partners\".\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem, type Rel } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport {\n ACTOR_KINDS,\n EXTERNAL_TAG,\n landscapeEvidence,\n maturityGaps,\n serviceMaturity,\n MATURITY_LADDER,\n type Maturity,\n} from \"../vocabulary/maturity.js\";\nimport { compareIds, type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../repo/paths.js\";\nimport { enumeratedServices } from \"../repo/service-target.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport type { GatePartner, LandscapeRead, PartnerRole } from \"./report.js\";\n\nexport interface PartnerScanRequest {\n docsDir: DocsDir;\n /** The gated service — a name the enumeration approved (`resolveServiceTarget`). */\n service: RawServiceId;\n fleet: FleetContext;\n}\n\n/** One service graded on the adoption ladder, with what the next rung wants. */\nexport interface GradedService {\n maturity: Maturity;\n gaps: string[];\n}\n\nexport interface PartnerScan {\n landscape: LandscapeRead;\n /**\n * Why the landscape graded `invalid` — the parse-error tally or the read\n * failure's own words. Null on `read` and `absent`; the finding that fails\n * the gate must say what broke, not merely that something did.\n */\n landscapeProblem: string | null;\n /**\n * The gated service's own rung, graded under the landscape's evidence —\n * `gate.service-undocumented`'s subject. Fail-closed on the api question:\n * with no readable map, `landscapeEvidence` assumes an API is expected.\n */\n target: GradedService;\n /**\n * The same service graded with NO api expected of it — the reading that\n * does not depend on the landscape at all. The check layer answers from\n * THIS one when the map is absent or broken: the api question is then\n * unanswerable, and one unanswerable fact must not convict twice (the\n * landscape already carries its own finding).\n */\n targetWithoutApi: GradedService;\n /**\n * The gated service's own enumerated entry — carried so a finding can name\n * the directory it actually occupies. `services/<id>/` is right only for an\n * unfiled fleet, and a check that sends a reader to a directory they cannot\n * open has answered worse than not at all.\n */\n targetEntry: ServiceEntry;\n partners: GatePartner[];\n /**\n * The enumerated entries behind the partners that HAVE a `services/`\n * directory, in `partners` order — what the freshness check walks. Kept as\n * entries rather than names so the ids that travel on carry the\n * enumeration's provenance (`RawServiceId`), never a document's.\n */\n partnerEntries: ServiceEntry[];\n}\n\n/** Is this rung at or above `documented` — the fixed threshold check 1 grades against? */\nexport function atLeastDocumented(m: Maturity): boolean {\n return MATURITY_LADDER.indexOf(m) >= MATURITY_LADDER.indexOf(\"documented\");\n}\n\n/** The id itself, then each dotted ancestor — the same walk `serviceResolver` resolves through. */\nfunction selfAndAncestors(id: string): string[] {\n const out = [id];\n for (let dot = id.lastIndexOf(\".\"); dot !== -1; dot = id.lastIndexOf(\".\", dot - 1)) {\n out.push(id.slice(0, dot));\n }\n return out;\n}\n\n/**\n * What the map says about one edge END: does it stand for a person, and is it\n * tagged `#external`? Walked up the ancestor chain because both facts are\n * usually written on the containing system while the edge is drawn from a\n * nested element — but the walk STOPS at the first element that stands for a\n * service (an explicit binding or a title naming a real directory,\n * `serviceResolver`'s own first two rungs, checked after that element's own\n * facts are read). What sits above the service boundary is a grouping, and a\n * grouping's `#external` is the grouping's claim, not the service's: without\n * the stop, an adopted service drawn inside an `#external` zone was exempted\n * from `gate.partner-undocumented` and its real rung hidden.\n */\nfunction endFacts(\n byId: Map<string, Elem>,\n known: ReadonlySet<string>,\n id: string,\n): { actor: boolean; external: boolean } {\n let actor = false;\n let external = false;\n for (const candidate of selfAndAncestors(id)) {\n const e = byId.get(candidate);\n if (e === undefined) continue;\n if (ACTOR_KINDS.has(e.kind.toLowerCase())) actor = true;\n if (e.tags.includes(EXTERNAL_TAG)) external = true;\n if (e.service !== undefined || known.has(e.title)) break;\n }\n return { actor, external };\n}\n\n/** How one edge joins the two ends, in the words the finding and the payload both use. */\nfunction viaOf(r: Rel): string[] {\n const typed = [\n ...(r.op === undefined ? [] : [`operation ${r.op}`]),\n ...(r.publishes === undefined ? [] : [`message ${r.publishes}`]),\n ...(r.consumes === undefined ? [] : [`message ${r.consumes}`]),\n ];\n if (typed.length > 0) return typed;\n return [r.title === undefined ? \"untyped edge\" : `edge '${r.title}'`];\n}\n\ninterface PartnerAccumulator {\n consumer: boolean;\n provider: boolean;\n external: boolean;\n via: Set<string>;\n}\n\nexport async function partnerScan(req: PartnerScanRequest): Promise<PartnerScan> {\n const { docsDir, service, fleet } = req;\n // The same memoized enumeration the command's resolveServiceTarget already\n // performed on this FleetContext — one walk answers both questions.\n const entries = await enumeratedServices(docsDir, fleet);\n // Keyed as plain strings on purpose: the lookups below arrive from the\n // resolver, whose names are document-asserted, and only a hit proves a\n // directory. The VALUES keep the enumeration's provenance.\n const entryById = new Map<string, ServiceEntry>(entries.map((e) => [e.id, e]));\n const known: ReadonlySet<string> = new Set(entries.map((e) => e.id));\n\n const lp = landscapePath(docsDir);\n let landscape: LandscapeRead = \"absent\";\n let landscapeProblem: string | null = null;\n let elements: Elem[] = [];\n let relationships: Rel[] = [];\n if (existsSync(lp)) {\n try {\n const doc = await fleet.loadLikeC4(lp);\n if (doc.errors.length > 0) {\n landscape = \"invalid\";\n const first = doc.errors[0];\n landscapeProblem =\n `${doc.errors.length} parse error(s)` +\n (first === undefined ? \"\" : ` — first: ${first.message}`);\n } else {\n landscape = \"read\";\n elements = doc.elements;\n relationships = doc.relationships;\n }\n } catch (err) {\n // A landscape that could not be READ, graded as one that did not PARSE —\n // commands/validate/fleet/load.ts's containment doctrine, spelled here\n // because core cannot import it: \"could not be read\" and \"did not parse\"\n // have the same consequence (nothing may be concluded from this file).\n // The reason travels into the finding, message-preserving as\n // `unreadableLandscape` is; the read error must not kill the run — the\n // other three checks still have answers.\n landscape = \"invalid\";\n landscapeProblem = err instanceof Error ? err.message : String(err);\n }\n }\n const parses = landscape === \"read\";\n const svcOf: (id: string) => string = parses\n ? serviceResolver(elements, known)\n : (id: string) => id;\n const elementIds = elements.map((e) => e.id);\n\n // One service's ladder rung. `landscapeEvidence` is the ONE shared spelling\n // of the positive-evidence apiExpected rule (core/vocabulary/maturity.ts);\n // its own docblock warns against calling it per service inside a FLEET loop,\n // and this loop is not one — it runs over the gated service and its direct\n // partners, a handful, never the enumeration.\n const gradeWith = (entry: ServiceEntry, apiExpected: boolean): GradedService => {\n const input = {\n entry,\n archSpec: existsSync(servicePathsAt(entry.dir).archSpec),\n apiExpected,\n };\n return { maturity: serviceMaturity(input), gaps: maturityGaps(input) };\n };\n const grade = (entry: ServiceEntry): GradedService =>\n gradeWith(\n entry,\n landscapeEvidence({ id: entry.id, parses, relationships, elementIds, svcOf }).apiExpected,\n );\n\n // Unreachable by construction: the command layer resolved `service` against\n // the same memoized enumeration this scan just read, and refused\n // `unknown-service` when no directory answered. Failing closed here beats\n // grading a service nobody enumerated as if its absence were a rung.\n const targetEntry = entryById.get(service);\n if (targetEntry === undefined) {\n throw new Error(`gate: '${service}' vanished from the enumeration between resolve and scan`);\n }\n\n const byId = new Map(elements.map((e) => [e.id, e]));\n const acc = new Map<string, PartnerAccumulator>();\n const join = (name: string, role: PartnerRole, r: Rel, external: boolean): void => {\n const slot = acc.get(name) ?? { consumer: false, provider: false, external: false, via: new Set<string>() };\n if (role === \"consumer\") slot.consumer = true;\n else slot.provider = true;\n slot.external = slot.external || external;\n for (const v of viaOf(r)) slot.via.add(v);\n acc.set(name, slot);\n };\n for (const r of relationships) {\n const source = svcOf(r.source);\n const target = svcOf(r.target);\n // A self-edge (both ends resolve to the gated service — its own containers\n // talking to each other) names no partner.\n if (source === target) continue;\n if (target === service) {\n const facts = endFacts(byId, known, r.source);\n // A person is never a deploy participant: the actor exemption every\n // landscape census applies (drawnSystems, commands/validate/fleet/census.ts).\n if (!facts.actor) join(source, \"consumer\", r, facts.external);\n } else if (source === service) {\n const facts = endFacts(byId, known, r.target);\n if (!facts.actor) join(target, \"provider\", r, facts.external);\n }\n }\n\n // Sorted by id so identical state yields identical bytes — the accumulator's\n // order is the landscape's edge order, which a reformat may shuffle.\n const joined = [...acc.entries()].sort((a, b) => compareIds(a[0], b[0]));\n const partners: GatePartner[] = joined.map(([name, slot]) => {\n const entry = entryById.get(name);\n return {\n service: name,\n maturity: entry === undefined ? null : grade(entry).maturity,\n role: slot.consumer && slot.provider ? \"both\" : slot.consumer ? \"consumer\" : \"provider\",\n via: [...slot.via].sort(),\n // An adopted service is OURS whatever a zone's tag says: `external` and\n // a maturity rung together must be unrepresentable, or the rung the\n // payload carries is one the exemption hides and the human view drops.\n external: entry === undefined && slot.external,\n };\n });\n\n return {\n landscape,\n landscapeProblem,\n targetEntry,\n target: grade(targetEntry),\n targetWithoutApi: gradeWith(targetEntry, false),\n partners,\n partnerEntries: joined.flatMap(([name]) => {\n const entry = entryById.get(name);\n return entry === undefined ? [] : [entry];\n }),\n };\n}\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** The four checks, in report order. A closed set — the payload's `checks[].check` values. */
|
|
2
|
+
export const GATE_CHECKS = ["partners", "freshness", "verification", "interrupted"];
|
|
3
|
+
/**
|
|
4
|
+
* Fail iff any check carries an error-severity finding. `--strict` never moves
|
|
5
|
+
* this — like validate's `valid`, the verdict means the same thing in text and
|
|
6
|
+
* JSON whatever the exit code does, so two pipelines reading one repo can
|
|
7
|
+
* grade the same report differently and both be telling the truth
|
|
8
|
+
* (commands/validate/validate.ts documents the stance; gate copies it).
|
|
9
|
+
*/
|
|
10
|
+
export function gateVerdict(checks) {
|
|
11
|
+
return checks.some((c) => c.findings.some((f) => f.severity === "error")) ? "fail" : "pass";
|
|
12
|
+
}
|
|
13
|
+
export function gateSummary(checks) {
|
|
14
|
+
const of = (severity) => checks.reduce((n, c) => n + c.findings.filter((f) => f.severity === severity).length, 0);
|
|
15
|
+
return { errors: of("error"), warnings: of("warn") };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Per-subject containment: one unreadable sibling degrades ONE subject's
|
|
19
|
+
* answer, with its path recorded, instead of killing the report the other
|
|
20
|
+
* subjects still have. The code is validate's own containment code
|
|
21
|
+
* (`guarded`, commands/validate/report.ts), reused so one breach is spelled
|
|
22
|
+
* one way — and it is an ERROR: an unanswerable check fails closed, because
|
|
23
|
+
* "nobody could look" must be distinguishable from "nothing is wrong" in the
|
|
24
|
+
* exit code as well as the payload. Anything carrying no errno and no path is
|
|
25
|
+
* a real bug, and still escapes untouched (reportRepositoryUnavailable's
|
|
26
|
+
* recognizer).
|
|
27
|
+
*/
|
|
28
|
+
export function unreadableSubject(kind, id, err) {
|
|
29
|
+
const e = err;
|
|
30
|
+
if (e.path === undefined && typeof e.errno !== "number")
|
|
31
|
+
throw err;
|
|
32
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
33
|
+
const consequence = kind === "service" ? "its freshness is unknown" : "its verification state is unknown";
|
|
34
|
+
return {
|
|
35
|
+
severity: "error",
|
|
36
|
+
code: kind === "service" ? "service.unreadable" : "feature.unreadable",
|
|
37
|
+
subject: id,
|
|
38
|
+
message: `${id}: ${e.path ?? "one of its documents"} could not be read, so ${consequence} — nobody could look, which is not the same as nothing being wrong. ${reason}` +
|
|
39
|
+
// The same sentence reportRepositoryUnavailable carries, for the same
|
|
40
|
+
// reason: Node reports EISDIR from read() with no path at all, so the
|
|
41
|
+
// commonest malformed-docs shape arrives here nameless.
|
|
42
|
+
(e.path === undefined
|
|
43
|
+
? " The failure named no path — that is how Node reports a directory sitting where a file belongs."
|
|
44
|
+
: ""),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/core/gate/report.ts"],"names":[],"mappings":"AAeA,8FAA8F;AAC9F,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,CAAU,CAAC;AA4D7F;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,MAA4B;IACtD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;AAC9F,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAA4B;IACtD,MAAM,EAAE,GAAG,CAAC,QAA0B,EAAU,EAAE,CAChD,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3F,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA2B,EAAE,EAAU,EAAE,GAAY;IACrF,MAAM,CAAC,GAAG,GAA4B,CAAC;IACvC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,MAAM,GAAG,CAAC;IACnE,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChE,MAAM,WAAW,GACf,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,mCAAmC,CAAC;IACxF,OAAO;QACL,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,oBAAoB;QACtE,OAAO,EAAE,EAAE;QACX,OAAO,EACL,GAAG,EAAE,KAAK,CAAC,CAAC,IAAI,IAAI,sBAAsB,0BAA0B,WAAW,uEAAuE,MAAM,EAAE;YAC9J,sEAAsE;YACtE,sEAAsE;YACtE,wDAAwD;YACxD,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS;gBACnB,CAAC,CAAC,iGAAiG;gBACnG,CAAC,CAAC,EAAE,CAAC;KACV,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The gate report — what `loam gate` computes before anything is printed.\n *\n * Shapes, verdict arithmetic and the per-subject containment classifier: no\n * filesystem, no process access, no printing. The four checks build findings\n * (`./checks.ts`, `./verification.ts`), the command layer renders them, and\n * this module is what the two agree through — the same split\n * `core/vocabulary/report.ts` gives `loam validate`, whose `Finding` shape\n * the checks reuse so one breach arrives in one shape whichever command\n * reports it.\n */\nimport type { Finding } from \"../vocabulary/report.js\";\nimport type { Maturity } from \"../vocabulary/maturity.js\";\nimport type { VerificationState } from \"../status/report.js\";\n\n/** The four checks, in report order. A closed set — the payload's `checks[].check` values. */\nexport const GATE_CHECKS = [\"partners\", \"freshness\", \"verification\", \"interrupted\"] as const;\nexport type GateCheckName = (typeof GATE_CHECKS)[number];\n\nexport interface GateCheck {\n check: GateCheckName;\n findings: Finding[];\n}\n\n/**\n * How the landscape read came out. `absent` and `invalid` are kept apart from\n * `read` because the partner set derived under either is NOT \"no partners\" —\n * it is \"could not look\", and `gate.partners-unknown` exists to keep a\n * pipeline from reading the empty list as a clean one.\n */\nexport type LandscapeRead = \"read\" | \"absent\" | \"invalid\";\n\n/**\n * What the partner is to the gated service: a `consumer` calls into it, a\n * `provider` is called by it, `both` when the landscape draws edges in both\n * directions.\n */\nexport type PartnerRole = \"consumer\" | \"provider\" | \"both\";\n\nexport interface GatePartner {\n /** The partner's resolved service name — a directory id when one exists, the drawn name otherwise. */\n service: string;\n /**\n * The partner's rung on the adoption ladder, or null when no `services/`\n * directory answers to the name at all — the state `gate.partner-undocumented`\n * names. Null is deliberately not a fifth rung: `empty` means \"exists,\n * nothing in it\", and a partner nobody adopted is a different fact.\n */\n maturity: Maturity | null;\n role: PartnerRole;\n /** The joins, sorted: `operation <op>`, `message <name>`, or the untyped edge's title. */\n via: string[];\n /** The element is tagged `#external` — somebody else's system, undocumented on purpose. */\n external: boolean;\n}\n\n/**\n * One active feature touching the gated service, with the verification\n * record's own projection beside it. Every field but `id` is\n * `verificationState`'s (`core/status/verification.ts`) — never a second\n * derivation of \"verified\".\n */\nexport interface GateFeature extends VerificationState {\n id: string;\n}\n\nexport interface GateReport {\n service: string;\n landscape: LandscapeRead;\n partners: GatePartner[];\n features: GateFeature[];\n checks: GateCheck[];\n}\n\nexport type GateVerdict = \"pass\" | \"fail\";\n\n/**\n * Fail iff any check carries an error-severity finding. `--strict` never moves\n * this — like validate's `valid`, the verdict means the same thing in text and\n * JSON whatever the exit code does, so two pipelines reading one repo can\n * grade the same report differently and both be telling the truth\n * (commands/validate/validate.ts documents the stance; gate copies it).\n */\nexport function gateVerdict(checks: readonly GateCheck[]): GateVerdict {\n return checks.some((c) => c.findings.some((f) => f.severity === \"error\")) ? \"fail\" : \"pass\";\n}\n\nexport function gateSummary(checks: readonly GateCheck[]): { errors: number; warnings: number } {\n const of = (severity: \"error\" | \"warn\"): number =>\n checks.reduce((n, c) => n + c.findings.filter((f) => f.severity === severity).length, 0);\n return { errors: of(\"error\"), warnings: of(\"warn\") };\n}\n\n/**\n * Per-subject containment: one unreadable sibling degrades ONE subject's\n * answer, with its path recorded, instead of killing the report the other\n * subjects still have. The code is validate's own containment code\n * (`guarded`, commands/validate/report.ts), reused so one breach is spelled\n * one way — and it is an ERROR: an unanswerable check fails closed, because\n * \"nobody could look\" must be distinguishable from \"nothing is wrong\" in the\n * exit code as well as the payload. Anything carrying no errno and no path is\n * a real bug, and still escapes untouched (reportRepositoryUnavailable's\n * recognizer).\n */\nexport function unreadableSubject(kind: \"service\" | \"feature\", id: string, err: unknown): Finding {\n const e = err as NodeJS.ErrnoException;\n if (e.path === undefined && typeof e.errno !== \"number\") throw err;\n const reason = err instanceof Error ? err.message : String(err);\n const consequence =\n kind === \"service\" ? \"its freshness is unknown\" : \"its verification state is unknown\";\n return {\n severity: \"error\",\n code: kind === \"service\" ? \"service.unreadable\" : \"feature.unreadable\",\n subject: id,\n message:\n `${id}: ${e.path ?? \"one of its documents\"} could not be read, so ${consequence} — nobody could look, which is not the same as nothing being wrong. ${reason}` +\n // The same sentence reportRepositoryUnavailable carries, for the same\n // reason: Node reports EISDIR from read() with no path at all, so the\n // commonest malformed-docs shape arrives here nameless.\n (e.path === undefined\n ? \" The failure named no path — that is how Node reports a directory sitting where a file belongs.\"\n : \"\"),\n };\n}\n"]}
|