@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2635 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +599 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +323 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +180 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +252 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +204 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +270 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +251 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +251 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +337 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +48 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +174 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +90 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +158 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +187 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +220 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/service.js +159 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +144 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +139 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +174 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +287 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/deployment/delta.js +188 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +137 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +210 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +277 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +196 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +197 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +72 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +58 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checklist.js","sourceRoot":"","sources":["../../../src/core/verify/checklist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAa,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,2EAA2E;AAC3E,uEAAuE;AACvE,4DAA4D;AAC5D,OAAO,EACL,eAAe,EACf,OAAO,EACP,aAAa,EACb,SAAS,GAEV,MAAM,sBAAsB,CAAC;AA4C9B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAgB,EAChB,UAAsB,EACtB,SAAiB,EACjB,KAAoB;IAEpB,mEAAmE;IACnE,2EAA2E;IAC3E,0EAA0E;IAC1E,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,GAAG,GAAG,KAAK,IAAI,IAAI,YAAY,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,KAAK,GAAG,CAAC,IAAe,EAAE,OAAe,EAAE,KAAe,EAAE,IAAY,EAAS,EAAE,CAAC,CAAC;QACzF,EAAE,EAAE,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC;QACzC,IAAI;QACJ,OAAO;QACP,KAAK,EAAE,IAAI;KACZ,CAAC,CAAC;IAEH,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAY,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAY,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAY,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAY,EAAE,CAAC;IAE9B,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IAEhE,2EAA2E;IAC3E,yEAAyE;IACzE,0EAA0E;IAC1E,qEAAqE;IACrE,6DAA6D;IAC7D,MAAM,SAAS,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC;IACjD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC5C,0EAA0E;QAC1E,8EAA8E;QAC9E,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAW,GAAG,CAAC,QAAQ,CAAC;YACtC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;oBAAE,SAAS;gBAC1C,wEAAwE;gBACxE,sDAAsD;gBACtD,IAAI,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;oBAAE,SAAS;gBAC1E,MAAM,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;gBAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC,CAAC,CAAC;YAC9E,CAAC;YACD,sEAAsE;YACtE,qEAAqE;YACrE,sEAAsE;YACtE,oEAAoE;YACpE,wEAAwE;YACxE,yEAAyE;YACzE,mEAAmE;YACnE,qEAAqE;YACrE,MAAM,KAAK,GAAG,eAAe,CAC3B,QAAQ,EACR,IAAI,GAAG,CAAS,CAAC,GAAG,CAAC,MAAM,oBAAoB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,YAAY,CAAC,CAAC,CAClF,CAAC;YACF,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;gBAClC,wEAAwE;gBACxE,yDAAyD;gBACzD,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,SAAS;oBAAE,SAAS;gBAChE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;gBAC7B,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;gBAC3B,KAAK,CAAC,IAAI,CAAC;oBACT,GAAG,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC;oBAChF,sEAAsE;oBACtE,sEAAsE;oBACtE,KAAK,EAAE,CAAC,CAAC,EAAE;iBACZ,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,gBAAgB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAEhD,2EAA2E;QAC3E,6EAA6E;QAC7E,0DAA0D;QAC1D,MAAM,OAAO,GAAG,CAAC,MAAM,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;aAC9C,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC;aACxC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,YAAY,CAAC,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YAClG,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;gBACzB,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC7B,OAAO,CAAC,IAAI,CAAC;oBACX,GAAG,KAAK,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,GAAG,GAAG,yBAAyB,EAAE,GAAG,CAAC;oBAC7E,SAAS,EAAE,EAAE;oBACb,KAAK,EAAE,EAAE;iBACV,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,qEAAqE;QACrE,wEAAwE;QACxE,wEAAwE;QACxE,0EAA0E;QAC1E,uEAAuE;QACvE,qDAAqD;QACrD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YAC1F,MAAM,UAAU,GAAG;gBACjB,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;gBACtE,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE;aACzE,CAAC;YACX,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;gBAC3B,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;oBAC1B,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;wBAAE,SAAS;oBAChC,QAAQ,CAAC,IAAI,CAAC;wBACZ,GAAG,KAAK,CACN,gBAAgB,EAChB,GAAG,EACH,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,EACxB,GAAG,GAAG,gBAAgB,CAAC,CAAC,SAAS,aAAa,IAAI,GAAG,CACtD;wBACD,KAAK,EAAE,IAAI;qBACZ,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,6EAA6E;QAC7E,6EAA6E;QAC7E,8DAA8D;QAC9D,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,KAAK,MAAM,CAAC,IAAI,iBAAiB,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;gBACtE,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;oBAAE,SAAS;gBACxD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;oBAC5B,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;oBAC5C,SAAS,CAAC,IAAI,CAAC;wBACb,GAAG,KAAK,CACN,iBAAiB,EACjB,GAAG,EACH,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,EACvD,aAAa,CAAC,CAAC,IAAI,qBAAqB,CAAC,CAAC,IAAI,MAAM,GAAG,wBAAwB,CAChF;wBACD,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC;qBACrC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,4EAA4E;QAC5E,4EAA4E;QAC5E,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,mEAAmE;QACnE,2EAA2E;QAC3E,sBAAsB;QACtB,IAAI,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,KAAK,MAAM,CAAC,IAAI,iBAAiB,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;gBAC1E,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;oBAAE,SAAS;gBACxD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;oBAC5B,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;oBACpD,SAAS,CAAC,IAAI,CAAC;wBACb,GAAG,KAAK,CACN,iBAAiB,EACjB,GAAG,EACH,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,EACvE,aAAa,CAAC,CAAC,IAAI,0BAA0B,CAAC,CAAC,IAAI,MAAM,GAAG,sCAAsC,CACnG;wBACD,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC;qBACrC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,GAAG,SAAS,CAAC,CAAC;IAC5E,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AAC1F,CAAC","sourcesContent":["/**\n * The questions: what a feature's own artifacts promised, derived mechanically.\n *\n * The original plan was to extract C4 from the built code and diff it against\n * the delta. There is no such extractor and there will not be one — nothing\n * deterministic reads a service and says what its architecture MEANS, and two\n * generated models of the same code disagree in wording every run, so the diff\n * would flap and be switched off inside a week.\n *\n * What survives of the idea is the part that was always deterministic, and it\n * is this module: a feature says this service will exist, it will expose this\n * operation, that service will call it, this scenario will have a test. The\n * list comes from the same files `validate` already reads.\n *\n * The claim ids are a function of the claim and of nothing else — so two runs\n * are diffable, reordering the delta renames nothing, and rewording a scenario\n * DOES rename its claim, because an answer about text nobody wrote must not\n * carry over.\n *\n * `scenarioBodyHash` lives in `core/gherkin/digest.ts` — one recipe shared\n * with `core/gherkin/stamp.ts`, which stamps the same digest as a tag on every\n * generated scenario so `--results` can match a cucumber report back to a\n * claim. A second spelling of the hash would silently stop every scenario\n * claim from being answerable by a run.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { readAsyncapi } from \"../asyncapi/read.js\";\nimport { type Elem } from \"../c4/likec4.js\";\nimport { elementService, serviceResolver } from \"../c4/resolve/service.js\";\nimport { ACTOR_KINDS } from \"../vocabulary/maturity.js\";\nimport { operationIds, operations } from \"../openapi/doc.js\";\nimport { featurePaths, featureSpecPaths } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport { featureSpecServices } from \"../repo/repo.js\";\nimport { enumeratedServiceIds } from \"../repo/service-target.js\";\nimport { FleetContext } from \"../fleet-context.js\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport { scenarioBodyHash } from \"../gherkin/digest.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\n// The kind vocabulary and both hash recipes live in ./claims/identity.ts —\n// one module for everything that decides whether two runs ask the same\n// question, and this one for how the questions are derived.\nimport {\n checklistDigest,\n claimId,\n DIGEST_LENGTH,\n ID_LENGTH,\n type ClaimKind,\n} from \"./claims/identity.js\";\n\nexport interface Claim {\n /** `<kind>-<8 hex>` — stable for the life of the claim. See `claimId`. */\n id: string;\n kind: ClaimKind;\n /** The service whose code answers this. A caller's claim is filed under the caller. */\n subject: string;\n /** The claim in one line, answerable without reading the feature's files. */\n claim: string;\n /**\n * `scenario.tested` only: the first {@link DIGEST_LENGTH} hex of the\n * scenario's body hash — the exact digest `loam gherkin` stamps as\n * `@loam-digest-…`, which is what a cucumber report scenario carries and\n * what `--results` matches on. Absent on every other kind.\n */\n digest?: string;\n /**\n * `api.exposes` only: the operationId the claim asserts, structurally — the\n * join key `--contract-results` matches a contract report's entries on,\n * exactly as `digest` is `--results`'s. Never parsed back out of the claim\n * text, which is prose and may be reworded. Absent on every other kind.\n */\n operation?: string;\n /**\n * The literal string the claim asserts of the cited artifact — the\n * operationId for `api.exposes`, the message name for `event.declares`, the\n * edge's op for `c4.calls` — carried structurally so evidence pins\n * (`./pins/pin.ts`) can stamp it at record time and the read side never\n * parses it back out of claim prose. A separate field from `operation` on\n * purpose: `contestedOperations` filters on `operation !== undefined` and\n * must not start seeing `c4.calls` claims. Absent where a claim asserts no\n * literal (`service.exists`, `scenario.tested`).\n */\n token?: string;\n}\n\nexport interface Checklist {\n feature: string;\n claims: Claim[];\n /** A digest of the claim id SET — what says a record still answers this feature. */\n digest: string;\n}\n\n/**\n * Derive the checklist from a feature's own artifacts. No code is read here and\n * none should be: the whole design rests on the questions being computed from\n * documents that do not change between runs.\n */\nexport async function featureChecklist(\n docsDir: DocsDir,\n featureDir: FeatureDir,\n featureId: string,\n fleet?: FleetContext,\n): Promise<Checklist> {\n // The per-service loop below resolves living contracts through the\n // enumeration twice per service; without a shared context each of those is\n // a full fleet walk, so deriving one checklist cost O(services²) reads. A\n // caller-supplied context is used when given; otherwise one is created for\n // this derivation alone — the loop still pays a single walk either way.\n const ctx = fleet ?? new FleetContext();\n const seen = new Map<string, number>();\n const claim = (kind: ClaimKind, subject: string, parts: string[], text: string): Claim => ({\n id: claimId(featureId, kind, parts, seen),\n kind,\n subject,\n claim: text,\n });\n\n const exists: Claim[] = [];\n const exposes: Claim[] = [];\n const declares: Claim[] = [];\n const calls: Claim[] = [];\n const scenarios: Claim[] = [];\n\n const specServices = await featureSpecServices(featureDir, ctx);\n\n // Architecture — what the delta promised the fleet would look like. Loaded\n // through the context rather than a bare parse, so a caller that derives\n // several checklists in one invocation (`loam gate`'s verification check,\n // the fleet forms) shares one parse per delta — and a batch prefetch\n // (`FleetContext.prefetchLikeC4`) genuinely seeds this load.\n const deltaPath = featurePaths(featureDir).delta;\n if (existsSync(deltaPath)) {\n const res = await ctx.loadLikeC4(deltaPath);\n // A delta nobody can parse promises nothing checkable. `validate` already\n // reports it; inventing claims out of a broken document would only add noise.\n if (res.errors.length === 0) {\n const elements: Elem[] = res.elements;\n for (const e of elements) {\n if (!e.tags.includes(featureId)) continue;\n // A dotted id is a container INSIDE a service, and an actor is nobody's\n // repository. Neither is a service that has to exist.\n if (e.id.includes(\".\") || ACTOR_KINDS.has(e.kind.toLowerCase())) continue;\n const svc = elementService(e);\n exists.push(claim(\"service.exists\", svc, [svc], `service '${svc}' exists`));\n }\n // The enumerated fleet — `services/` plus this feature's own `specs/`\n // directories, the only place a service the feature INTRODUCES has a\n // directory at all — rides into the resolver so a claim about an edge\n // drawn into a modelled container names the service that owns it. A\n // claim against a service called \"api\" that has never existed is one no\n // evidence in any repository could ever answer, and the fleet half alone\n // still derived exactly that claim for an introduced service's own\n // container. Unenumerable services/ degrades to the feature's names.\n const svcOf = serviceResolver(\n elements,\n new Set<string>([...(await enumeratedServiceIds(docsDir, ctx)), ...specServices]),\n );\n for (const r of res.relationships) {\n // Untagged edges are context for the diagram; an edge with no operation\n // names nothing specific enough to look for in the code.\n if (!r.tags.includes(featureId) || r.op === undefined) continue;\n const from = svcOf(r.source);\n const to = svcOf(r.target);\n calls.push({\n ...claim(\"c4.calls\", from, [from, to, r.op], `${from} calls '${r.op}' on ${to}`),\n // The same string as the provider's api.exposes token, scanned in the\n // CALLER's cited file — a call site plausibly spells the operationId.\n token: r.op,\n });\n }\n }\n }\n\n for (const svc of specServices) {\n const paths = featureSpecPaths(featureDir, svc);\n\n // Contract — only what is NEW. A delta's openapi.yaml is a whole document,\n // not a patch, so authors restate the living API inside it; asking whether a\n // service still exposes what it already exposed is noise.\n const featOps = (await operations(paths.openapi))\n .filter((operation) => !operation.remove)\n .map((operation) => operation.id);\n if (featOps.length > 0) {\n const living = new Set(await operationIds((await locateServicePaths(docsDir, svc, ctx)).openapi));\n for (const op of featOps) {\n if (living.has(op)) continue;\n exposes.push({\n ...claim(\"api.exposes\", svc, [svc, op], `${svc} exposes operationId '${op}'`),\n operation: op,\n token: op,\n });\n }\n }\n\n // Events — the same NEW-only discipline on the async axis: a delta's\n // asyncapi.yaml is a complete document too, so what the living contract\n // already sends or receives is restatement, not a promise. Removals are\n // not questions either — the reader already keeps marked declarations out\n // of the sent/received sets — and an unreadable delta promises nothing\n // checkable, exactly like the broken C4 delta above.\n const events = await readAsyncapi(paths.asyncapi);\n if (events.sent.length > 0 || events.received.length > 0) {\n const living = await readAsyncapi((await locateServicePaths(docsDir, svc, ctx)).asyncapi);\n const directions = [\n { direction: \"sends\", feat: events.sent, known: new Set(living.sent) },\n { direction: \"receives\", feat: events.received, known: new Set(living.received) },\n ] as const;\n for (const d of directions) {\n for (const name of d.feat) {\n if (d.known.has(name)) continue;\n declares.push({\n ...claim(\n \"event.declares\",\n svc,\n [svc, d.direction, name],\n `${svc} declares it ${d.direction} message '${name}'`,\n ),\n token: name,\n });\n }\n }\n }\n\n // Behaviour — the scenarios of the requirements this feature CHANGES. A BASE\n // requirement is the living state quoted inside the delta, and a REMOVED one\n // is being retired: neither is work anybody has to have done.\n if (existsSync(paths.spec)) {\n for (const r of parseRequirements(await readFile(paths.spec, \"utf8\"))) {\n if (r.kind === \"BASE\" || r.kind === \"REMOVED\") continue;\n for (const s of r.scenarios) {\n const body = scenarioBodyHash(svc, s.lines);\n scenarios.push({\n ...claim(\n \"scenario.tested\",\n svc,\n [svc, r.id ?? r.name, s.name, body.slice(0, ID_LENGTH)],\n `scenario '${s.name}' of requirement '${r.name}' (${svc}) is covered by a test`,\n ),\n digest: body.slice(0, DIGEST_LENGTH),\n });\n }\n }\n }\n\n // The architecture spec axis, same walk: an arch scenario is a promise like\n // any other — the outbox test nobody was going to write IS the point of the\n // axis. The source file rides in the id tuple AND in the body hash itself\n // (two axes are two namespaces: identically-worded scenarios must stay two\n // questions, and the axis-salted digest is what keeps a business-only test\n // run from answering the arch claim) and in the claim text, so the\n // answering agent knows it is being asked for an integration/ops test, not\n // an acceptance test.\n if (existsSync(paths.archSpec)) {\n for (const r of parseRequirements(await readFile(paths.archSpec, \"utf8\"))) {\n if (r.kind === \"BASE\" || r.kind === \"REMOVED\") continue;\n for (const s of r.scenarios) {\n const body = scenarioBodyHash(svc, s.lines, \"arch\");\n scenarios.push({\n ...claim(\n \"scenario.tested\",\n svc,\n [svc, \"arch.spec.md\", r.id ?? r.name, s.name, body.slice(0, ID_LENGTH)],\n `scenario '${s.name}' of arch requirement '${r.name}' (${svc}, arch.spec.md) is covered by a test`,\n ),\n digest: body.slice(0, DIGEST_LENGTH),\n });\n }\n }\n }\n }\n\n const claims = [...exists, ...exposes, ...declares, ...calls, ...scenarios];\n return { feature: featureId, claims, digest: checklistDigest(claims.map((c) => c.id)) };\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a claim is about, and what makes it the same claim next run: the kind
|
|
3
|
+
* vocabulary and the two hash recipes. Moved out of `../checklist.ts` when the
|
|
4
|
+
* `operation` join key pushed the derivation module over the line limit — the
|
|
5
|
+
* seam was already in that file's own header, which discusses what a claim's
|
|
6
|
+
* id is a function of apart from how the questions are derived. The kinds live
|
|
7
|
+
* beside the id recipe rather than beside the derivation because the id STARTS
|
|
8
|
+
* with the kind: the two are one identity, and a diff to this module is a diff
|
|
9
|
+
* to every record in the fleet, which is exactly why it holds nothing else.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
/**
|
|
13
|
+
* What a claim is about. The order is the order the checklist comes back in,
|
|
14
|
+
* and it reads as the story of the feature: the service exists, it exposes its
|
|
15
|
+
* operations, it declares its messages, the calls into it are wired, the
|
|
16
|
+
* behaviour is tested.
|
|
17
|
+
*/
|
|
18
|
+
export const CLAIM_KINDS = ["service.exists", "api.exposes", "event.declares", "c4.calls", "scenario.tested"];
|
|
19
|
+
/**
|
|
20
|
+
* How much of the sha256 goes into an id, and into a digest — the checklist's,
|
|
21
|
+
* and a scenario claim's runner-matching one, which is deliberately the same
|
|
22
|
+
* 16 hex `loam gherkin` stamps (its GHERKIN_DIGEST_LENGTH): the tag in a
|
|
23
|
+
* cucumber report and the digest on a claim must be the same string.
|
|
24
|
+
*/
|
|
25
|
+
export const ID_LENGTH = 8;
|
|
26
|
+
export const DIGEST_LENGTH = 16;
|
|
27
|
+
/**
|
|
28
|
+
* A claim's identity: a hash of what it says, and nothing about how it was
|
|
29
|
+
* produced.
|
|
30
|
+
*
|
|
31
|
+
* The feature id is part of it so an answers file for one feature can never
|
|
32
|
+
* validate against another. Two claims that really are identical (the same
|
|
33
|
+
* scenario name twice under one requirement) are distinguished by occurrence, in
|
|
34
|
+
* document order — they are still two questions, and answering one must not
|
|
35
|
+
* answer the other.
|
|
36
|
+
*
|
|
37
|
+
* The hash is short because the claim text sits next to it everywhere it is
|
|
38
|
+
* shown; it identifies a question, it does not authenticate one.
|
|
39
|
+
*/
|
|
40
|
+
export function claimId(featureId, kind, parts, seen) {
|
|
41
|
+
// NUL-joined so no claim's own text can spell another claim's tuple by
|
|
42
|
+
// containing the separator: ['a b','c'] and ['a','b c'] stay two questions.
|
|
43
|
+
const tuple = [featureId, kind, ...parts].join("\u0000");
|
|
44
|
+
const n = (seen.get(tuple) ?? 0) + 1;
|
|
45
|
+
seen.set(tuple, n);
|
|
46
|
+
const canonical = n === 1 ? tuple : `${tuple}\u0000#${n}`;
|
|
47
|
+
return `${kind}-${createHash("sha256").update(canonical).digest("hex").slice(0, ID_LENGTH)}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A digest of the claim id SET — sorted, so reordering the artifacts does not
|
|
51
|
+
* make a record look stale. It changes when a claim is added, removed or
|
|
52
|
+
* reworded, which is exactly when an answer set stops describing the feature.
|
|
53
|
+
* Takes the ids rather than the claims so this module never has to import the
|
|
54
|
+
* `Claim` shape the derivation declares — the recipe is over ids and nothing
|
|
55
|
+
* else, and the signature now says so.
|
|
56
|
+
*/
|
|
57
|
+
export function checklistDigest(ids) {
|
|
58
|
+
const sorted = [...ids].sort();
|
|
59
|
+
return createHash("sha256").update(sorted.join("\n")).digest("hex").slice(0, DIGEST_LENGTH);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../../src/core/verify/claims/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,iBAAiB,CAAU,CAAC;AAGvH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAC3B,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,OAAO,CACrB,SAAiB,EACjB,IAAe,EACf,KAAe,EACf,IAAyB;IAEzB,uEAAuE;IACvE,4EAA4E;IAC5E,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnB,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,UAAU,CAAC,EAAE,CAAC;IAC1D,OAAO,GAAG,IAAI,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC;AAC/F,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,GAAsB;IACpD,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;AAC9F,CAAC","sourcesContent":["/**\n * What a claim is about, and what makes it the same claim next run: the kind\n * vocabulary and the two hash recipes. Moved out of `../checklist.ts` when the\n * `operation` join key pushed the derivation module over the line limit — the\n * seam was already in that file's own header, which discusses what a claim's\n * id is a function of apart from how the questions are derived. The kinds live\n * beside the id recipe rather than beside the derivation because the id STARTS\n * with the kind: the two are one identity, and a diff to this module is a diff\n * to every record in the fleet, which is exactly why it holds nothing else.\n */\nimport { createHash } from \"node:crypto\";\n\n/**\n * What a claim is about. The order is the order the checklist comes back in,\n * and it reads as the story of the feature: the service exists, it exposes its\n * operations, it declares its messages, the calls into it are wired, the\n * behaviour is tested.\n */\nexport const CLAIM_KINDS = [\"service.exists\", \"api.exposes\", \"event.declares\", \"c4.calls\", \"scenario.tested\"] as const;\nexport type ClaimKind = (typeof CLAIM_KINDS)[number];\n\n/**\n * How much of the sha256 goes into an id, and into a digest — the checklist's,\n * and a scenario claim's runner-matching one, which is deliberately the same\n * 16 hex `loam gherkin` stamps (its GHERKIN_DIGEST_LENGTH): the tag in a\n * cucumber report and the digest on a claim must be the same string.\n */\nexport const ID_LENGTH = 8;\nexport const DIGEST_LENGTH = 16;\n\n/**\n * A claim's identity: a hash of what it says, and nothing about how it was\n * produced.\n *\n * The feature id is part of it so an answers file for one feature can never\n * validate against another. Two claims that really are identical (the same\n * scenario name twice under one requirement) are distinguished by occurrence, in\n * document order — they are still two questions, and answering one must not\n * answer the other.\n *\n * The hash is short because the claim text sits next to it everywhere it is\n * shown; it identifies a question, it does not authenticate one.\n */\nexport function claimId(\n featureId: string,\n kind: ClaimKind,\n parts: string[],\n seen: Map<string, number>,\n): string {\n // NUL-joined so no claim's own text can spell another claim's tuple by\n // containing the separator: ['a b','c'] and ['a','b c'] stay two questions.\n const tuple = [featureId, kind, ...parts].join(\"\\u0000\");\n const n = (seen.get(tuple) ?? 0) + 1;\n seen.set(tuple, n);\n const canonical = n === 1 ? tuple : `${tuple}\\u0000#${n}`;\n return `${kind}-${createHash(\"sha256\").update(canonical).digest(\"hex\").slice(0, ID_LENGTH)}`;\n}\n\n/**\n * A digest of the claim id SET — sorted, so reordering the artifacts does not\n * make a record look stale. It changes when a claim is added, removed or\n * reworded, which is exactly when an answer set stops describing the feature.\n * Takes the ids rather than the claims so this module never has to import the\n * `Claim` shape the derivation declares — the recipe is over ids and nothing\n * else, and the signature now says so.\n */\nexport function checklistDigest(ids: readonly string[]): string {\n const sorted = [...ids].sort();\n return createHash(\"sha256\").update(sorted.join(\"\\n\")).digest(\"hex\").slice(0, DIGEST_LENGTH);\n}\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FILE a piece of evidence cites: one trailing `:<line>` or
|
|
3
|
+
* `:<line>-<line>` suffix stripped, nothing else judged. Mechanical string
|
|
4
|
+
* surgery on purpose — evidence is prose the answerer wrote, and any deeper
|
|
5
|
+
* reading (resolving against a tree, normalizing separators) would turn the
|
|
6
|
+
* disjointness signal into an opinion about paths instead of a fact about
|
|
7
|
+
* citations.
|
|
8
|
+
*/
|
|
9
|
+
function citedFile(evidence) {
|
|
10
|
+
return evidence.replace(/:\d+(-\d+)?$/, "");
|
|
11
|
+
}
|
|
12
|
+
function side(answer) {
|
|
13
|
+
return {
|
|
14
|
+
verdict: answer.verdict,
|
|
15
|
+
evidence: answer.evidence,
|
|
16
|
+
...(answer.note === undefined ? {} : { note: answer.note }),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function crossRow(claim, a, b) {
|
|
20
|
+
const base = {
|
|
21
|
+
id: claim.id,
|
|
22
|
+
kind: claim.kind,
|
|
23
|
+
subject: claim.subject,
|
|
24
|
+
claim: claim.claim,
|
|
25
|
+
a: side(a),
|
|
26
|
+
b: side(b),
|
|
27
|
+
};
|
|
28
|
+
if (a.verdict !== b.verdict)
|
|
29
|
+
return { ...base, code: "cross.disagree" };
|
|
30
|
+
if (a.verdict === "confirmed") {
|
|
31
|
+
// Both confirmed, so both cite at least one entry (`checkAnswers` refuses
|
|
32
|
+
// an evidenced-nothing confirmation) — disjoint therefore means "looked in
|
|
33
|
+
// different places and agreed anyway", never "nobody cited anything".
|
|
34
|
+
const files = new Set(a.evidence.map(citedFile));
|
|
35
|
+
const disjoint = !b.evidence.some((entry) => files.has(citedFile(entry)));
|
|
36
|
+
return { ...base, code: "cross.agree-confirmed", ...(disjoint ? { evidenceDisjoint: true } : {}) };
|
|
37
|
+
}
|
|
38
|
+
return { ...base, code: "cross.agree-unconfirmed" };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Join two validated answer sets over the claims they both answered.
|
|
42
|
+
*
|
|
43
|
+
* Both sets must have come out of `checkAnswers` over THIS `claims` array,
|
|
44
|
+
* which refuses any set that does not answer every claim exactly once — so
|
|
45
|
+
* there is deliberately no missing-answer branch in the result shape. The
|
|
46
|
+
* guard below still fails closed on a caller that skipped validation: a
|
|
47
|
+
* silent skip would report "no disagreement" about a claim nobody compared,
|
|
48
|
+
* which is the one lie this module exists to prevent.
|
|
49
|
+
*/
|
|
50
|
+
export function crossExamine(claims, a, b) {
|
|
51
|
+
const byIdA = new Map(a.map((answer) => [answer.id, answer]));
|
|
52
|
+
const byIdB = new Map(b.map((answer) => [answer.id, answer]));
|
|
53
|
+
const rows = claims.map((claim) => {
|
|
54
|
+
const answerA = byIdA.get(claim.id);
|
|
55
|
+
const answerB = byIdB.get(claim.id);
|
|
56
|
+
if (answerA === undefined || answerB === undefined) {
|
|
57
|
+
throw new Error(`crossExamine: claim ${claim.id} has no answer in the ${answerA === undefined ? "first" : "second"} set — ` +
|
|
58
|
+
"validate both sets with checkAnswers over the same claims before joining them");
|
|
59
|
+
}
|
|
60
|
+
return crossRow(claim, answerA, answerB);
|
|
61
|
+
});
|
|
62
|
+
const of = (code) => rows.filter((row) => row.code === code).length;
|
|
63
|
+
const disagreements = rows.filter((row) => row.code === "cross.disagree");
|
|
64
|
+
return {
|
|
65
|
+
rows,
|
|
66
|
+
summary: {
|
|
67
|
+
claims: rows.length,
|
|
68
|
+
agreeConfirmed: of("cross.agree-confirmed"),
|
|
69
|
+
agreeUnconfirmed: of("cross.agree-unconfirmed"),
|
|
70
|
+
disagree: disagreements.length,
|
|
71
|
+
evidenceDisjoint: rows.filter((row) => row.code === "cross.agree-confirmed" && row.evidenceDisjoint === true)
|
|
72
|
+
.length,
|
|
73
|
+
},
|
|
74
|
+
disagreements,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The one notice this lens raises: agreements whose cited file sets share no
|
|
79
|
+
* path. Second in the reading order after the disagreements, because it is
|
|
80
|
+
* the shape correlated agreement takes when both agents guessed — each found
|
|
81
|
+
* something plausible somewhere, and nothing says they found the same thing.
|
|
82
|
+
*/
|
|
83
|
+
export function disjointNotice(rows) {
|
|
84
|
+
const ids = rows
|
|
85
|
+
.filter((row) => row.code === "cross.agree-confirmed" && row.evidenceDisjoint === true)
|
|
86
|
+
.map((row) => row.id);
|
|
87
|
+
if (ids.length === 0)
|
|
88
|
+
return null;
|
|
89
|
+
return {
|
|
90
|
+
code: "cross.evidence-disjoint",
|
|
91
|
+
severity: "warn",
|
|
92
|
+
message: `${ids.length} agreed-confirmed claim(s) cite non-overlapping files: ${ids.join(", ")}. ` +
|
|
93
|
+
"Two agents agreeing from different evidence is the second thing to read, after the disagreements.",
|
|
94
|
+
claims: ids,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../../src/core/verify/cross/diff.ts"],"names":[],"mappings":"AAyEA;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,QAAgB;IACjC,OAAO,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,IAAI,CAAC,MAAc;IAC1B,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;KAC5D,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAY,EAAE,CAAS,EAAE,CAAS;IAClD,MAAM,IAAI,GAAc;QACtB,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACV,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;KACX,CAAC;IACF,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;QAAE,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACxE,IAAI,CAAC,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;QAC9B,0EAA0E;QAC1E,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QACjD,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1E,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,uBAAuB,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAC9G,CAAC;IACD,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC;AACtD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,MAAe,EAAE,CAAW,EAAE,CAAW;IACpE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAChC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CACb,uBAAuB,KAAK,CAAC,EAAE,yBAAyB,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,SAAS;gBACzG,+EAA+E,CAClF,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,CAAC,IAAe,EAAU,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IACvF,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC;IAC1E,OAAO;QACL,IAAI;QACJ,OAAO,EAAE;YACP,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,cAAc,EAAE,EAAE,CAAC,uBAAuB,CAAC;YAC3C,gBAAgB,EAAE,EAAE,CAAC,yBAAyB,CAAC;YAC/C,QAAQ,EAAE,aAAa,CAAC,MAAM;YAC9B,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,uBAAuB,IAAI,GAAG,CAAC,gBAAgB,KAAK,IAAI,CAAC;iBAC1G,MAAM;SACV;QACD,aAAa;KACd,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAA2B;IACxD,MAAM,GAAG,GAAG,IAAI;SACb,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,uBAAuB,IAAI,GAAG,CAAC,gBAAgB,KAAK,IAAI,CAAC;SACtF,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACxB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO;QACL,IAAI,EAAE,yBAAyB;QAC/B,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,GAAG,GAAG,CAAC,MAAM,0DAA0D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACzF,mGAAmG;QACrG,MAAM,EAAE,GAAG;KACZ,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Mechanical cross-examination of two blind answer sets over one checklist.\n *\n * The doctrine lives here because every surface reads it from this module:\n * agreement is a review-ranking signal, never a verdict. The two sets almost\n * always come from two contexts of the same model, so their agreeing is\n * correlated evidence — which is why this module deliberately imports nothing\n * from `../record.ts`'s verdict machinery (`verificationVerdict`,\n * `tallyRecord`): a cross-examination has no `verified`, no `attested`, and\n * can never upgrade either. What it ranks is where a human reads first — the\n * disagreements, then the agreements whose cited files do not overlap.\n *\n * The join is by claim id ONLY. The ids are deterministic functions of the\n * claims (`../claims/identity.ts`), which is the whole mechanism: two agents\n * who never saw each other's work still answered the same question under the\n * same key, so their answers are mechanically joinable — where fuzzy-matching\n * claim TEXT would make \"the same question\" an opinion about wording.\n */\nimport type { Answer, Verdict } from \"../answers.js\";\nimport type { Claim } from \"../checklist.js\";\nimport type { ClaimKind } from \"../claims/identity.js\";\nimport type { VerifyNotice } from \"../record.js\";\n\n/**\n * One side's answer, as the cross-examination reports it. Deliberately\n * narrower than {@link Answer}: `answered_by` is always `agent` under a diff\n * (both sets validate through `checkAnswers`' agent path, no report in\n * sight), and `evidence_pins` never rides through — a pin is loam's own\n * stamp, absent from `checkAnswers`' output by construction, and a diff of\n * caller-written files must not launder one into a payload.\n */\nexport interface CrossAnswer {\n verdict: Verdict;\n evidence: string[];\n note?: string;\n}\n\ninterface CrossBase {\n id: string;\n kind: ClaimKind;\n subject: string;\n claim: string;\n a: CrossAnswer;\n b: CrossAnswer;\n}\n\n/**\n * One claim, both answers, and what their comparison is. A union rather than\n * an optional-field record so `evidenceDisjoint` is not constructible on the\n * arms it means nothing for: two agents disagreeing cite different things by\n * definition, and two unconfirmed answers cite nothing.\n */\nexport type CrossClaim =\n | (CrossBase & { code: \"cross.agree-confirmed\"; evidenceDisjoint?: true })\n | (CrossBase & { code: \"cross.agree-unconfirmed\" })\n | (CrossBase & { code: \"cross.disagree\" });\n\nexport type CrossCode = CrossClaim[\"code\"];\n\nexport interface CrossExamination {\n /** Checklist order — the order the questions were asked, whatever either agent did. */\n rows: CrossClaim[];\n summary: {\n claims: number;\n agreeConfirmed: number;\n agreeUnconfirmed: number;\n disagree: number;\n evidenceDisjoint: number;\n };\n /** The `cross.disagree` rows again, first-class: they are what a reader is for. */\n disagreements: CrossClaim[];\n}\n\n/**\n * The FILE a piece of evidence cites: one trailing `:<line>` or\n * `:<line>-<line>` suffix stripped, nothing else judged. Mechanical string\n * surgery on purpose — evidence is prose the answerer wrote, and any deeper\n * reading (resolving against a tree, normalizing separators) would turn the\n * disjointness signal into an opinion about paths instead of a fact about\n * citations.\n */\nfunction citedFile(evidence: string): string {\n return evidence.replace(/:\\d+(-\\d+)?$/, \"\");\n}\n\nfunction side(answer: Answer): CrossAnswer {\n return {\n verdict: answer.verdict,\n evidence: answer.evidence,\n ...(answer.note === undefined ? {} : { note: answer.note }),\n };\n}\n\nfunction crossRow(claim: Claim, a: Answer, b: Answer): CrossClaim {\n const base: CrossBase = {\n id: claim.id,\n kind: claim.kind,\n subject: claim.subject,\n claim: claim.claim,\n a: side(a),\n b: side(b),\n };\n if (a.verdict !== b.verdict) return { ...base, code: \"cross.disagree\" };\n if (a.verdict === \"confirmed\") {\n // Both confirmed, so both cite at least one entry (`checkAnswers` refuses\n // an evidenced-nothing confirmation) — disjoint therefore means \"looked in\n // different places and agreed anyway\", never \"nobody cited anything\".\n const files = new Set(a.evidence.map(citedFile));\n const disjoint = !b.evidence.some((entry) => files.has(citedFile(entry)));\n return { ...base, code: \"cross.agree-confirmed\", ...(disjoint ? { evidenceDisjoint: true as const } : {}) };\n }\n return { ...base, code: \"cross.agree-unconfirmed\" };\n}\n\n/**\n * Join two validated answer sets over the claims they both answered.\n *\n * Both sets must have come out of `checkAnswers` over THIS `claims` array,\n * which refuses any set that does not answer every claim exactly once — so\n * there is deliberately no missing-answer branch in the result shape. The\n * guard below still fails closed on a caller that skipped validation: a\n * silent skip would report \"no disagreement\" about a claim nobody compared,\n * which is the one lie this module exists to prevent.\n */\nexport function crossExamine(claims: Claim[], a: Answer[], b: Answer[]): CrossExamination {\n const byIdA = new Map(a.map((answer) => [answer.id, answer]));\n const byIdB = new Map(b.map((answer) => [answer.id, answer]));\n const rows = claims.map((claim) => {\n const answerA = byIdA.get(claim.id);\n const answerB = byIdB.get(claim.id);\n if (answerA === undefined || answerB === undefined) {\n throw new Error(\n `crossExamine: claim ${claim.id} has no answer in the ${answerA === undefined ? \"first\" : \"second\"} set — ` +\n \"validate both sets with checkAnswers over the same claims before joining them\",\n );\n }\n return crossRow(claim, answerA, answerB);\n });\n const of = (code: CrossCode): number => rows.filter((row) => row.code === code).length;\n const disagreements = rows.filter((row) => row.code === \"cross.disagree\");\n return {\n rows,\n summary: {\n claims: rows.length,\n agreeConfirmed: of(\"cross.agree-confirmed\"),\n agreeUnconfirmed: of(\"cross.agree-unconfirmed\"),\n disagree: disagreements.length,\n evidenceDisjoint: rows.filter((row) => row.code === \"cross.agree-confirmed\" && row.evidenceDisjoint === true)\n .length,\n },\n disagreements,\n };\n}\n\n/**\n * The one notice this lens raises: agreements whose cited file sets share no\n * path. Second in the reading order after the disagreements, because it is\n * the shape correlated agreement takes when both agents guessed — each found\n * something plausible somewhere, and nothing says they found the same thing.\n */\nexport function disjointNotice(rows: readonly CrossClaim[]): VerifyNotice | null {\n const ids = rows\n .filter((row) => row.code === \"cross.agree-confirmed\" && row.evidenceDisjoint === true)\n .map((row) => row.id);\n if (ids.length === 0) return null;\n return {\n code: \"cross.evidence-disjoint\",\n severity: \"warn\",\n message:\n `${ids.length} agreed-confirmed claim(s) cite non-overlapping files: ${ids.join(\", \")}. ` +\n \"Two agents agreeing from different evidence is the second thing to read, after the disagreements.\",\n claims: ids,\n };\n}\n"]}
|