@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.2
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 +2635 -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 +599 -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 +323 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +180 -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 +252 -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 +204 -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 +270 -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 +251 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +251 -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 +337 -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 +48 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +174 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +90 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +158 -0
- package/dist/core/c4/parsed/deployment.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 +187 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +220 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/service.js +159 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +144 -0
- package/dist/core/c4/resolve/steps.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 +139 -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 +174 -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 +287 -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/deployment/delta.js +188 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +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 +137 -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 +210 -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 +277 -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 +196 -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 +197 -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 +72 -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 +58 -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,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loam verify --contract-results` — an API contract-test report as the answer
|
|
3
|
+
* sheet for `api.exposes` claims.
|
|
4
|
+
*
|
|
5
|
+
* The accepted shape is loam's own documented one (SCHEMA.md, "Contract-test
|
|
6
|
+
* reports"): `{"loamContractReport": 1, "results": [{"operationId": "…",
|
|
7
|
+
* "status": "passed"}]}`. It is deliberately a shape tools EMIT INTO rather
|
|
8
|
+
* than a vendor format loam parses, because no surveyed vendor format carries
|
|
9
|
+
* both halves of what confirming needs — an unambiguous executed-AND-passed
|
|
10
|
+
* status and a deterministic operation join key. Specmatic's JSON coverage
|
|
11
|
+
* report was the v1 candidate and is declined by name: its `coverageStatus:
|
|
12
|
+
* "covered"` means exercised, not passed (a red run still writes "covered"),
|
|
13
|
+
* so an adapter over it would confirm exactly what a failing suite refuted.
|
|
14
|
+
*
|
|
15
|
+
* The operationId is the ONLY identity, matched against the structured
|
|
16
|
+
* `operation` a claim carries (`../checklist.ts`). It is half an identity, and
|
|
17
|
+
* the design leans on the missing half being supplied by scope: an operationId
|
|
18
|
+
* is unique within ONE contract document, not across a fleet, and a report
|
|
19
|
+
* entry names no service — so when more than one service on the handed-in
|
|
20
|
+
* checklist exposes the same operationId, the claims are refused rather than
|
|
21
|
+
* answered ({@link contestedOperations}), and `--service` is what gives a
|
|
22
|
+
* contract report an owner, exactly as it is for a contested scenario digest.
|
|
23
|
+
* Within one service's claims the match is exact, and renaming an operation
|
|
24
|
+
* renames its claim, so rewording cannot carry an answer over. Names and free
|
|
25
|
+
* text match nothing.
|
|
26
|
+
*
|
|
27
|
+
* Parsing is strict where the cucumber reader is tolerant, and the asymmetry
|
|
28
|
+
* is deliberate: a cucumber report is another tool's file full of legitimate
|
|
29
|
+
* entries loam has no business judging, while this shape exists for exactly
|
|
30
|
+
* one purpose, so an entry that cannot be matched is a malformed report, not
|
|
31
|
+
* scenery. A file that is recognizably not this shape refuses rather than
|
|
32
|
+
* quietly answering every claim "not exercised" — an unparseable report never
|
|
33
|
+
* silently answers zero claims as if none matched.
|
|
34
|
+
*/
|
|
35
|
+
import { isRecord } from "../../kernel/records.js";
|
|
36
|
+
import {} from "../answers.js";
|
|
37
|
+
import {} from "../checklist.js";
|
|
38
|
+
/** The marker key that makes a JSON document this shape, and the version this loam reads. */
|
|
39
|
+
export const CONTRACT_REPORT_MARKER = "loamContractReport";
|
|
40
|
+
export const CONTRACT_REPORT_VERSION = 1;
|
|
41
|
+
/**
|
|
42
|
+
* Read a parsed JSON document down to its runs. `reportName` is how the caller
|
|
43
|
+
* spelled the file, for the refusal message; core never prints, so a refusal
|
|
44
|
+
* is data for the command to fail with (`answers-unreadable`).
|
|
45
|
+
*/
|
|
46
|
+
export function readContractReport(doc, reportName) {
|
|
47
|
+
const shape = `{"${CONTRACT_REPORT_MARKER}": ${CONTRACT_REPORT_VERSION}, "results": [{"operationId": "…", "status": "passed"}]}`;
|
|
48
|
+
if (!isRecord(doc) || !(CONTRACT_REPORT_MARKER in doc)) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
message: `${reportName} is not a contract-results report — expected loam's generic shape ${shape} ` +
|
|
52
|
+
"(see SCHEMA.md's contract-test reports section; Specmatic, Pact, Dredd and property harnesses emit it with a one-line transform).",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (doc[CONTRACT_REPORT_MARKER] !== CONTRACT_REPORT_VERSION) {
|
|
56
|
+
// A later version may redefine what an entry means; reading it with this
|
|
57
|
+
// parser could confirm something the report never said. Fail closed.
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
message: `${reportName} declares ${CONTRACT_REPORT_MARKER}: ${JSON.stringify(doc[CONTRACT_REPORT_MARKER])} — ` +
|
|
61
|
+
`this loam reads version ${CONTRACT_REPORT_VERSION} only.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const results = doc["results"];
|
|
65
|
+
if (!Array.isArray(results)) {
|
|
66
|
+
return { ok: false, message: `${reportName} has no \`results\` array — expected ${shape}.` };
|
|
67
|
+
}
|
|
68
|
+
const runs = [];
|
|
69
|
+
for (const [i, entry] of results.entries()) {
|
|
70
|
+
const where = `${reportName} entry ${i + 1}`;
|
|
71
|
+
if (!isRecord(entry))
|
|
72
|
+
return { ok: false, message: `${where} is not an object — expected ${shape}.` };
|
|
73
|
+
const operationId = entry["operationId"];
|
|
74
|
+
if (typeof operationId !== "string" || operationId.length === 0) {
|
|
75
|
+
return { ok: false, message: `${where} has no operationId — every entry must name the operation it exercised.` };
|
|
76
|
+
}
|
|
77
|
+
const status = entry["status"];
|
|
78
|
+
if (typeof status !== "string" || status.length === 0) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
message: `${where} ('${operationId}') has no status — expected "passed", "failed", or the tool's own word for anything else.`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const test = entry["test"];
|
|
85
|
+
runs.push({
|
|
86
|
+
operationId,
|
|
87
|
+
status,
|
|
88
|
+
...(typeof test === "string" && test.trim().length > 0 ? { test: test.trim() } : {}),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return { ok: true, runs };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The operationIds that more than one service on this checklist exposes, with
|
|
95
|
+
* the services that expose them — `contestedDigests`' (core/results.ts) exact
|
|
96
|
+
* shape, for this axis's join key.
|
|
97
|
+
*
|
|
98
|
+
* An operationId is unique within one contract document, not across a fleet:
|
|
99
|
+
* two services may each expose a 'createSplit', and a report entry carries no
|
|
100
|
+
* service half, so a single contract report matches both services' claims and
|
|
101
|
+
* cannot say whose suite it describes. The all-at-once form once confirmed a
|
|
102
|
+
* service from a suite that never touched it exactly this way. loam does not
|
|
103
|
+
* choose: those claims are refused rather than answered, and `--service` is
|
|
104
|
+
* what gives a report an owner — it narrows the checklist to one subject
|
|
105
|
+
* before the matching starts, so nothing handed in here is contested.
|
|
106
|
+
*/
|
|
107
|
+
export function contestedOperations(claims) {
|
|
108
|
+
const owners = new Map();
|
|
109
|
+
for (const c of claims) {
|
|
110
|
+
if (c.operation === undefined)
|
|
111
|
+
continue;
|
|
112
|
+
const seen = owners.get(c.operation) ?? new Set();
|
|
113
|
+
seen.add(c.subject);
|
|
114
|
+
owners.set(c.operation, seen);
|
|
115
|
+
}
|
|
116
|
+
const contested = new Map();
|
|
117
|
+
for (const [operation, services] of owners) {
|
|
118
|
+
if (services.size > 1)
|
|
119
|
+
contested.set(operation, [...services].sort());
|
|
120
|
+
}
|
|
121
|
+
return contested;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Answer every claim from the report — the contract runner's half of the
|
|
125
|
+
* record. Total over the claims handed in, like `runnerAnswers`: a claim the
|
|
126
|
+
* report does not exercise is answered `unconfirmed`, never skipped, because
|
|
127
|
+
* silence must never read as checked. Report entries matching no claim are
|
|
128
|
+
* skipped silently — a real contract suite exercises the whole living API,
|
|
129
|
+
* and the claims only ever cover what the feature ADDS.
|
|
130
|
+
*
|
|
131
|
+
* Confirmation is strict on purpose: every matching entry (an operationId the
|
|
132
|
+
* report holds twice is two test cases, and all of them count) has status
|
|
133
|
+
* exactly `"passed"`. One non-passed entry wins as failure, and an unknown
|
|
134
|
+
* status is named in the note rather than rounded to either verdict. An
|
|
135
|
+
* operationId two services expose is refused before any of that: see
|
|
136
|
+
* {@link contestedOperations}.
|
|
137
|
+
*/
|
|
138
|
+
export function contractAnswers(claims, runs, reportName) {
|
|
139
|
+
const contested = contestedOperations(claims);
|
|
140
|
+
return claims.map((c) => {
|
|
141
|
+
const rivals = c.operation === undefined ? undefined : contested.get(c.operation);
|
|
142
|
+
if (rivals !== undefined) {
|
|
143
|
+
return answer(c.id, "unconfirmed", [], `operationId '${c.operation}' is exposed by more than one service on this checklist (${rivals.join(", ")}), ` +
|
|
144
|
+
`so ${reportName} cannot say whose suite exercised it. Record each service's claims from its own repo with --service.`);
|
|
145
|
+
}
|
|
146
|
+
if (c.operation === undefined) {
|
|
147
|
+
// Unreachable for the api.exposes claims this loam derives, which always
|
|
148
|
+
// carry one — but the function is total, and a claim without a join key
|
|
149
|
+
// must fail closed rather than throw or silently confirm.
|
|
150
|
+
return answer(c.id, "unconfirmed", [], "claim carries no operationId for a contract report to match on");
|
|
151
|
+
}
|
|
152
|
+
const matching = runs.filter((r) => r.operationId === c.operation);
|
|
153
|
+
if (matching.length === 0) {
|
|
154
|
+
return answer(c.id, "unconfirmed", [], `not exercised — no entry in ${reportName} names operationId '${c.operation}'`);
|
|
155
|
+
}
|
|
156
|
+
const failing = matching.filter((r) => r.status !== "passed");
|
|
157
|
+
if (failing.length === 0) {
|
|
158
|
+
return answer(c.id, "confirmed", matching.map((r) => `${reportName}: ${r.test ?? `operationId '${c.operation}' passed`}`));
|
|
159
|
+
}
|
|
160
|
+
const first = failing[0];
|
|
161
|
+
const why = first.status === "failed" ? "failed" : `status '${first.status}' is not 'passed'`;
|
|
162
|
+
const rerun = matching.length > 1 ? ` — ${failing.length} of ${matching.length} matching entries did not pass` : "";
|
|
163
|
+
return answer(c.id, "unconfirmed", [], `${why} (${reportName}${first.test === undefined ? "" : `: ${first.test}`})${rerun}`);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function answer(id, verdict, evidence, note) {
|
|
167
|
+
return { id, verdict, evidence, ...(note === undefined ? {} : { note }), answered_by: "external-runner" };
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../../src/core/verify/evidence/contract.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam verify --contract-results` — an API contract-test report as the answer\n * sheet for `api.exposes` claims.\n *\n * The accepted shape is loam's own documented one (SCHEMA.md, \"Contract-test\n * reports\"): `{\"loamContractReport\": 1, \"results\": [{\"operationId\": \"…\",\n * \"status\": \"passed\"}]}`. It is deliberately a shape tools EMIT INTO rather\n * than a vendor format loam parses, because no surveyed vendor format carries\n * both halves of what confirming needs — an unambiguous executed-AND-passed\n * status and a deterministic operation join key. Specmatic's JSON coverage\n * report was the v1 candidate and is declined by name: its `coverageStatus:\n * \"covered\"` means exercised, not passed (a red run still writes \"covered\"),\n * so an adapter over it would confirm exactly what a failing suite refuted.\n *\n * The operationId is the ONLY identity, matched against the structured\n * `operation` a claim carries (`../checklist.ts`). It is half an identity, and\n * the design leans on the missing half being supplied by scope: an operationId\n * is unique within ONE contract document, not across a fleet, and a report\n * entry names no service — so when more than one service on the handed-in\n * checklist exposes the same operationId, the claims are refused rather than\n * answered ({@link contestedOperations}), and `--service` is what gives a\n * contract report an owner, exactly as it is for a contested scenario digest.\n * Within one service's claims the match is exact, and renaming an operation\n * renames its claim, so rewording cannot carry an answer over. Names and free\n * text match nothing.\n *\n * Parsing is strict where the cucumber reader is tolerant, and the asymmetry\n * is deliberate: a cucumber report is another tool's file full of legitimate\n * entries loam has no business judging, while this shape exists for exactly\n * one purpose, so an entry that cannot be matched is a malformed report, not\n * scenery. A file that is recognizably not this shape refuses rather than\n * quietly answering every claim \"not exercised\" — an unparseable report never\n * silently answers zero claims as if none matched.\n */\nimport { isRecord } from \"../../kernel/records.js\";\nimport { type Answer } from \"../answers.js\";\nimport { type Claim } from \"../checklist.js\";\n\n/** The marker key that makes a JSON document this shape, and the version this loam reads. */\nexport const CONTRACT_REPORT_MARKER = \"loamContractReport\";\nexport const CONTRACT_REPORT_VERSION = 1;\n\n/** One report entry, reduced to what matching needs. Extra keys pass untouched. */\nexport interface ContractRun {\n operationId: string;\n /** Exactly `\"passed\"` confirms; `\"failed\"` and every other string do not. */\n status: string;\n /** Optional free text naming the test — it rides into the evidence line. */\n test?: string;\n}\n\nexport type ContractReportRead =\n | { ok: true; runs: ContractRun[] }\n | { ok: false; message: string };\n\n/**\n * Read a parsed JSON document down to its runs. `reportName` is how the caller\n * spelled the file, for the refusal message; core never prints, so a refusal\n * is data for the command to fail with (`answers-unreadable`).\n */\nexport function readContractReport(doc: unknown, reportName: string): ContractReportRead {\n const shape =\n `{\"${CONTRACT_REPORT_MARKER}\": ${CONTRACT_REPORT_VERSION}, \"results\": [{\"operationId\": \"…\", \"status\": \"passed\"}]}`;\n if (!isRecord(doc) || !(CONTRACT_REPORT_MARKER in doc)) {\n return {\n ok: false,\n message:\n `${reportName} is not a contract-results report — expected loam's generic shape ${shape} ` +\n \"(see SCHEMA.md's contract-test reports section; Specmatic, Pact, Dredd and property harnesses emit it with a one-line transform).\",\n };\n }\n if (doc[CONTRACT_REPORT_MARKER] !== CONTRACT_REPORT_VERSION) {\n // A later version may redefine what an entry means; reading it with this\n // parser could confirm something the report never said. Fail closed.\n return {\n ok: false,\n message:\n `${reportName} declares ${CONTRACT_REPORT_MARKER}: ${JSON.stringify(doc[CONTRACT_REPORT_MARKER])} — ` +\n `this loam reads version ${CONTRACT_REPORT_VERSION} only.`,\n };\n }\n const results = doc[\"results\"];\n if (!Array.isArray(results)) {\n return { ok: false, message: `${reportName} has no \\`results\\` array — expected ${shape}.` };\n }\n const runs: ContractRun[] = [];\n for (const [i, entry] of results.entries()) {\n const where = `${reportName} entry ${i + 1}`;\n if (!isRecord(entry)) return { ok: false, message: `${where} is not an object — expected ${shape}.` };\n const operationId = entry[\"operationId\"];\n if (typeof operationId !== \"string\" || operationId.length === 0) {\n return { ok: false, message: `${where} has no operationId — every entry must name the operation it exercised.` };\n }\n const status = entry[\"status\"];\n if (typeof status !== \"string\" || status.length === 0) {\n return {\n ok: false,\n message: `${where} ('${operationId}') has no status — expected \"passed\", \"failed\", or the tool's own word for anything else.`,\n };\n }\n const test = entry[\"test\"];\n runs.push({\n operationId,\n status,\n ...(typeof test === \"string\" && test.trim().length > 0 ? { test: test.trim() } : {}),\n });\n }\n return { ok: true, runs };\n}\n\n/**\n * The operationIds that more than one service on this checklist exposes, with\n * the services that expose them — `contestedDigests`' (core/results.ts) exact\n * shape, for this axis's join key.\n *\n * An operationId is unique within one contract document, not across a fleet:\n * two services may each expose a 'createSplit', and a report entry carries no\n * service half, so a single contract report matches both services' claims and\n * cannot say whose suite it describes. The all-at-once form once confirmed a\n * service from a suite that never touched it exactly this way. loam does not\n * choose: those claims are refused rather than answered, and `--service` is\n * what gives a report an owner — it narrows the checklist to one subject\n * before the matching starts, so nothing handed in here is contested.\n */\nexport function contestedOperations(claims: readonly Claim[]): Map<string, string[]> {\n const owners = new Map<string, Set<string>>();\n for (const c of claims) {\n if (c.operation === undefined) continue;\n const seen = owners.get(c.operation) ?? new Set<string>();\n seen.add(c.subject);\n owners.set(c.operation, seen);\n }\n const contested = new Map<string, string[]>();\n for (const [operation, services] of owners) {\n if (services.size > 1) contested.set(operation, [...services].sort());\n }\n return contested;\n}\n\n/**\n * Answer every claim from the report — the contract runner's half of the\n * record. Total over the claims handed in, like `runnerAnswers`: a claim the\n * report does not exercise is answered `unconfirmed`, never skipped, because\n * silence must never read as checked. Report entries matching no claim are\n * skipped silently — a real contract suite exercises the whole living API,\n * and the claims only ever cover what the feature ADDS.\n *\n * Confirmation is strict on purpose: every matching entry (an operationId the\n * report holds twice is two test cases, and all of them count) has status\n * exactly `\"passed\"`. One non-passed entry wins as failure, and an unknown\n * status is named in the note rather than rounded to either verdict. An\n * operationId two services expose is refused before any of that: see\n * {@link contestedOperations}.\n */\nexport function contractAnswers(claims: Claim[], runs: ContractRun[], reportName: string): Answer[] {\n const contested = contestedOperations(claims);\n return claims.map((c) => {\n const rivals = c.operation === undefined ? undefined : contested.get(c.operation);\n if (rivals !== undefined) {\n return answer(\n c.id,\n \"unconfirmed\",\n [],\n `operationId '${c.operation}' is exposed by more than one service on this checklist (${rivals.join(\", \")}), ` +\n `so ${reportName} cannot say whose suite exercised it. Record each service's claims from its own repo with --service.`,\n );\n }\n if (c.operation === undefined) {\n // Unreachable for the api.exposes claims this loam derives, which always\n // carry one — but the function is total, and a claim without a join key\n // must fail closed rather than throw or silently confirm.\n return answer(c.id, \"unconfirmed\", [], \"claim carries no operationId for a contract report to match on\");\n }\n const matching = runs.filter((r) => r.operationId === c.operation);\n if (matching.length === 0) {\n return answer(\n c.id,\n \"unconfirmed\",\n [],\n `not exercised — no entry in ${reportName} names operationId '${c.operation}'`,\n );\n }\n const failing = matching.filter((r) => r.status !== \"passed\");\n if (failing.length === 0) {\n return answer(\n c.id,\n \"confirmed\",\n matching.map((r) => `${reportName}: ${r.test ?? `operationId '${c.operation}' passed`}`),\n );\n }\n const first = failing[0]!;\n const why = first.status === \"failed\" ? \"failed\" : `status '${first.status}' is not 'passed'`;\n const rerun =\n matching.length > 1 ? ` — ${failing.length} of ${matching.length} matching entries did not pass` : \"\";\n return answer(\n c.id,\n \"unconfirmed\",\n [],\n `${why} (${reportName}${first.test === undefined ? \"\" : `: ${first.test}`})${rerun}`,\n );\n });\n}\n\nfunction answer(id: string, verdict: Answer[\"verdict\"], evidence: string[], note?: string): Answer {\n return { id, verdict, evidence, ...(note === undefined ? {} : { note }), answered_by: \"external-runner\" };\n}\n"]}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `verification.yaml` read back, and refused when it cannot be trusted.
|
|
3
|
+
*
|
|
4
|
+
* Reading is deliberately not the inverse of rendering. A record loam wrote is
|
|
5
|
+
* re-graded on the way in — unreadable YAML, a shape that is not a
|
|
6
|
+
* verification, a `summary` that disagrees with the claims below it — because
|
|
7
|
+
* the file is data meant to survive without loam, and anything may have edited
|
|
8
|
+
* it since. The render lives with the writer it feeds (`store/render.ts`
|
|
9
|
+
* produces exactly the bytes `store/commit.ts` swaps into place; races and
|
|
10
|
+
* kills mid-write live there too), and the lock that serialises writers is
|
|
11
|
+
* taken by `commands/verify/verify.ts` before this module's authoritative
|
|
12
|
+
* read. Nothing here touches the filesystem except to read.
|
|
13
|
+
*/
|
|
14
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
15
|
+
import { parse } from "yaml";
|
|
16
|
+
import { isRecord } from "../kernel/records.js";
|
|
17
|
+
import { VERDICTS } from "./answers.js";
|
|
18
|
+
import { isEvidencePinList } from "./pins/pin.js";
|
|
19
|
+
import { tallyAnswers, verificationPath } from "./record.js";
|
|
20
|
+
export async function readVerificationState(featureDir) {
|
|
21
|
+
const path = verificationPath(featureDir);
|
|
22
|
+
let raw;
|
|
23
|
+
try {
|
|
24
|
+
raw = await readFile(path);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (err.code === "ENOENT") {
|
|
28
|
+
// ENOENT is "absent" — except from a symlink whose target is gone, which
|
|
29
|
+
// `existsSync` used to fold into the same answer. That mislabel was a
|
|
30
|
+
// trap: the read said "no record" while the commit's exclusive link(2)
|
|
31
|
+
// saw the link itself and refused EEXIST, so every `--record` answered
|
|
32
|
+
// `record-raced` with advice (re-run) that could never work. The lstat
|
|
33
|
+
// must CONFIRM the link before the message asserts one: a real file can
|
|
34
|
+
// also appear between the two calls, and telling its owner to "remove
|
|
35
|
+
// the link" would delete a valid record.
|
|
36
|
+
let entry;
|
|
37
|
+
try {
|
|
38
|
+
entry = await lstat(path);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return { state: "absent" };
|
|
42
|
+
}
|
|
43
|
+
if (entry.isSymbolicLink()) {
|
|
44
|
+
return {
|
|
45
|
+
state: "unreadable",
|
|
46
|
+
reason: "it is a symlink whose target does not exist — replace the link with a real record, or remove it",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
state: "unreadable",
|
|
51
|
+
reason: "it appeared while being read — another writer is landing; re-run",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { state: "unreadable", reason: err instanceof Error ? err.message : String(err) };
|
|
55
|
+
}
|
|
56
|
+
let doc;
|
|
57
|
+
try {
|
|
58
|
+
doc = parse(raw.toString("utf8"));
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
return { state: "unreadable", reason: yamlReason(err) };
|
|
62
|
+
}
|
|
63
|
+
const verification = asVerification(doc);
|
|
64
|
+
if (verification === null) {
|
|
65
|
+
return {
|
|
66
|
+
state: "unreadable",
|
|
67
|
+
reason: "it parses as YAML but does not have a verification record's shape (feature, recorded, checklist, summary and claims)",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const miscount = summaryDisagreement(verification);
|
|
71
|
+
if (miscount === null)
|
|
72
|
+
return { state: "ok", verification, raw };
|
|
73
|
+
return {
|
|
74
|
+
state: "unreadable",
|
|
75
|
+
// Spelled twice on purpose: the field is what a caller branches on, the
|
|
76
|
+
// prose is what a terminal prints and what somebody greps for.
|
|
77
|
+
code: "verify.record-miscounted",
|
|
78
|
+
reason: `verify.record-miscounted: ${miscount}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Does the record's `summary` add up from its own `claims[]`?
|
|
83
|
+
*
|
|
84
|
+
* The summary is what wrote the file; the claims are what it answered. Nothing
|
|
85
|
+
* ever compared them, so every reader that took the shortcut — verify's frozen
|
|
86
|
+
* verdict for a shipped feature, list's column, status's block — reported a
|
|
87
|
+
* record full of unconfirmed claims as fully confirmed. Believing either side
|
|
88
|
+
* over the other would be a guess about which half was tampered with, so a
|
|
89
|
+
* record that contradicts itself is refused like any other file loam cannot
|
|
90
|
+
* use. The code is in the reason: it travels wherever the reason is printed.
|
|
91
|
+
*/
|
|
92
|
+
function summaryDisagreement(v) {
|
|
93
|
+
const t = tallyAnswers(v.claims);
|
|
94
|
+
const unanswered = v.summary.unanswered ?? 0;
|
|
95
|
+
const says = [];
|
|
96
|
+
// Before the arithmetic, because a negative count defeats it: `unanswered:
|
|
97
|
+
// -2` is never something loam wrote, and it is the one value that lets a
|
|
98
|
+
// hand-edited summary balance its own books — every comparison below then
|
|
99
|
+
// agrees, and a record contradicting its claims reported `verified`.
|
|
100
|
+
for (const [field, value] of Object.entries(v.summary)) {
|
|
101
|
+
if (typeof value === "number" && value < 0)
|
|
102
|
+
says.push(`carries a negative count (${field}: ${value})`);
|
|
103
|
+
}
|
|
104
|
+
if (v.summary.confirmed !== t.confirmed) {
|
|
105
|
+
says.push(`says ${v.summary.confirmed} confirmed where claims[] holds ${t.confirmed}`);
|
|
106
|
+
}
|
|
107
|
+
if (v.summary.unconfirmed !== t.unconfirmed) {
|
|
108
|
+
says.push(`says ${v.summary.unconfirmed} unconfirmed where claims[] holds ${t.unconfirmed}`);
|
|
109
|
+
}
|
|
110
|
+
if (v.summary.claims !== v.claims.length + unanswered) {
|
|
111
|
+
says.push(`says ${v.summary.claims} claim(s)${unanswered === 0 ? "" : ` including ${unanswered} unanswered`} where claims[] holds ${v.claims.length}`);
|
|
112
|
+
}
|
|
113
|
+
return says.length === 0
|
|
114
|
+
? null
|
|
115
|
+
: `its summary ${says.join(", ")} — the record contradicts itself, so neither half can be believed`;
|
|
116
|
+
}
|
|
117
|
+
/** A YAML failure with its line, when the parser located one: that line is the fix. */
|
|
118
|
+
function yamlReason(err) {
|
|
119
|
+
const message = err instanceof Error ? err.message.split("\n")[0].trim() : String(err);
|
|
120
|
+
const pos = err?.linePos;
|
|
121
|
+
const line = Array.isArray(pos) ? pos[0]?.line : undefined;
|
|
122
|
+
return line === undefined ? `YAML error: ${message}` : `YAML error at line ${line}: ${message}`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The record beside a feature, or null when there is none loam can use. Kept
|
|
126
|
+
* for readers that have nothing to say about the difference (`loam list`'s
|
|
127
|
+
* verification column shows one glyph either way); anything that WRITES must
|
|
128
|
+
* use {@link readVerificationState} instead, or it overwrites what it could not
|
|
129
|
+
* read.
|
|
130
|
+
*
|
|
131
|
+
* A record whose summary contradicts its own claims is one of the nulls now, so
|
|
132
|
+
* no reader can print its counts as fact. Reading it as ABSENT is the safe half
|
|
133
|
+
* of the truth and not the whole of it: a reader that can say why should take
|
|
134
|
+
* {@link readVerificationState} and its `code`.
|
|
135
|
+
*/
|
|
136
|
+
export async function readVerification(featureDir) {
|
|
137
|
+
const read = await readVerificationState(featureDir);
|
|
138
|
+
return read.state === "ok" ? read.verification : null;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The shape check `readVerification` stands behind: everything its readers
|
|
142
|
+
* dereference (verify's report/frozen views, list's verification column) must
|
|
143
|
+
* be present and typed, or the cast would manufacture a Verification that
|
|
144
|
+
* crashes them. Deliberately no tighter than the readers need: extra keys pass
|
|
145
|
+
* (a human may annotate), `answered_by` stays optional (absent in records that
|
|
146
|
+
* predate `--results`) and is any string, like `kind` — the readers only ever
|
|
147
|
+
* compare them, and a record written by a newer loam must not read as absent.
|
|
148
|
+
*/
|
|
149
|
+
function asVerification(doc) {
|
|
150
|
+
if (!isRecord(doc))
|
|
151
|
+
return null;
|
|
152
|
+
const summary = doc["summary"];
|
|
153
|
+
if (typeof doc["feature"] !== "string" ||
|
|
154
|
+
typeof doc["recorded"] !== "string" ||
|
|
155
|
+
typeof doc["checklist"] !== "string" ||
|
|
156
|
+
!isRecord(summary) ||
|
|
157
|
+
!isCount(summary["claims"]) ||
|
|
158
|
+
!isCount(summary["confirmed"]) ||
|
|
159
|
+
!isCount(summary["unconfirmed"]) ||
|
|
160
|
+
(summary["unanswered"] !== undefined && !isCount(summary["unanswered"])) ||
|
|
161
|
+
!Array.isArray(doc["claims"])) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
for (const c of doc["claims"]) {
|
|
165
|
+
if (!isRecord(c))
|
|
166
|
+
return null;
|
|
167
|
+
if (typeof c["id"] !== "string" || typeof c["kind"] !== "string" || typeof c["claim"] !== "string")
|
|
168
|
+
return null;
|
|
169
|
+
if (c["subject"] !== undefined && typeof c["subject"] !== "string")
|
|
170
|
+
return null;
|
|
171
|
+
if (typeof c["verdict"] !== "string" || !VERDICTS.includes(c["verdict"]))
|
|
172
|
+
return null;
|
|
173
|
+
// The frozen view iterates evidence per claim — a scalar here would crash it.
|
|
174
|
+
if (!Array.isArray(c["evidence"]) || c["evidence"].some((e) => typeof e !== "string"))
|
|
175
|
+
return null;
|
|
176
|
+
if (c["note"] !== undefined && typeof c["note"] !== "string")
|
|
177
|
+
return null;
|
|
178
|
+
if (c["answered_by"] !== undefined && typeof c["answered_by"] !== "string")
|
|
179
|
+
return null;
|
|
180
|
+
// Absent fine, present whole — the isConsumedContractReport precedent, and
|
|
181
|
+
// the guard body lives with the pin shape it vouches for (pins/pin.ts). A
|
|
182
|
+
// broken pins block makes the whole record unreadable and therefore never
|
|
183
|
+
// overwritten: skipping it instead would read a hand-damaged record as
|
|
184
|
+
// drift-free.
|
|
185
|
+
if (!isEvidencePinList(c["evidence_pins"]))
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
if (doc["schema"] !== undefined && doc["schema"] !== 2)
|
|
189
|
+
return null;
|
|
190
|
+
if (!isConsumedReport(doc["report"]))
|
|
191
|
+
return null;
|
|
192
|
+
if (!isConsumedContractReport(doc["contractReport"]))
|
|
193
|
+
return null;
|
|
194
|
+
if (doc["attestations"] !== undefined) {
|
|
195
|
+
if (!Array.isArray(doc["attestations"]))
|
|
196
|
+
return null;
|
|
197
|
+
for (const a of doc["attestations"]) {
|
|
198
|
+
if (!isRecord(a))
|
|
199
|
+
return null;
|
|
200
|
+
if (typeof a["service"] !== "string" || typeof a["commit"] !== "string" || typeof a["recorded"] !== "string")
|
|
201
|
+
return null;
|
|
202
|
+
if (!/^[0-9a-f]{40,64}$/i.test(a["commit"]))
|
|
203
|
+
return null;
|
|
204
|
+
if (!Array.isArray(a["claims"]) || a["claims"].some((id) => typeof id !== "string"))
|
|
205
|
+
return null;
|
|
206
|
+
if (!isConsumedReport(a["report"]))
|
|
207
|
+
return null;
|
|
208
|
+
if (!isConsumedContractReport(a["contractReport"]))
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return doc;
|
|
213
|
+
}
|
|
214
|
+
/** Absent is fine — most records answer nothing from a report. Present must be whole: it is printed. */
|
|
215
|
+
function isConsumedReport(v) {
|
|
216
|
+
if (v === undefined)
|
|
217
|
+
return true;
|
|
218
|
+
return (isRecord(v) &&
|
|
219
|
+
typeof v["path"] === "string" &&
|
|
220
|
+
typeof v["digest"] === "string" &&
|
|
221
|
+
typeof v["mtime"] === "string" &&
|
|
222
|
+
isCount(v["scenarios"]));
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The contract report's pin, exactly as loose as {@link isConsumedReport}:
|
|
226
|
+
* absent fine, present whole — for the fields the readers dereference.
|
|
227
|
+
* `format` is OPTIONAL here even though loam always writes it: nothing in the
|
|
228
|
+
* readers dereferences it (it is provenance for a person, echoed only inside
|
|
229
|
+
* whole-object payloads), and the generated AGENTS.md teaches the pin as
|
|
230
|
+
* path/sha256/mtime/operation-count — so a hand-written block faithful to that
|
|
231
|
+
* teaching must not make the WHOLE record unreadable over a field no reader
|
|
232
|
+
* uses. When present it must be a string, and any string: a record written by
|
|
233
|
+
* a newer loam that learned a second report format must not read as
|
|
234
|
+
* unreadable here.
|
|
235
|
+
*/
|
|
236
|
+
function isConsumedContractReport(v) {
|
|
237
|
+
if (v === undefined)
|
|
238
|
+
return true;
|
|
239
|
+
return (isRecord(v) &&
|
|
240
|
+
typeof v["path"] === "string" &&
|
|
241
|
+
typeof v["digest"] === "string" &&
|
|
242
|
+
typeof v["mtime"] === "string" &&
|
|
243
|
+
isCount(v["operations"]) &&
|
|
244
|
+
(v["format"] === undefined || typeof v["format"] === "string"));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* A summary count: an integer. YAML hands back strings for anything quoted.
|
|
248
|
+
* Negativity is deliberately NOT checked here: a negative count is a summary
|
|
249
|
+
* that disagrees with the claims below it, and `summaryDisagreement` owns that
|
|
250
|
+
* refusal — failing the SHAPE check instead made the message list five keys
|
|
251
|
+
* that were all present, while the miscount branch one call away holds the
|
|
252
|
+
* advice the case actually needs.
|
|
253
|
+
*/
|
|
254
|
+
function isCount(v) {
|
|
255
|
+
return typeof v === "number" && Number.isInteger(v);
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/core/verify/file.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `verification.yaml` read back, and refused when it cannot be trusted.\n *\n * Reading is deliberately not the inverse of rendering. A record loam wrote is\n * re-graded on the way in — unreadable YAML, a shape that is not a\n * verification, a `summary` that disagrees with the claims below it — because\n * the file is data meant to survive without loam, and anything may have edited\n * it since. The render lives with the writer it feeds (`store/render.ts`\n * produces exactly the bytes `store/commit.ts` swaps into place; races and\n * kills mid-write live there too), and the lock that serialises writers is\n * taken by `commands/verify/verify.ts` before this module's authoritative\n * read. Nothing here touches the filesystem except to read.\n */\nimport { lstat, readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\nimport { isRecord } from \"../kernel/records.js\";\nimport { VERDICTS } from \"./answers.js\";\nimport { isEvidencePinList } from \"./pins/pin.js\";\nimport { tallyAnswers, verificationPath, type Verification } from \"./record.js\";\n\n/**\n * What is on disk beside a feature: nothing, something unreadable, or a record.\n *\n * The three are kept apart because they call for opposite handling. \"Absent\" is\n * a feature nobody has verified — verify starts a fresh record over it without\n * a second thought. \"Unreadable\" is a file that IS somebody's record: it parses\n * as garbage, or its shape is not a record's, and every answer and attestation\n * it holds is unaccounted for. Collapsing the two (which this function used to\n * do, returning null for both) meant a hand-edited or half-written\n * verification.yaml read as \"not verified\" and the next `--record` overwrote a\n * whole fleet's attestations without a word.\n */\nexport type VerificationRead =\n | { state: \"absent\" }\n /**\n * `reason` names the YAML line when the parser gave one — the file is the\n * thing to fix. `code` is set for the one unreadable that parses perfectly: a\n * record whose `summary` contradicts its own `claims[]`. That lives here\n * rather than in a state of its own so every reader which already refuses an\n * unreadable record refuses this one too, without a line of new code.\n */\n | { state: \"unreadable\"; reason: string; code?: \"verify.record-miscounted\" }\n /**\n * `raw` is the exact bytes `verification` was parsed from. A writer merges\n * over the PARSE, but commits over the BYTES: `commitVerification` compares\n * these against the file immediately before its swap, so an edit that landed\n * between this read and that write — an editor, a writer that ignored the\n * docs lock — is refused and preserved instead of silently buried.\n */\n | { state: \"ok\"; verification: Verification; raw: Buffer };\n\nexport async function readVerificationState(featureDir: string): Promise<VerificationRead> {\n const path = verificationPath(featureDir);\n let raw: Buffer;\n try {\n raw = await readFile(path);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n // ENOENT is \"absent\" — except from a symlink whose target is gone, which\n // `existsSync` used to fold into the same answer. That mislabel was a\n // trap: the read said \"no record\" while the commit's exclusive link(2)\n // saw the link itself and refused EEXIST, so every `--record` answered\n // `record-raced` with advice (re-run) that could never work. The lstat\n // must CONFIRM the link before the message asserts one: a real file can\n // also appear between the two calls, and telling its owner to \"remove\n // the link\" would delete a valid record.\n let entry;\n try {\n entry = await lstat(path);\n } catch {\n return { state: \"absent\" };\n }\n if (entry.isSymbolicLink()) {\n return {\n state: \"unreadable\",\n reason: \"it is a symlink whose target does not exist — replace the link with a real record, or remove it\",\n };\n }\n return {\n state: \"unreadable\",\n reason: \"it appeared while being read — another writer is landing; re-run\",\n };\n }\n return { state: \"unreadable\", reason: err instanceof Error ? err.message : String(err) };\n }\n let doc: unknown;\n try {\n doc = parse(raw.toString(\"utf8\"));\n } catch (err) {\n return { state: \"unreadable\", reason: yamlReason(err) };\n }\n const verification = asVerification(doc);\n if (verification === null) {\n return {\n state: \"unreadable\",\n reason:\n \"it parses as YAML but does not have a verification record's shape (feature, recorded, checklist, summary and claims)\",\n };\n }\n const miscount = summaryDisagreement(verification);\n if (miscount === null) return { state: \"ok\", verification, raw };\n return {\n state: \"unreadable\",\n // Spelled twice on purpose: the field is what a caller branches on, the\n // prose is what a terminal prints and what somebody greps for.\n code: \"verify.record-miscounted\",\n reason: `verify.record-miscounted: ${miscount}`,\n };\n}\n\n/**\n * Does the record's `summary` add up from its own `claims[]`?\n *\n * The summary is what wrote the file; the claims are what it answered. Nothing\n * ever compared them, so every reader that took the shortcut — verify's frozen\n * verdict for a shipped feature, list's column, status's block — reported a\n * record full of unconfirmed claims as fully confirmed. Believing either side\n * over the other would be a guess about which half was tampered with, so a\n * record that contradicts itself is refused like any other file loam cannot\n * use. The code is in the reason: it travels wherever the reason is printed.\n */\nfunction summaryDisagreement(v: Verification): string | null {\n const t = tallyAnswers(v.claims);\n const unanswered = v.summary.unanswered ?? 0;\n const says: string[] = [];\n // Before the arithmetic, because a negative count defeats it: `unanswered:\n // -2` is never something loam wrote, and it is the one value that lets a\n // hand-edited summary balance its own books — every comparison below then\n // agrees, and a record contradicting its claims reported `verified`.\n for (const [field, value] of Object.entries(v.summary)) {\n if (typeof value === \"number\" && value < 0) says.push(`carries a negative count (${field}: ${value})`);\n }\n if (v.summary.confirmed !== t.confirmed) {\n says.push(`says ${v.summary.confirmed} confirmed where claims[] holds ${t.confirmed}`);\n }\n if (v.summary.unconfirmed !== t.unconfirmed) {\n says.push(`says ${v.summary.unconfirmed} unconfirmed where claims[] holds ${t.unconfirmed}`);\n }\n if (v.summary.claims !== v.claims.length + unanswered) {\n says.push(\n `says ${v.summary.claims} claim(s)${unanswered === 0 ? \"\" : ` including ${unanswered} unanswered`} where claims[] holds ${v.claims.length}`,\n );\n }\n return says.length === 0\n ? null\n : `its summary ${says.join(\", \")} — the record contradicts itself, so neither half can be believed`;\n}\n\n/** A YAML failure with its line, when the parser located one: that line is the fix. */\nfunction yamlReason(err: unknown): string {\n const message = err instanceof Error ? err.message.split(\"\\n\")[0]!.trim() : String(err);\n const pos = (err as { linePos?: Array<{ line: number; col: number }> } | null)?.linePos;\n const line = Array.isArray(pos) ? pos[0]?.line : undefined;\n return line === undefined ? `YAML error: ${message}` : `YAML error at line ${line}: ${message}`;\n}\n\n/**\n * The record beside a feature, or null when there is none loam can use. Kept\n * for readers that have nothing to say about the difference (`loam list`'s\n * verification column shows one glyph either way); anything that WRITES must\n * use {@link readVerificationState} instead, or it overwrites what it could not\n * read.\n *\n * A record whose summary contradicts its own claims is one of the nulls now, so\n * no reader can print its counts as fact. Reading it as ABSENT is the safe half\n * of the truth and not the whole of it: a reader that can say why should take\n * {@link readVerificationState} and its `code`.\n */\nexport async function readVerification(featureDir: string): Promise<Verification | null> {\n const read = await readVerificationState(featureDir);\n return read.state === \"ok\" ? read.verification : null;\n}\n\n/**\n * The shape check `readVerification` stands behind: everything its readers\n * dereference (verify's report/frozen views, list's verification column) must\n * be present and typed, or the cast would manufacture a Verification that\n * crashes them. Deliberately no tighter than the readers need: extra keys pass\n * (a human may annotate), `answered_by` stays optional (absent in records that\n * predate `--results`) and is any string, like `kind` — the readers only ever\n * compare them, and a record written by a newer loam must not read as absent.\n */\nfunction asVerification(doc: unknown): Verification | null {\n if (!isRecord(doc)) return null;\n const summary = doc[\"summary\"];\n if (\n typeof doc[\"feature\"] !== \"string\" ||\n typeof doc[\"recorded\"] !== \"string\" ||\n typeof doc[\"checklist\"] !== \"string\" ||\n !isRecord(summary) ||\n !isCount(summary[\"claims\"]) ||\n !isCount(summary[\"confirmed\"]) ||\n !isCount(summary[\"unconfirmed\"]) ||\n (summary[\"unanswered\"] !== undefined && !isCount(summary[\"unanswered\"])) ||\n !Array.isArray(doc[\"claims\"])\n ) {\n return null;\n }\n for (const c of doc[\"claims\"]) {\n if (!isRecord(c)) return null;\n if (typeof c[\"id\"] !== \"string\" || typeof c[\"kind\"] !== \"string\" || typeof c[\"claim\"] !== \"string\") return null;\n if (c[\"subject\"] !== undefined && typeof c[\"subject\"] !== \"string\") return null;\n if (typeof c[\"verdict\"] !== \"string\" || !(VERDICTS as readonly string[]).includes(c[\"verdict\"])) return null;\n // The frozen view iterates evidence per claim — a scalar here would crash it.\n if (!Array.isArray(c[\"evidence\"]) || c[\"evidence\"].some((e) => typeof e !== \"string\")) return null;\n if (c[\"note\"] !== undefined && typeof c[\"note\"] !== \"string\") return null;\n if (c[\"answered_by\"] !== undefined && typeof c[\"answered_by\"] !== \"string\") return null;\n // Absent fine, present whole — the isConsumedContractReport precedent, and\n // the guard body lives with the pin shape it vouches for (pins/pin.ts). A\n // broken pins block makes the whole record unreadable and therefore never\n // overwritten: skipping it instead would read a hand-damaged record as\n // drift-free.\n if (!isEvidencePinList(c[\"evidence_pins\"])) return null;\n }\n if (doc[\"schema\"] !== undefined && doc[\"schema\"] !== 2) return null;\n if (!isConsumedReport(doc[\"report\"])) return null;\n if (!isConsumedContractReport(doc[\"contractReport\"])) return null;\n if (doc[\"attestations\"] !== undefined) {\n if (!Array.isArray(doc[\"attestations\"])) return null;\n for (const a of doc[\"attestations\"]) {\n if (!isRecord(a)) return null;\n if (typeof a[\"service\"] !== \"string\" || typeof a[\"commit\"] !== \"string\" || typeof a[\"recorded\"] !== \"string\") return null;\n if (!/^[0-9a-f]{40,64}$/i.test(a[\"commit\"])) return null;\n if (!Array.isArray(a[\"claims\"]) || a[\"claims\"].some((id) => typeof id !== \"string\")) return null;\n if (!isConsumedReport(a[\"report\"])) return null;\n if (!isConsumedContractReport(a[\"contractReport\"])) return null;\n }\n }\n return doc as unknown as Verification;\n}\n\n/** Absent is fine — most records answer nothing from a report. Present must be whole: it is printed. */\nfunction isConsumedReport(v: unknown): boolean {\n if (v === undefined) return true;\n return (\n isRecord(v) &&\n typeof v[\"path\"] === \"string\" &&\n typeof v[\"digest\"] === \"string\" &&\n typeof v[\"mtime\"] === \"string\" &&\n isCount(v[\"scenarios\"])\n );\n}\n\n/**\n * The contract report's pin, exactly as loose as {@link isConsumedReport}:\n * absent fine, present whole — for the fields the readers dereference.\n * `format` is OPTIONAL here even though loam always writes it: nothing in the\n * readers dereferences it (it is provenance for a person, echoed only inside\n * whole-object payloads), and the generated AGENTS.md teaches the pin as\n * path/sha256/mtime/operation-count — so a hand-written block faithful to that\n * teaching must not make the WHOLE record unreadable over a field no reader\n * uses. When present it must be a string, and any string: a record written by\n * a newer loam that learned a second report format must not read as\n * unreadable here.\n */\nfunction isConsumedContractReport(v: unknown): boolean {\n if (v === undefined) return true;\n return (\n isRecord(v) &&\n typeof v[\"path\"] === \"string\" &&\n typeof v[\"digest\"] === \"string\" &&\n typeof v[\"mtime\"] === \"string\" &&\n isCount(v[\"operations\"]) &&\n (v[\"format\"] === undefined || typeof v[\"format\"] === \"string\")\n );\n}\n\n/**\n * A summary count: an integer. YAML hands back strings for anything quoted.\n * Negativity is deliberately NOT checked here: a negative count is a summary\n * that disagrees with the claims below it, and `summaryDisagreement` owns that\n * refusal — failing the SHAPE check instead made the message list five keys\n * that were all present, while the miscount branch one call away holds the\n * advice the case actually needs.\n */\nfunction isCount(v: unknown): v is number {\n return typeof v === \"number\" && Number.isInteger(v);\n}\n\n"]}
|