@ybotok/loam 0.1.0-beta.2 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2570 -61
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2362 -245
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +82 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/agent/agents-stamp.js +155 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/asyncapi/read.js +239 -0
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +59 -55
- 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 +132 -77
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -291
- 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 -254
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1591
- 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 -343
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2065
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js +0 -68
- 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/brief.js +0 -733
- 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 -559
- 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 -479
- 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 -720
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -436
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -481
- 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 -1104
- 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,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The answers, and the one thing loam checks about them.
|
|
3
|
+
*
|
|
4
|
+
* loam does not judge an answer. It cannot: it never read the code. What it
|
|
5
|
+
* guarantees is that every question was asked and that every answer sits next
|
|
6
|
+
* to the question it answers — so an answer set that does not correspond to the
|
|
7
|
+
* current checklist is refused rather than merged. An unchecked claim must
|
|
8
|
+
* never be able to masquerade as checked, and that refusal is `checkAnswers`.
|
|
9
|
+
*
|
|
10
|
+
* A scenario claim is answered mechanically wherever it can be: `--results`
|
|
11
|
+
* reads a cucumber JSON report and matches it by the digest tag `loam gherkin`
|
|
12
|
+
* stamped, and only a green run confirms one (`core/results.ts`). Where it
|
|
13
|
+
* cannot — a legacy service whose suite is months away, which is the fleet loam
|
|
14
|
+
* exists for — `--record` may still take an agent's word for it, and
|
|
15
|
+
* `./record.ts` marks that answer attested rather than run.
|
|
16
|
+
*/
|
|
17
|
+
import { isRecord } from "../kernel/records.js";
|
|
18
|
+
import {} from "./checklist.js";
|
|
19
|
+
import {} from "./pins/pin.js";
|
|
20
|
+
/**
|
|
21
|
+
* Two verdicts, deliberately. Anything an agent cannot show evidence for is
|
|
22
|
+
* unconfirmed — a third value for "not applicable" would absorb every claim
|
|
23
|
+
* nobody wanted to answer.
|
|
24
|
+
*/
|
|
25
|
+
export const VERDICTS = ["confirmed", "unconfirmed"];
|
|
26
|
+
/**
|
|
27
|
+
* Who answered a claim: the `runner` (a cucumber report's digest-tagged
|
|
28
|
+
* scenarios, matched mechanically by `--results`), the `external-runner` (an
|
|
29
|
+
* API contract-test report's operationId-matched entries, matched mechanically
|
|
30
|
+
* by `--contract-results` — external because loam did not generate the suite
|
|
31
|
+
* the way it generates the gherkin, so the join key is the operationId the
|
|
32
|
+
* claim asserts rather than a digest loam stamped), or an `agent` (somebody's
|
|
33
|
+
* word about the code, taken back by `--record`). On the record so a reviewer
|
|
34
|
+
* can tell a green run from an assertion. `external-runner` never answers a
|
|
35
|
+
* `scenario.tested` claim, which is what keeps the attested boundary —
|
|
36
|
+
* `attestedClaims` in `./record.ts` — exactly where it was.
|
|
37
|
+
*/
|
|
38
|
+
export const ANSWERED_BY = ["runner", "external-runner", "agent"];
|
|
39
|
+
/**
|
|
40
|
+
* Accept an answer set only if it answers THIS checklist: every claim once, no
|
|
41
|
+
* claim twice, nothing extra, and no "confirmed" that shows nothing.
|
|
42
|
+
*
|
|
43
|
+
* The refusals are the point of the command. If a stale or partial answer file
|
|
44
|
+
* could be recorded, `verification.yaml` would say a claim was checked when
|
|
45
|
+
* nobody ever asked it — and a record that can lie is worse than no record,
|
|
46
|
+
* because it looks like evidence.
|
|
47
|
+
*
|
|
48
|
+
* `runnerOwned` is the composition rule under `--results` and
|
|
49
|
+
* `--contract-results`: ids a mechanical report answers — scenario claims under
|
|
50
|
+
* the first flag, api.exposes claims under the second — which an answers file
|
|
51
|
+
* must therefore not touch. An entry naming one refuses with its own diagnosis
|
|
52
|
+
* — the id IS on the feature's checklist, so calling it unknown would send the
|
|
53
|
+
* caller hunting a staleness that is not there. `--diff-answers`
|
|
54
|
+
* (commands/verify/cross/diff.ts) calls this with it UNSET on purpose: under a
|
|
55
|
+
* diff both agents answer everything in scope, scenario claims included,
|
|
56
|
+
* because the diff consults no report — so making scenario claims runner-only
|
|
57
|
+
* even without `--results` would break that lens.
|
|
58
|
+
*
|
|
59
|
+
* `context` is what turns the last line from a circle into an instruction. In a
|
|
60
|
+
* fleet the commonest way to "miss" a claim is that it is not yours: the
|
|
61
|
+
* checklist spans ten repositories and this one can only answer its own. Told
|
|
62
|
+
* only to "answer the claims it lists", an agent in the payment-service repo
|
|
63
|
+
* re-runs verify, sees the same checkout-web claims it cannot speak for, and
|
|
64
|
+
* either loops or invents evidence. So when every unanswered claim belongs to
|
|
65
|
+
* another service, say THAT, and name the command that records them.
|
|
66
|
+
*/
|
|
67
|
+
export function checkAnswers(claims, raw, runnerOwned, context) {
|
|
68
|
+
const refuse = (code, message) => ({ ok: false, code, message });
|
|
69
|
+
const list = Array.isArray(raw)
|
|
70
|
+
? raw
|
|
71
|
+
: isRecord(raw) && Array.isArray(raw["answers"])
|
|
72
|
+
? raw["answers"]
|
|
73
|
+
: null;
|
|
74
|
+
if (list === null) {
|
|
75
|
+
return refuse("answers-unreadable", "Expected a JSON array of answers, or an object with an `answers` array: " +
|
|
76
|
+
`[{ "id": "...", "verdict": "confirmed", "evidence": ["file:line"] }]`);
|
|
77
|
+
}
|
|
78
|
+
const answers = [];
|
|
79
|
+
for (const [i, entry] of list.entries()) {
|
|
80
|
+
const where = `answer ${i + 1}`;
|
|
81
|
+
if (!isRecord(entry))
|
|
82
|
+
return refuse("answers-unreadable", `${where} is not an object`);
|
|
83
|
+
const id = entry["id"];
|
|
84
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
85
|
+
return refuse("answers-unreadable", `${where} has no claim id`);
|
|
86
|
+
}
|
|
87
|
+
const verdict = entry["verdict"];
|
|
88
|
+
if (typeof verdict !== "string" || !VERDICTS.includes(verdict)) {
|
|
89
|
+
return refuse("answers-unreadable", `${where} ('${id}') has verdict ${JSON.stringify(verdict)} — expected one of ${VERDICTS.join(", ")}`);
|
|
90
|
+
}
|
|
91
|
+
const note = typeof entry["note"] === "string" ? entry["note"].trim() : "";
|
|
92
|
+
answers.push({
|
|
93
|
+
id,
|
|
94
|
+
verdict: verdict,
|
|
95
|
+
evidence: stringList(entry["evidence"]),
|
|
96
|
+
...(note.length > 0 ? { note } : {}),
|
|
97
|
+
answered_by: "agent",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const byId = new Map();
|
|
101
|
+
const twice = [];
|
|
102
|
+
for (const a of answers) {
|
|
103
|
+
if (byId.has(a.id))
|
|
104
|
+
twice.push(a.id);
|
|
105
|
+
else
|
|
106
|
+
byId.set(a.id, a);
|
|
107
|
+
}
|
|
108
|
+
const known = new Set(claims.map((c) => c.id));
|
|
109
|
+
const strayIds = [...new Set(answers.map((a) => a.id).filter((id) => !known.has(id)))];
|
|
110
|
+
const runnerHit = strayIds.filter((id) => runnerOwned?.has(id) === true);
|
|
111
|
+
const unknown = strayIds.filter((id) => runnerOwned?.has(id) !== true);
|
|
112
|
+
const missingClaims = claims.filter((c) => !byId.has(c.id));
|
|
113
|
+
const missing = missingClaims.map((c) => c.id);
|
|
114
|
+
if (runnerHit.length + unknown.length + missing.length + twice.length > 0) {
|
|
115
|
+
const parts = [
|
|
116
|
+
runnerHit.length > 0
|
|
117
|
+
? `${runnerHit.length} answer(s) name claim(s) a report owns under --results or --contract-results: ${runnerHit.join(", ")} — the report answers those; take them out of the answers file`
|
|
118
|
+
: "",
|
|
119
|
+
unknown.length > 0 ? `${unknown.length} answer(s) name a claim that is not on the checklist: ${unknown.join(", ")}` : "",
|
|
120
|
+
missing.length > 0 ? `${missing.length} claim(s) have no answer: ${missing.join(", ")}` : "",
|
|
121
|
+
twice.length > 0 ? `${twice.length} claim(s) answered more than once: ${[...new Set(twice)].join(", ")}` : "",
|
|
122
|
+
].filter((s) => s.length > 0);
|
|
123
|
+
return refuse("answers-mismatch", `These answers do not match the checklist — ${parts.join("; ")}. ` +
|
|
124
|
+
missingAdvice(missingClaims, claims.filter((c) => byId.has(c.id)), context));
|
|
125
|
+
}
|
|
126
|
+
const unevidenced = claims
|
|
127
|
+
.map((c) => byId.get(c.id))
|
|
128
|
+
.filter((a) => a.verdict === "confirmed" && a.evidence.length === 0)
|
|
129
|
+
.map((a) => a.id);
|
|
130
|
+
if (unevidenced.length > 0) {
|
|
131
|
+
return refuse("answers-unevidenced", `${unevidenced.length} claim(s) are confirmed with no evidence: ${unevidenced.join(", ")}. A confirmation with nothing behind it is an assertion — give a file:line, or answer 'unconfirmed'.`);
|
|
132
|
+
}
|
|
133
|
+
// Checklist order, not the order they were written in: the record reads in the
|
|
134
|
+
// order the questions were asked, whatever the agent did.
|
|
135
|
+
return { ok: true, answers: claims.map((c) => byId.get(c.id)) };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The last sentence of an `answers-mismatch`. "Re-run and answer the claims it
|
|
139
|
+
* lists" is sound advice only when the caller CAN answer them. In a fleet the
|
|
140
|
+
* checklist is federated — a feature's claims are filed under the service whose
|
|
141
|
+
* code answers them — so the honest diagnosis for a repo that answered
|
|
142
|
+
* everything it owns is "the rest is not yours", together with the form that
|
|
143
|
+
* records the rest. Anything else sends the caller round the same loop.
|
|
144
|
+
*/
|
|
145
|
+
function missingAdvice(missing, answered, context) {
|
|
146
|
+
const generic = "Re-run `loam verify` and answer the claims it lists.";
|
|
147
|
+
if (context === undefined || missing.length === 0 || answered.length === 0)
|
|
148
|
+
return generic;
|
|
149
|
+
// The signature of "I answered mine and the rest is not mine": the services
|
|
150
|
+
// that own the unanswered claims and the services that own the answered ones
|
|
151
|
+
// do not overlap. A single-service checklist with one claim forgotten fails
|
|
152
|
+
// this test and keeps the generic advice, which is the right advice there —
|
|
153
|
+
// that claim really is the caller's to answer.
|
|
154
|
+
const owners = [...new Set(missing.map((c) => c.subject))].sort();
|
|
155
|
+
const covered = new Set(answered.map((c) => c.subject));
|
|
156
|
+
if (owners.some((s) => covered.has(s)))
|
|
157
|
+
return generic;
|
|
158
|
+
const form = `\`loam verify ${context.feature} --record answers.json --service <svc>\``;
|
|
159
|
+
const mine = context.service;
|
|
160
|
+
return mine === undefined
|
|
161
|
+
? `Those claims are owned by ${owners.join(", ")} — each service attests its own from its own repository (${form}); ` +
|
|
162
|
+
"this all-at-once form has to answer the whole checklist in one file."
|
|
163
|
+
: `Every unanswered claim belongs to a different service (${owners.join(", ")}) and this repository can only attest '${mine}' — ` +
|
|
164
|
+
`run ${form} in each of those repositories instead.`;
|
|
165
|
+
}
|
|
166
|
+
function stringList(v) {
|
|
167
|
+
if (typeof v === "string")
|
|
168
|
+
return v.trim().length > 0 ? [v.trim()] : [];
|
|
169
|
+
if (!Array.isArray(v))
|
|
170
|
+
return [];
|
|
171
|
+
return v.map((x) => String(x).trim()).filter((s) => s.length > 0);
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=answers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answers.js","sourceRoot":"","sources":["../../../src/core/verify/answers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The answers, and the one thing loam checks about them.\n *\n * loam does not judge an answer. It cannot: it never read the code. What it\n * guarantees is that every question was asked and that every answer sits next\n * to the question it answers — so an answer set that does not correspond to the\n * current checklist is refused rather than merged. An unchecked claim must\n * never be able to masquerade as checked, and that refusal is `checkAnswers`.\n *\n * A scenario claim is answered mechanically wherever it can be: `--results`\n * reads a cucumber JSON report and matches it by the digest tag `loam gherkin`\n * stamped, and only a green run confirms one (`core/results.ts`). Where it\n * cannot — a legacy service whose suite is months away, which is the fleet loam\n * exists for — `--record` may still take an agent's word for it, and\n * `./record.ts` marks that answer attested rather than run.\n */\nimport { isRecord } from \"../kernel/records.js\";\nimport { type Claim } from \"./checklist.js\";\nimport { type EvidencePin } from \"./pins/pin.js\";\n\n/**\n * Two verdicts, deliberately. Anything an agent cannot show evidence for is\n * unconfirmed — a third value for \"not applicable\" would absorb every claim\n * nobody wanted to answer.\n */\nexport const VERDICTS = [\"confirmed\", \"unconfirmed\"] as const;\nexport type Verdict = (typeof VERDICTS)[number];\n\n/**\n * Who answered a claim: the `runner` (a cucumber report's digest-tagged\n * scenarios, matched mechanically by `--results`), the `external-runner` (an\n * API contract-test report's operationId-matched entries, matched mechanically\n * by `--contract-results` — external because loam did not generate the suite\n * the way it generates the gherkin, so the join key is the operationId the\n * claim asserts rather than a digest loam stamped), or an `agent` (somebody's\n * word about the code, taken back by `--record`). On the record so a reviewer\n * can tell a green run from an assertion. `external-runner` never answers a\n * `scenario.tested` claim, which is what keeps the attested boundary —\n * `attestedClaims` in `./record.ts` — exactly where it was.\n */\nexport const ANSWERED_BY = [\"runner\", \"external-runner\", \"agent\"] as const;\nexport type AnsweredBy = (typeof ANSWERED_BY)[number];\n\nexport interface Answer {\n id: string;\n verdict: Verdict;\n /** Where it can be seen — `file:line`, or a report scenario for the runner. Required for `confirmed`. */\n evidence: string[];\n note?: string;\n answered_by: AnsweredBy;\n /**\n * loam's own stamp, never caller input: `validateServiceEvidence` computes\n * these from the attested commit's blobs and the record path attaches them.\n * `checkAnswers` below deliberately never reads the field, so an\n * `evidence_pins` key in an answers FILE is ignored, not honored — an agent\n * must not be able to hand loam a pin loam did not derive.\n */\n evidence_pins?: EvidencePin[];\n}\n\n/** Why an answer set was refused. Each names a different way it fails to answer. */\nexport type AnswerRefusal = \"answers-unreadable\" | \"answers-mismatch\" | \"answers-unevidenced\";\n\nexport type AnswerCheck =\n /** The answers, re-ordered to the checklist, one per claim. */\n | { ok: true; answers: Answer[] }\n | { ok: false; code: AnswerRefusal; message: string };\n\n/**\n * Accept an answer set only if it answers THIS checklist: every claim once, no\n * claim twice, nothing extra, and no \"confirmed\" that shows nothing.\n *\n * The refusals are the point of the command. If a stale or partial answer file\n * could be recorded, `verification.yaml` would say a claim was checked when\n * nobody ever asked it — and a record that can lie is worse than no record,\n * because it looks like evidence.\n *\n * `runnerOwned` is the composition rule under `--results` and\n * `--contract-results`: ids a mechanical report answers — scenario claims under\n * the first flag, api.exposes claims under the second — which an answers file\n * must therefore not touch. An entry naming one refuses with its own diagnosis\n * — the id IS on the feature's checklist, so calling it unknown would send the\n * caller hunting a staleness that is not there. `--diff-answers`\n * (commands/verify/cross/diff.ts) calls this with it UNSET on purpose: under a\n * diff both agents answer everything in scope, scenario claims included,\n * because the diff consults no report — so making scenario claims runner-only\n * even without `--results` would break that lens.\n *\n * `context` is what turns the last line from a circle into an instruction. In a\n * fleet the commonest way to \"miss\" a claim is that it is not yours: the\n * checklist spans ten repositories and this one can only answer its own. Told\n * only to \"answer the claims it lists\", an agent in the payment-service repo\n * re-runs verify, sees the same checkout-web claims it cannot speak for, and\n * either loops or invents evidence. So when every unanswered claim belongs to\n * another service, say THAT, and name the command that records them.\n */\nexport function checkAnswers(\n claims: Claim[],\n raw: unknown,\n runnerOwned?: ReadonlySet<string>,\n context?: { feature: string; service?: string },\n): AnswerCheck {\n const refuse = (code: AnswerRefusal, message: string): AnswerCheck => ({ ok: false, code, message });\n\n const list = Array.isArray(raw)\n ? raw\n : isRecord(raw) && Array.isArray(raw[\"answers\"])\n ? (raw[\"answers\"] as unknown[])\n : null;\n if (list === null) {\n return refuse(\n \"answers-unreadable\",\n \"Expected a JSON array of answers, or an object with an `answers` array: \" +\n `[{ \"id\": \"...\", \"verdict\": \"confirmed\", \"evidence\": [\"file:line\"] }]`,\n );\n }\n\n const answers: Answer[] = [];\n for (const [i, entry] of list.entries()) {\n const where = `answer ${i + 1}`;\n if (!isRecord(entry)) return refuse(\"answers-unreadable\", `${where} is not an object`);\n const id = entry[\"id\"];\n if (typeof id !== \"string\" || id.length === 0) {\n return refuse(\"answers-unreadable\", `${where} has no claim id`);\n }\n const verdict = entry[\"verdict\"];\n if (typeof verdict !== \"string\" || !(VERDICTS as readonly string[]).includes(verdict)) {\n return refuse(\n \"answers-unreadable\",\n `${where} ('${id}') has verdict ${JSON.stringify(verdict)} — expected one of ${VERDICTS.join(\", \")}`,\n );\n }\n const note = typeof entry[\"note\"] === \"string\" ? entry[\"note\"].trim() : \"\";\n answers.push({\n id,\n verdict: verdict as Verdict,\n evidence: stringList(entry[\"evidence\"]),\n ...(note.length > 0 ? { note } : {}),\n answered_by: \"agent\",\n });\n }\n\n const byId = new Map<string, Answer>();\n const twice: string[] = [];\n for (const a of answers) {\n if (byId.has(a.id)) twice.push(a.id);\n else byId.set(a.id, a);\n }\n const known = new Set(claims.map((c) => c.id));\n const strayIds = [...new Set(answers.map((a) => a.id).filter((id) => !known.has(id)))];\n const runnerHit = strayIds.filter((id) => runnerOwned?.has(id) === true);\n const unknown = strayIds.filter((id) => runnerOwned?.has(id) !== true);\n const missingClaims = claims.filter((c) => !byId.has(c.id));\n const missing = missingClaims.map((c) => c.id);\n if (runnerHit.length + unknown.length + missing.length + twice.length > 0) {\n const parts = [\n runnerHit.length > 0\n ? `${runnerHit.length} answer(s) name claim(s) a report owns under --results or --contract-results: ${runnerHit.join(\", \")} — the report answers those; take them out of the answers file`\n : \"\",\n unknown.length > 0 ? `${unknown.length} answer(s) name a claim that is not on the checklist: ${unknown.join(\", \")}` : \"\",\n missing.length > 0 ? `${missing.length} claim(s) have no answer: ${missing.join(\", \")}` : \"\",\n twice.length > 0 ? `${twice.length} claim(s) answered more than once: ${[...new Set(twice)].join(\", \")}` : \"\",\n ].filter((s) => s.length > 0);\n return refuse(\n \"answers-mismatch\",\n `These answers do not match the checklist — ${parts.join(\"; \")}. ` +\n missingAdvice(missingClaims, claims.filter((c) => byId.has(c.id)), context),\n );\n }\n\n const unevidenced = claims\n .map((c) => byId.get(c.id)!)\n .filter((a) => a.verdict === \"confirmed\" && a.evidence.length === 0)\n .map((a) => a.id);\n if (unevidenced.length > 0) {\n return refuse(\n \"answers-unevidenced\",\n `${unevidenced.length} claim(s) are confirmed with no evidence: ${unevidenced.join(\", \")}. A confirmation with nothing behind it is an assertion — give a file:line, or answer 'unconfirmed'.`,\n );\n }\n\n // Checklist order, not the order they were written in: the record reads in the\n // order the questions were asked, whatever the agent did.\n return { ok: true, answers: claims.map((c) => byId.get(c.id)!) };\n}\n\n/**\n * The last sentence of an `answers-mismatch`. \"Re-run and answer the claims it\n * lists\" is sound advice only when the caller CAN answer them. In a fleet the\n * checklist is federated — a feature's claims are filed under the service whose\n * code answers them — so the honest diagnosis for a repo that answered\n * everything it owns is \"the rest is not yours\", together with the form that\n * records the rest. Anything else sends the caller round the same loop.\n */\nfunction missingAdvice(\n missing: Claim[],\n answered: Claim[],\n context?: { feature: string; service?: string },\n): string {\n const generic = \"Re-run `loam verify` and answer the claims it lists.\";\n if (context === undefined || missing.length === 0 || answered.length === 0) return generic;\n // The signature of \"I answered mine and the rest is not mine\": the services\n // that own the unanswered claims and the services that own the answered ones\n // do not overlap. A single-service checklist with one claim forgotten fails\n // this test and keeps the generic advice, which is the right advice there —\n // that claim really is the caller's to answer.\n const owners = [...new Set(missing.map((c) => c.subject))].sort();\n const covered = new Set(answered.map((c) => c.subject));\n if (owners.some((s) => covered.has(s))) return generic;\n const form = `\\`loam verify ${context.feature} --record answers.json --service <svc>\\``;\n const mine = context.service;\n return mine === undefined\n ? `Those claims are owned by ${owners.join(\", \")} — each service attests its own from its own repository (${form}); ` +\n \"this all-at-once form has to answer the whole checklist in one file.\"\n : `Every unanswered claim belongs to a different service (${owners.join(\", \")}) and this repository can only attest '${mine}' — ` +\n `run ${form} in each of those repositories instead.`;\n}\n\nfunction stringList(v: unknown): string[] {\n if (typeof v === \"string\") return v.trim().length > 0 ? [v.trim()] : [];\n if (!Array.isArray(v)) return [];\n return v.map((x) => String(x).trim()).filter((s) => s.length > 0);\n}\n\n"]}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checklist plus answers becomes a record — once for a feature answered all at
|
|
3
|
+
* once, once for a fleet answering service by service.
|
|
4
|
+
*
|
|
5
|
+
* The federated form is separate from `buildVerification` and not a flag on it
|
|
6
|
+
* because the two disagree about what an unanswered claim means: answering
|
|
7
|
+
* everything at once, a missing answer is a gap in this run, while a federated
|
|
8
|
+
* record is assembled from attestations that each covered one service and were
|
|
9
|
+
* never expected to cover the rest.
|
|
10
|
+
*/
|
|
11
|
+
import {} from "./checklist.js";
|
|
12
|
+
import {} from "./answers.js";
|
|
13
|
+
import {} from "./record.js";
|
|
14
|
+
export function buildVerification(checklist, answers, today, reports = {}) {
|
|
15
|
+
const byId = new Map(answers.map((a) => [a.id, a]));
|
|
16
|
+
const claims = checklist.claims.map((c) => {
|
|
17
|
+
// Callers hand in an answer per claim — `runnerAnswers` is total over the
|
|
18
|
+
// checklist, and both other callers map the checklist to build theirs. The
|
|
19
|
+
// branch is here because the alternative to a diagnosis is a `TypeError` on
|
|
20
|
+
// `a.verdict`, which cli.ts reports as `internal` with a stack about a
|
|
21
|
+
// property of `undefined` and no word about which claim went missing.
|
|
22
|
+
const a = byId.get(c.id);
|
|
23
|
+
if (a === undefined)
|
|
24
|
+
throw new Error(`buildVerification: no answer for claim ${c.id}`);
|
|
25
|
+
// Deliberately no `evidence_pins` here: the legacy all-at-once form binds
|
|
26
|
+
// to no commit, so there is no attested blob to pin — and none is ever
|
|
27
|
+
// handed in, because pins are computed inside the federated evidence
|
|
28
|
+
// validation this form never runs.
|
|
29
|
+
return {
|
|
30
|
+
id: c.id,
|
|
31
|
+
kind: c.kind,
|
|
32
|
+
claim: c.claim,
|
|
33
|
+
verdict: a.verdict,
|
|
34
|
+
answered_by: a.answered_by,
|
|
35
|
+
evidence: a.evidence,
|
|
36
|
+
...(a.note === undefined ? {} : { note: a.note }),
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
const confirmed = claims.filter((c) => c.verdict === "confirmed").length;
|
|
40
|
+
return {
|
|
41
|
+
feature: checklist.feature,
|
|
42
|
+
recorded: today,
|
|
43
|
+
checklist: checklist.digest,
|
|
44
|
+
summary: { claims: claims.length, confirmed, unconfirmed: claims.length - confirmed },
|
|
45
|
+
claims,
|
|
46
|
+
...(reports.results === undefined ? {} : { report: reports.results }),
|
|
47
|
+
...(reports.contract === undefined ? {} : { contractReport: reports.contract }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function buildFederatedVerification(checklist, attestation, answers, previous) {
|
|
51
|
+
const { service, recorded, commit, reports, docsCommit } = attestation;
|
|
52
|
+
const currentById = new Map(checklist.claims.map((claim) => [claim.id, claim]));
|
|
53
|
+
const localIds = new Set(checklist.claims.filter((claim) => claim.subject === service).map((claim) => claim.id));
|
|
54
|
+
// An old all-at-once record remains readable, but its answers have no commit
|
|
55
|
+
// attestation and therefore cannot silently become somebody else's
|
|
56
|
+
// federated contribution. The first schema-2 write starts a clean federation.
|
|
57
|
+
const previouslyAttested = new Set((previous?.attestations ?? []).flatMap((a) => a.claims));
|
|
58
|
+
const retained = (previous?.schema === 2 ? previous.claims : []).filter((claim) => {
|
|
59
|
+
const current = currentById.get(claim.id);
|
|
60
|
+
return current !== undefined && current.subject !== service && previouslyAttested.has(claim.id);
|
|
61
|
+
});
|
|
62
|
+
const local = answers.map((answer) => {
|
|
63
|
+
// Same contract as `buildVerification`: an answer is built from the live
|
|
64
|
+
// checklist, so it always names a current claim. Off-checklist answers do
|
|
65
|
+
// NOT belong here — `discarded` means "an answer the PREVIOUS record held",
|
|
66
|
+
// and routing an incoming stray into it would change what the printed
|
|
67
|
+
// notice tells an operator. A caller that reaches this gets told which
|
|
68
|
+
// answer it was rather than a `TypeError` on `claim.kind`.
|
|
69
|
+
const claim = currentById.get(answer.id);
|
|
70
|
+
if (claim === undefined) {
|
|
71
|
+
throw new Error(`buildFederatedVerification: answer ${answer.id} names no current claim`);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
id: claim.id,
|
|
75
|
+
kind: claim.kind,
|
|
76
|
+
subject: claim.subject,
|
|
77
|
+
claim: claim.claim,
|
|
78
|
+
verdict: answer.verdict,
|
|
79
|
+
answered_by: answer.answered_by,
|
|
80
|
+
evidence: answer.evidence,
|
|
81
|
+
...(answer.note === undefined ? {} : { note: answer.note }),
|
|
82
|
+
// The pins the evidence validation stamped onto this repository's
|
|
83
|
+
// answers ride into the record here; retained other-service claims are
|
|
84
|
+
// spread whole below, so THEIR pins survive a re-record untouched.
|
|
85
|
+
...(answer.evidence_pins === undefined ? {} : { evidence_pins: answer.evidence_pins }),
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
const byId = new Map([...retained, ...local].map((claim) => [claim.id, claim]));
|
|
89
|
+
const claims = checklist.claims.flatMap((claim) => {
|
|
90
|
+
const answer = byId.get(claim.id);
|
|
91
|
+
if (answer === undefined)
|
|
92
|
+
return [];
|
|
93
|
+
// Normalize retained legacy entries to the current question text and add
|
|
94
|
+
// the subject schema 2 needs for future independent replacement.
|
|
95
|
+
return [{ ...answer, kind: claim.kind, subject: claim.subject, claim: claim.claim }];
|
|
96
|
+
});
|
|
97
|
+
const retainedAttestations = (previous?.attestations ?? []).flatMap((attestation) => {
|
|
98
|
+
if (attestation.service === service)
|
|
99
|
+
return [];
|
|
100
|
+
const ids = attestation.claims.filter((id) => currentById.get(id)?.subject === attestation.service && byId.has(id));
|
|
101
|
+
return ids.length === 0 ? [] : [{ ...attestation, claims: ids }];
|
|
102
|
+
});
|
|
103
|
+
const attestations = [
|
|
104
|
+
...retainedAttestations,
|
|
105
|
+
{
|
|
106
|
+
service,
|
|
107
|
+
commit,
|
|
108
|
+
recorded,
|
|
109
|
+
claims: checklist.claims.filter((c) => localIds.has(c.id)).map((c) => c.id),
|
|
110
|
+
// What THIS repository answered, and what it answered it against. Both
|
|
111
|
+
// ride with the attestation rather than the record for the reason the
|
|
112
|
+
// reports do: they are one repository's facts, pruned and retained with
|
|
113
|
+
// that repository's answers.
|
|
114
|
+
checklist: checklist.digest,
|
|
115
|
+
...(docsCommit === undefined ? {} : { docsCommit }),
|
|
116
|
+
// Filed with the attestation, not the record: each report is one
|
|
117
|
+
// repository's run, and it is pruned or retained with that repository's
|
|
118
|
+
// answers rather than outliving them — the contract report under the
|
|
119
|
+
// same rule as the cucumber one.
|
|
120
|
+
...(reports.results === undefined ? {} : { report: reports.results }),
|
|
121
|
+
...(reports.contract === undefined ? {} : { contractReport: reports.contract }),
|
|
122
|
+
},
|
|
123
|
+
].sort((a, b) => a.service.localeCompare(b.service));
|
|
124
|
+
const confirmed = claims.filter((claim) => claim.verdict === "confirmed").length;
|
|
125
|
+
const unconfirmed = claims.length - confirmed;
|
|
126
|
+
const unanswered = checklist.claims.length - claims.length;
|
|
127
|
+
// Everything the old record answered that the new one does not. The caller
|
|
128
|
+
// prints it; nothing here decides it is acceptable.
|
|
129
|
+
const discarded = (previous?.claims ?? [])
|
|
130
|
+
.filter((claim) => !byId.has(claim.id))
|
|
131
|
+
.map((claim) => ({
|
|
132
|
+
id: claim.id,
|
|
133
|
+
claim: claim.claim,
|
|
134
|
+
...(claim.subject === undefined ? {} : { subject: claim.subject }),
|
|
135
|
+
reason: currentById.has(claim.id) ? "unattested" : "off-checklist",
|
|
136
|
+
}));
|
|
137
|
+
return {
|
|
138
|
+
verification: {
|
|
139
|
+
schema: 2,
|
|
140
|
+
feature: checklist.feature,
|
|
141
|
+
recorded,
|
|
142
|
+
checklist: checklist.digest,
|
|
143
|
+
summary: { claims: checklist.claims.length, confirmed, unconfirmed, unanswered },
|
|
144
|
+
claims,
|
|
145
|
+
attestations,
|
|
146
|
+
},
|
|
147
|
+
discarded,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/core/verify/build.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Checklist plus answers becomes a record — once for a feature answered all at\n * once, once for a fleet answering service by service.\n *\n * The federated form is separate from `buildVerification` and not a flag on it\n * because the two disagree about what an unanswered claim means: answering\n * everything at once, a missing answer is a gap in this run, while a federated\n * record is assembled from attestations that each covered one service and were\n * never expected to cover the rest.\n */\nimport { type Checklist } from \"./checklist.js\";\nimport { type Answer } from \"./answers.js\";\nimport {\n type ConsumedReports,\n type RecordedClaim,\n type ServiceAttestation,\n type Verification,\n} from \"./record.js\";\n\nexport function buildVerification(\n checklist: Checklist,\n answers: Answer[],\n today: string,\n reports: ConsumedReports = {},\n): Verification {\n const byId = new Map(answers.map((a) => [a.id, a]));\n const claims: RecordedClaim[] = checklist.claims.map((c) => {\n // Callers hand in an answer per claim — `runnerAnswers` is total over the\n // checklist, and both other callers map the checklist to build theirs. The\n // branch is here because the alternative to a diagnosis is a `TypeError` on\n // `a.verdict`, which cli.ts reports as `internal` with a stack about a\n // property of `undefined` and no word about which claim went missing.\n const a = byId.get(c.id);\n if (a === undefined) throw new Error(`buildVerification: no answer for claim ${c.id}`);\n // Deliberately no `evidence_pins` here: the legacy all-at-once form binds\n // to no commit, so there is no attested blob to pin — and none is ever\n // handed in, because pins are computed inside the federated evidence\n // validation this form never runs.\n return {\n id: c.id,\n kind: c.kind,\n claim: c.claim,\n verdict: a.verdict,\n answered_by: a.answered_by,\n evidence: a.evidence,\n ...(a.note === undefined ? {} : { note: a.note }),\n };\n });\n const confirmed = claims.filter((c) => c.verdict === \"confirmed\").length;\n return {\n feature: checklist.feature,\n recorded: today,\n checklist: checklist.digest,\n summary: { claims: claims.length, confirmed, unconfirmed: claims.length - confirmed },\n claims,\n ...(reports.results === undefined ? {} : { report: reports.results }),\n ...(reports.contract === undefined ? {} : { contractReport: reports.contract }),\n };\n}\n\n/**\n * An answer the previous record held that the new one does not carry.\n *\n * Dropping these is correct — see `buildFederatedVerification` — but doing it\n * silently is not: a schema-1 record answers the WHOLE checklist, so the first\n * federated write turns a green record into a partial one, and an operator who\n * is not told which answers went missing reads the drop as loam losing data.\n */\nexport interface DiscardedAnswer {\n id: string;\n claim: string;\n /** The service the record filed it under, when it said. Absent in schema-1 records. */\n subject?: string;\n /**\n * `off-checklist` — the feature changed and nobody asks this question any\n * more. `unattested` — the question still stands, but no service attestation\n * binds the answer to a commit, so it cannot be carried into a federated\n * record; the owning repository has to record it again.\n */\n reason: \"off-checklist\" | \"unattested\";\n}\n\nexport interface FederatedBuild {\n verification: Verification;\n /** In checklist-independent record order — see {@link DiscardedAnswer}. */\n discarded: DiscardedAnswer[];\n}\n\n/**\n * Replace one service's contribution while retaining current contributions\n * from other repositories. Claim ids removed from the live checklist are\n * pruned from both the answers and their attestations, so a changed feature can\n * never keep a green answer to a question it no longer asks.\n */\n/** One service's attestation: whose word it is, when, and against which commit. */\nexport interface Attestation {\n service: string;\n recorded: string;\n commit: string;\n reports: ConsumedReports;\n /** The docs-repo HEAD the checklist was derived from, when git could answer. */\n docsCommit?: string;\n}\n\nexport function buildFederatedVerification(\n checklist: Checklist,\n attestation: Attestation,\n answers: Answer[],\n previous: Verification | null,\n): FederatedBuild {\n const { service, recorded, commit, reports, docsCommit } = attestation;\n const currentById = new Map(checklist.claims.map((claim) => [claim.id, claim]));\n const localIds = new Set(checklist.claims.filter((claim) => claim.subject === service).map((claim) => claim.id));\n\n // An old all-at-once record remains readable, but its answers have no commit\n // attestation and therefore cannot silently become somebody else's\n // federated contribution. The first schema-2 write starts a clean federation.\n const previouslyAttested = new Set((previous?.attestations ?? []).flatMap((a) => a.claims));\n const retained = (previous?.schema === 2 ? previous.claims : []).filter((claim) => {\n const current = currentById.get(claim.id);\n return current !== undefined && current.subject !== service && previouslyAttested.has(claim.id);\n });\n const local = answers.map((answer): RecordedClaim => {\n // Same contract as `buildVerification`: an answer is built from the live\n // checklist, so it always names a current claim. Off-checklist answers do\n // NOT belong here — `discarded` means \"an answer the PREVIOUS record held\",\n // and routing an incoming stray into it would change what the printed\n // notice tells an operator. A caller that reaches this gets told which\n // answer it was rather than a `TypeError` on `claim.kind`.\n const claim = currentById.get(answer.id);\n if (claim === undefined) {\n throw new Error(`buildFederatedVerification: answer ${answer.id} names no current claim`);\n }\n return {\n id: claim.id,\n kind: claim.kind,\n subject: claim.subject,\n claim: claim.claim,\n verdict: answer.verdict,\n answered_by: answer.answered_by,\n evidence: answer.evidence,\n ...(answer.note === undefined ? {} : { note: answer.note }),\n // The pins the evidence validation stamped onto this repository's\n // answers ride into the record here; retained other-service claims are\n // spread whole below, so THEIR pins survive a re-record untouched.\n ...(answer.evidence_pins === undefined ? {} : { evidence_pins: answer.evidence_pins }),\n };\n });\n const byId = new Map([...retained, ...local].map((claim) => [claim.id, claim]));\n const claims = checklist.claims.flatMap((claim) => {\n const answer = byId.get(claim.id);\n if (answer === undefined) return [];\n // Normalize retained legacy entries to the current question text and add\n // the subject schema 2 needs for future independent replacement.\n return [{ ...answer, kind: claim.kind, subject: claim.subject, claim: claim.claim }];\n });\n\n const retainedAttestations = (previous?.attestations ?? []).flatMap((attestation) => {\n if (attestation.service === service) return [];\n const ids = attestation.claims.filter(\n (id) => currentById.get(id)?.subject === attestation.service && byId.has(id),\n );\n return ids.length === 0 ? [] : [{ ...attestation, claims: ids }];\n });\n const attestations: ServiceAttestation[] = [\n ...retainedAttestations,\n {\n service,\n commit,\n recorded,\n claims: checklist.claims.filter((c) => localIds.has(c.id)).map((c) => c.id),\n // What THIS repository answered, and what it answered it against. Both\n // ride with the attestation rather than the record for the reason the\n // reports do: they are one repository's facts, pruned and retained with\n // that repository's answers.\n checklist: checklist.digest,\n ...(docsCommit === undefined ? {} : { docsCommit }),\n // Filed with the attestation, not the record: each report is one\n // repository's run, and it is pruned or retained with that repository's\n // answers rather than outliving them — the contract report under the\n // same rule as the cucumber one.\n ...(reports.results === undefined ? {} : { report: reports.results }),\n ...(reports.contract === undefined ? {} : { contractReport: reports.contract }),\n },\n ].sort((a, b) => a.service.localeCompare(b.service));\n\n const confirmed = claims.filter((claim) => claim.verdict === \"confirmed\").length;\n const unconfirmed = claims.length - confirmed;\n const unanswered = checklist.claims.length - claims.length;\n\n // Everything the old record answered that the new one does not. The caller\n // prints it; nothing here decides it is acceptable.\n const discarded = (previous?.claims ?? [])\n .filter((claim) => !byId.has(claim.id))\n .map((claim): DiscardedAnswer => ({\n id: claim.id,\n claim: claim.claim,\n ...(claim.subject === undefined ? {} : { subject: claim.subject }),\n reason: currentById.has(claim.id) ? \"unattested\" : \"off-checklist\",\n }));\n\n return {\n verification: {\n schema: 2,\n feature: checklist.feature,\n recorded,\n checklist: checklist.digest,\n summary: { claims: checklist.claims.length, confirmed, unconfirmed, unanswered },\n claims,\n attestations,\n },\n discarded,\n };\n}\n"]}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The questions: what a feature's own artifacts promised, derived mechanically.
|
|
3
|
+
*
|
|
4
|
+
* The original plan was to extract C4 from the built code and diff it against
|
|
5
|
+
* the delta. There is no such extractor and there will not be one — nothing
|
|
6
|
+
* deterministic reads a service and says what its architecture MEANS, and two
|
|
7
|
+
* generated models of the same code disagree in wording every run, so the diff
|
|
8
|
+
* would flap and be switched off inside a week.
|
|
9
|
+
*
|
|
10
|
+
* What survives of the idea is the part that was always deterministic, and it
|
|
11
|
+
* is this module: a feature says this service will exist, it will expose this
|
|
12
|
+
* operation, that service will call it, this scenario will have a test. The
|
|
13
|
+
* list comes from the same files `validate` already reads.
|
|
14
|
+
*
|
|
15
|
+
* The claim ids are a function of the claim and of nothing else — so two runs
|
|
16
|
+
* are diffable, reordering the delta renames nothing, and rewording a scenario
|
|
17
|
+
* DOES rename its claim, because an answer about text nobody wrote must not
|
|
18
|
+
* carry over.
|
|
19
|
+
*
|
|
20
|
+
* `scenarioBodyHash` lives in `core/gherkin/digest.ts` — one recipe shared
|
|
21
|
+
* with `core/gherkin/stamp.ts`, which stamps the same digest as a tag on every
|
|
22
|
+
* generated scenario so `--results` can match a cucumber report back to a
|
|
23
|
+
* claim. A second spelling of the hash would silently stop every scenario
|
|
24
|
+
* claim from being answerable by a run.
|
|
25
|
+
*/
|
|
26
|
+
import { existsSync } from "node:fs";
|
|
27
|
+
import { readFile } from "node:fs/promises";
|
|
28
|
+
import { readAsyncapi } from "../asyncapi/read.js";
|
|
29
|
+
import {} from "../c4/likec4.js";
|
|
30
|
+
import { elementService, serviceResolver } from "../c4/resolve/service.js";
|
|
31
|
+
import { ACTOR_KINDS } from "../vocabulary/maturity.js";
|
|
32
|
+
import { operationIds, operations } from "../openapi/doc.js";
|
|
33
|
+
import { featurePaths, featureSpecPaths } from "../repo/paths.js";
|
|
34
|
+
import { locateServicePaths } from "../repo/service-target.js";
|
|
35
|
+
import { featureSpecServices } from "../repo/repo.js";
|
|
36
|
+
import { enumeratedServiceIds } from "../repo/service-target.js";
|
|
37
|
+
import { FleetContext } from "../fleet-context.js";
|
|
38
|
+
import { parseRequirements } from "../document/parse.js";
|
|
39
|
+
import { scenarioBodyHash } from "../gherkin/digest.js";
|
|
40
|
+
// The kind vocabulary and both hash recipes live in ./claims/identity.ts —
|
|
41
|
+
// one module for everything that decides whether two runs ask the same
|
|
42
|
+
// question, and this one for how the questions are derived.
|
|
43
|
+
import { checklistDigest, claimId, DIGEST_LENGTH, ID_LENGTH, } from "./claims/identity.js";
|
|
44
|
+
/**
|
|
45
|
+
* Derive the checklist from a feature's own artifacts. No code is read here and
|
|
46
|
+
* none should be: the whole design rests on the questions being computed from
|
|
47
|
+
* documents that do not change between runs.
|
|
48
|
+
*/
|
|
49
|
+
export async function featureChecklist(docsDir, featureDir, featureId, fleet) {
|
|
50
|
+
// The per-service loop below resolves living contracts through the
|
|
51
|
+
// enumeration twice per service; without a shared context each of those is
|
|
52
|
+
// a full fleet walk, so deriving one checklist cost O(services²) reads. A
|
|
53
|
+
// caller-supplied context is used when given; otherwise one is created for
|
|
54
|
+
// this derivation alone — the loop still pays a single walk either way.
|
|
55
|
+
const ctx = fleet ?? new FleetContext();
|
|
56
|
+
const seen = new Map();
|
|
57
|
+
const claim = (kind, subject, parts, text) => ({
|
|
58
|
+
id: claimId(featureId, kind, parts, seen),
|
|
59
|
+
kind,
|
|
60
|
+
subject,
|
|
61
|
+
claim: text,
|
|
62
|
+
});
|
|
63
|
+
const exists = [];
|
|
64
|
+
const exposes = [];
|
|
65
|
+
const declares = [];
|
|
66
|
+
const calls = [];
|
|
67
|
+
const scenarios = [];
|
|
68
|
+
const specServices = await featureSpecServices(featureDir, ctx);
|
|
69
|
+
// Architecture — what the delta promised the fleet would look like. Loaded
|
|
70
|
+
// through the context rather than a bare parse, so a caller that derives
|
|
71
|
+
// several checklists in one invocation (`loam gate`'s verification check,
|
|
72
|
+
// the fleet forms) shares one parse per delta — and a batch prefetch
|
|
73
|
+
// (`FleetContext.prefetchLikeC4`) genuinely seeds this load.
|
|
74
|
+
const deltaPath = featurePaths(featureDir).delta;
|
|
75
|
+
if (existsSync(deltaPath)) {
|
|
76
|
+
const res = await ctx.loadLikeC4(deltaPath);
|
|
77
|
+
// A delta nobody can parse promises nothing checkable. `validate` already
|
|
78
|
+
// reports it; inventing claims out of a broken document would only add noise.
|
|
79
|
+
if (res.errors.length === 0) {
|
|
80
|
+
const elements = res.elements;
|
|
81
|
+
for (const e of elements) {
|
|
82
|
+
if (!e.tags.includes(featureId))
|
|
83
|
+
continue;
|
|
84
|
+
// A dotted id is a container INSIDE a service, and an actor is nobody's
|
|
85
|
+
// repository. Neither is a service that has to exist.
|
|
86
|
+
if (e.id.includes(".") || ACTOR_KINDS.has(e.kind.toLowerCase()))
|
|
87
|
+
continue;
|
|
88
|
+
const svc = elementService(e);
|
|
89
|
+
exists.push(claim("service.exists", svc, [svc], `service '${svc}' exists`));
|
|
90
|
+
}
|
|
91
|
+
// The enumerated fleet — `services/` plus this feature's own `specs/`
|
|
92
|
+
// directories, the only place a service the feature INTRODUCES has a
|
|
93
|
+
// directory at all — rides into the resolver so a claim about an edge
|
|
94
|
+
// drawn into a modelled container names the service that owns it. A
|
|
95
|
+
// claim against a service called "api" that has never existed is one no
|
|
96
|
+
// evidence in any repository could ever answer, and the fleet half alone
|
|
97
|
+
// still derived exactly that claim for an introduced service's own
|
|
98
|
+
// container. Unenumerable services/ degrades to the feature's names.
|
|
99
|
+
const svcOf = serviceResolver(elements, new Set([...(await enumeratedServiceIds(docsDir, ctx)), ...specServices]));
|
|
100
|
+
for (const r of res.relationships) {
|
|
101
|
+
// Untagged edges are context for the diagram; an edge with no operation
|
|
102
|
+
// names nothing specific enough to look for in the code.
|
|
103
|
+
if (!r.tags.includes(featureId) || r.op === undefined)
|
|
104
|
+
continue;
|
|
105
|
+
const from = svcOf(r.source);
|
|
106
|
+
const to = svcOf(r.target);
|
|
107
|
+
calls.push({
|
|
108
|
+
...claim("c4.calls", from, [from, to, r.op], `${from} calls '${r.op}' on ${to}`),
|
|
109
|
+
// The same string as the provider's api.exposes token, scanned in the
|
|
110
|
+
// CALLER's cited file — a call site plausibly spells the operationId.
|
|
111
|
+
token: r.op,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const svc of specServices) {
|
|
117
|
+
const paths = featureSpecPaths(featureDir, svc);
|
|
118
|
+
// Contract — only what is NEW. A delta's openapi.yaml is a whole document,
|
|
119
|
+
// not a patch, so authors restate the living API inside it; asking whether a
|
|
120
|
+
// service still exposes what it already exposed is noise.
|
|
121
|
+
const featOps = (await operations(paths.openapi))
|
|
122
|
+
.filter((operation) => !operation.remove)
|
|
123
|
+
.map((operation) => operation.id);
|
|
124
|
+
if (featOps.length > 0) {
|
|
125
|
+
const living = new Set(await operationIds((await locateServicePaths(docsDir, svc, ctx)).openapi));
|
|
126
|
+
for (const op of featOps) {
|
|
127
|
+
if (living.has(op))
|
|
128
|
+
continue;
|
|
129
|
+
exposes.push({
|
|
130
|
+
...claim("api.exposes", svc, [svc, op], `${svc} exposes operationId '${op}'`),
|
|
131
|
+
operation: op,
|
|
132
|
+
token: op,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Events — the same NEW-only discipline on the async axis: a delta's
|
|
137
|
+
// asyncapi.yaml is a complete document too, so what the living contract
|
|
138
|
+
// already sends or receives is restatement, not a promise. Removals are
|
|
139
|
+
// not questions either — the reader already keeps marked declarations out
|
|
140
|
+
// of the sent/received sets — and an unreadable delta promises nothing
|
|
141
|
+
// checkable, exactly like the broken C4 delta above.
|
|
142
|
+
const events = await readAsyncapi(paths.asyncapi);
|
|
143
|
+
if (events.sent.length > 0 || events.received.length > 0) {
|
|
144
|
+
const living = await readAsyncapi((await locateServicePaths(docsDir, svc, ctx)).asyncapi);
|
|
145
|
+
const directions = [
|
|
146
|
+
{ direction: "sends", feat: events.sent, known: new Set(living.sent) },
|
|
147
|
+
{ direction: "receives", feat: events.received, known: new Set(living.received) },
|
|
148
|
+
];
|
|
149
|
+
for (const d of directions) {
|
|
150
|
+
for (const name of d.feat) {
|
|
151
|
+
if (d.known.has(name))
|
|
152
|
+
continue;
|
|
153
|
+
declares.push({
|
|
154
|
+
...claim("event.declares", svc, [svc, d.direction, name], `${svc} declares it ${d.direction} message '${name}'`),
|
|
155
|
+
token: name,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Behaviour — the scenarios of the requirements this feature CHANGES. A BASE
|
|
161
|
+
// requirement is the living state quoted inside the delta, and a REMOVED one
|
|
162
|
+
// is being retired: neither is work anybody has to have done.
|
|
163
|
+
if (existsSync(paths.spec)) {
|
|
164
|
+
for (const r of parseRequirements(await readFile(paths.spec, "utf8"))) {
|
|
165
|
+
if (r.kind === "BASE" || r.kind === "REMOVED")
|
|
166
|
+
continue;
|
|
167
|
+
for (const s of r.scenarios) {
|
|
168
|
+
const body = scenarioBodyHash(svc, s.lines);
|
|
169
|
+
scenarios.push({
|
|
170
|
+
...claim("scenario.tested", svc, [svc, r.id ?? r.name, s.name, body.slice(0, ID_LENGTH)], `scenario '${s.name}' of requirement '${r.name}' (${svc}) is covered by a test`),
|
|
171
|
+
digest: body.slice(0, DIGEST_LENGTH),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// The architecture spec axis, same walk: an arch scenario is a promise like
|
|
177
|
+
// any other — the outbox test nobody was going to write IS the point of the
|
|
178
|
+
// axis. The source file rides in the id tuple AND in the body hash itself
|
|
179
|
+
// (two axes are two namespaces: identically-worded scenarios must stay two
|
|
180
|
+
// questions, and the axis-salted digest is what keeps a business-only test
|
|
181
|
+
// run from answering the arch claim) and in the claim text, so the
|
|
182
|
+
// answering agent knows it is being asked for an integration/ops test, not
|
|
183
|
+
// an acceptance test.
|
|
184
|
+
if (existsSync(paths.archSpec)) {
|
|
185
|
+
for (const r of parseRequirements(await readFile(paths.archSpec, "utf8"))) {
|
|
186
|
+
if (r.kind === "BASE" || r.kind === "REMOVED")
|
|
187
|
+
continue;
|
|
188
|
+
for (const s of r.scenarios) {
|
|
189
|
+
const body = scenarioBodyHash(svc, s.lines, "arch");
|
|
190
|
+
scenarios.push({
|
|
191
|
+
...claim("scenario.tested", svc, [svc, "arch.spec.md", r.id ?? r.name, s.name, body.slice(0, ID_LENGTH)], `scenario '${s.name}' of arch requirement '${r.name}' (${svc}, arch.spec.md) is covered by a test`),
|
|
192
|
+
digest: body.slice(0, DIGEST_LENGTH),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const claims = [...exists, ...exposes, ...declares, ...calls, ...scenarios];
|
|
199
|
+
return { feature: featureId, claims, digest: checklistDigest(claims.map((c) => c.id)) };
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=checklist.js.map
|