@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2548 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What gets written down, and how a set of answers counts up to a verdict.
|
|
3
|
+
*
|
|
4
|
+
* The shapes and the arithmetic are one module because they are one claim about
|
|
5
|
+
* the same numbers: `tallyRecord` recounts from the answers rather than reading
|
|
6
|
+
* the `summary` field back, so a record whose header disagrees with its own
|
|
7
|
+
* claims is caught rather than believed. A separate counter would be a second
|
|
8
|
+
* opinion, which is precisely what this file exists to prevent.
|
|
9
|
+
*
|
|
10
|
+
* `verified` is reserved for a checklist whose scenarios a RUN answered.
|
|
11
|
+
* Where an agent answered one instead — a legacy service whose suite is months
|
|
12
|
+
* away, which is the fleet loam exists for — the record marks it attested, and
|
|
13
|
+
* `attestedNotice` says so on every surface that reads the record.
|
|
14
|
+
*/
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import {} from "./claims/identity.js";
|
|
17
|
+
import {} from "./answers.js";
|
|
18
|
+
import {} from "./pins/pin.js";
|
|
19
|
+
/**
|
|
20
|
+
* The confirmed `scenario.tested` claims that no test run answered.
|
|
21
|
+
*
|
|
22
|
+
* A scenario claim's whole premise is that a run answers it — its digest IS the
|
|
23
|
+
* tag `loam gherkin` stamps, and `--results` matches the two mechanically. An
|
|
24
|
+
* agent may still answer one, because a legacy service has no runnable suite
|
|
25
|
+
* for months and that fleet is who loam is for; but the answer is somebody's
|
|
26
|
+
* word about a test, and it must not read as the run. A record written before
|
|
27
|
+
* `--results` existed carries no `answered_by` at all: unknown counts as agent
|
|
28
|
+
* here, because the alternative is to credit a run nobody can point at.
|
|
29
|
+
*/
|
|
30
|
+
export function attestedClaims(claims) {
|
|
31
|
+
return claims
|
|
32
|
+
.filter((c) => c.kind === "scenario.tested" && c.verdict === "confirmed" && c.answered_by !== "runner")
|
|
33
|
+
.map((c) => c.id);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Of the CONFIRMED answers, one count per `answered_by` value. Anything that is not a runner spelling
|
|
37
|
+
* counts as the agent's word — a record too old to carry the field says nothing, and crediting a run
|
|
38
|
+
* nobody can point at is the error that matters. Never derive it by subtracting {@link attestedClaims}
|
|
39
|
+
* from the confirmed total: that count is `scenario.tested` ALONE, so the fleet scorecard filed every
|
|
40
|
+
* other kind an agent confirmed under a runner that never ran.
|
|
41
|
+
*/
|
|
42
|
+
export function confirmedProvenance(claims) {
|
|
43
|
+
const done = claims.filter((c) => c.verdict === "confirmed");
|
|
44
|
+
const by = (who) => done.filter((c) => c.answered_by === who).length;
|
|
45
|
+
const ran = { runner: by("runner"), "external-runner": by("external-runner") };
|
|
46
|
+
return { ...ran, agent: done.length - ran.runner - ran["external-runner"] };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* `unanswered` is the count the answers themselves cannot show: a federated
|
|
50
|
+
* record holds only what somebody answered, and the claims still owed to other
|
|
51
|
+
* repositories exist on the checklist, not in `claims[]`. Read views that carry
|
|
52
|
+
* an `unanswered` status inside the list pass 0 and are counted in place.
|
|
53
|
+
*/
|
|
54
|
+
export function tallyAnswers(claims, unanswered = 0) {
|
|
55
|
+
const of = (verdict) => claims.filter((c) => c.verdict === verdict).length;
|
|
56
|
+
return {
|
|
57
|
+
claims: claims.length + unanswered,
|
|
58
|
+
confirmed: of("confirmed"),
|
|
59
|
+
unconfirmed: of("unconfirmed"),
|
|
60
|
+
unanswered: of("unanswered") + unanswered,
|
|
61
|
+
attested: attestedClaims(claims).length,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The record's own answers, recounted. Never `v.summary`: the summary is what
|
|
66
|
+
* whoever wrote the file said the answers add up to, and every reader that
|
|
67
|
+
* believed it reported a record full of unconfirmed claims as fully confirmed.
|
|
68
|
+
* `readVerificationState` refuses a record whose summary contradicts this, so a
|
|
69
|
+
* caller holding a Verification may use either — and should use this one.
|
|
70
|
+
*/
|
|
71
|
+
export function tallyRecord(v) {
|
|
72
|
+
return tallyAnswers(v.claims, v.summary.unanswered ?? 0);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Three states, because two were a lie. `verified` is the strong claim — every
|
|
76
|
+
* question answered, every answer confirmed, and every scenario answered by a
|
|
77
|
+
* digest-matched green run. `attested` is the same completeness resting, for at
|
|
78
|
+
* least one scenario, on an agent's word. `unverified` is everything else:
|
|
79
|
+
* nothing recorded, a record gone stale, a claim unanswered or unconfirmed, or
|
|
80
|
+
* a checklist that asks nothing at all.
|
|
81
|
+
*/
|
|
82
|
+
export const VERIFICATION_VERDICTS = ["verified", "attested", "unverified"];
|
|
83
|
+
export function verificationVerdict(t, stale = false) {
|
|
84
|
+
if (stale || t.claims === 0 || t.confirmed !== t.claims)
|
|
85
|
+
return "unverified";
|
|
86
|
+
return t.attested > 0 ? "attested" : "verified";
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The one notice `verify`, `list` and `status` all show: this record's scenario
|
|
90
|
+
* claims were confirmed without a run.
|
|
91
|
+
*
|
|
92
|
+
* It is a warning and it gates nothing — verify has never gated, and a service
|
|
93
|
+
* with no suite yet has to be able to ship. What it must never do is read the
|
|
94
|
+
* same as a green run.
|
|
95
|
+
*/
|
|
96
|
+
export function attestedNotice(claims, feature) {
|
|
97
|
+
const ids = attestedClaims(claims);
|
|
98
|
+
if (ids.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
return {
|
|
101
|
+
code: "verify.scenario-attested",
|
|
102
|
+
severity: "warn",
|
|
103
|
+
message: `${ids.length} scenario claim(s) are confirmed on an agent's word, not on a test run: ${ids.join(", ")}. ` +
|
|
104
|
+
`Answer them mechanically once the suite runs: \`loam verify ${feature} --results <cucumber.json>\`.`,
|
|
105
|
+
claims: ids,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The one-line honesty summary of a record that leaves claims open: partial is
|
|
110
|
+
* not a clean result, and the counts alone did not say so — `summary` carries
|
|
111
|
+
* them on every surface, and a reader skimming for the ⚠ lines shipped a
|
|
112
|
+
* half-answered record as done anyway. Null when nothing is open, and the
|
|
113
|
+
* callers fire it only when a record EXISTS: a feature with no record already
|
|
114
|
+
* says "Not verified", and calling that "not a clean result" would conflate
|
|
115
|
+
* not-started with partial. A pure-attested record (all confirmed, some on an
|
|
116
|
+
* agent's word) never reaches the message either — its counts are closed, and
|
|
117
|
+
* `attestedNotice` above owns that honesty; two notices about one fact is the
|
|
118
|
+
* double-reporting this file's notices exist to prevent.
|
|
119
|
+
*/
|
|
120
|
+
export function openClaimsNotice(tally, feature) {
|
|
121
|
+
const open = tally.unconfirmed + tally.unanswered;
|
|
122
|
+
if (open === 0)
|
|
123
|
+
return null;
|
|
124
|
+
return {
|
|
125
|
+
code: "verify.claims-open",
|
|
126
|
+
severity: "warn",
|
|
127
|
+
message: `not a clean result: of ${tally.claims} claim(s), ${tally.confirmed} confirmed` +
|
|
128
|
+
(tally.attested > 0 ? ` (${tally.attested} on an agent's word)` : "") +
|
|
129
|
+
`, ${tally.unconfirmed} unconfirmed, ${tally.unanswered} unanswered — ` +
|
|
130
|
+
`treat ${feature} as unverified until every claim is answered and confirmed.`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Where the record lives: inside the feature, so `archive` carries it into
|
|
135
|
+
* `features/archive/` with everything else and a reviewer finds it next to the
|
|
136
|
+
* delta it is about. YAML rather than frontmatter or JSON — it is data, it has
|
|
137
|
+
* to survive without loam, and one claim per block is a diff a person can read.
|
|
138
|
+
*/
|
|
139
|
+
export function verificationPath(featureDir) {
|
|
140
|
+
return join(featureDir, "verification.yaml");
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../../src/core/verify/record.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What gets written down, and how a set of answers counts up to a verdict.\n *\n * The shapes and the arithmetic are one module because they are one claim about\n * the same numbers: `tallyRecord` recounts from the answers rather than reading\n * the `summary` field back, so a record whose header disagrees with its own\n * claims is caught rather than believed. A separate counter would be a second\n * opinion, which is precisely what this file exists to prevent.\n *\n * `verified` is reserved for a checklist whose scenarios a RUN answered.\n * Where an agent answered one instead — a legacy service whose suite is months\n * away, which is the fleet loam exists for — the record marks it attested, and\n * `attestedNotice` says so on every surface that reads the record.\n */\nimport { join } from \"node:path\";\nimport { type ClaimKind } from \"./claims/identity.js\";\nimport { type AnsweredBy, type Verdict } from \"./answers.js\";\nimport { type EvidencePin } from \"./pins/pin.js\";\n\nexport interface RecordedClaim {\n id: string;\n kind: ClaimKind;\n /** Present in federated records; omitted by legacy records written before schema 2. */\n subject?: string;\n claim: string;\n verdict: Verdict;\n /** Who answered — absent only in records written before `--results` existed. */\n answered_by?: AnsweredBy;\n evidence: string[];\n note?: string;\n /**\n * One pin per file:line citation of a federated agent-confirmed answer: the\n * cited file's normalized sha256 at the attested commit, the cited line's\n * text, and the claim's token where it asserts one. A pin records which\n * text was cited at that commit so `loam validate`, run later in the\n * service's own repo, can convict drift; it does not prove the claim true —\n * a faithful pin of the wrong line is still a wrong answer. Absent on\n * runner-answered claims (their evidence names a report entry, not a file)\n * and on every record written before pins existed.\n */\n evidence_pins?: EvidencePin[];\n}\n\n/**\n * The test report a `--results` run consumed.\n *\n * Nothing can prove a JSON file came from executing a particular commit — a\n * report is bytes, and bytes are writable. What loam can do is stop leaving the\n * question open: it records exactly which file it read (inside the attesting\n * repository, resolved by the same path rules as evidence), the sha256 of the\n * bytes it read, the file's own mtime and how many digest-tagged scenarios it\n * carried. A reviewer holding the repository can tell whether this is that\n * file; before this, `answered_by: runner` named nothing at all.\n */\nexport interface ConsumedReport {\n /** As it was passed to `--results` — repo-relative in a federated record. */\n path: string;\n /** Full sha256 of the bytes read, so `shasum -a 256 <path>` answers the question. */\n digest: string;\n /** ISO-8601 mtime of the report file — when the run that wrote it finished. */\n mtime: string;\n /** How many digest-tagged scenarios the report carried. */\n scenarios: number;\n}\n\n/**\n * The contract-test report a `--contract-results` run consumed — the same pin,\n * the same honesty, for the report that answers `api.exposes` claims by\n * operationId. Nothing here proves the file came from executing the attested\n * commit either; the digest identifies WHICH file was consumed, and the\n * federated form binds a committed one to the commit exactly as `report:` is.\n */\nexport interface ConsumedContractReport {\n /** As it was passed to `--contract-results` — repo-relative in a federated record. */\n path: string;\n /** Full sha256 of the bytes read. */\n digest: string;\n /** ISO-8601 mtime of the report file. */\n mtime: string;\n /** How many distinct operationIds the report's entries name. */\n operations: number;\n /**\n * Which accepted shape the report was read as — `\"generic\"` for the\n * documented `loamContractReport: 1` shape, the only one v1 accepts. loam\n * always writes it, but the READ shape keeps it optional: no reader\n * dereferences it, so a hand-written pin without it stays a readable record\n * (`isConsumedContractReport` in ./file.ts says why) — and it is a string\n * rather than a closed union so a format a NEWER loam learned to read stays\n * readable here too.\n */\n format?: string;\n}\n\n/**\n * Both mechanical answer sheets one recording run may consume, together\n * because they are validated together (`validateServiceEvidence` binds each to\n * the attested commit) and filed together (on the record all-at-once, inside\n * the service's attestation federated). Two loose optional parameters made the\n * inconsistent pair representable at every call site.\n */\nexport interface ConsumedReports {\n results?: ConsumedReport;\n contract?: ConsumedContractReport;\n}\n\n/** A service repository's commit-bound contribution to a federated record. */\nexport interface ServiceAttestation {\n service: string;\n commit: string;\n recorded: string;\n /** Claim ids this commit answered. Kept explicit so stale answers can be pruned safely. */\n claims: string[];\n /**\n * The checklist digest THIS attestation answered.\n *\n * The record carries one `checklist` for the whole file, which is the right\n * field for the all-at-once form and an incomplete one for a federated\n * record: two services attesting a week apart across a changed delta write\n * the same top-level digest, and nothing in the file can say they answered\n * different questions. With this, `verify.checklist-forked` can.\n *\n * Optional forever. An attestation written before this field existed carries\n * no claim about which checklist it answered, and inventing one from the\n * record-level digest would be asserting exactly the thing that cannot be\n * known — the `evidence.unpinned` precedent, one axis over.\n */\n checklist?: string;\n /**\n * The docs-repo commit the checklist was computed from, when the docs repo is\n * a git repository with a committed HEAD.\n *\n * `commit` pins the SERVICE repo — the code the evidence points into.\n * Nothing pinned the side the QUESTION came from, so a reader could not tell\n * a re-record against an unchanged feature from one against a rewritten\n * delta. Absent whenever git cannot answer: a docs repo is not required to be\n * a git checkout, and a missing pin is not a defect.\n */\n docsCommit?: string;\n /** The report that answered this service's scenario claims, when `--results` did. */\n report?: ConsumedReport;\n /** The report that answered this service's api.exposes claims, when `--contract-results` did. */\n contractReport?: ConsumedContractReport;\n}\n\nexport interface Verification {\n /** Federated, partial records use schema 2. Absent means the original all-at-once format. */\n schema?: 2;\n feature: string;\n /** The day the answers were recorded. */\n recorded: string;\n /** The checklist digest they answer — how a later reader spots a record gone stale. */\n checklist: string;\n summary: { claims: number; confirmed: number; unconfirmed: number; unanswered?: number };\n claims: RecordedClaim[];\n /** The all-at-once form's consumed report; the federated form files it per attestation. */\n report?: ConsumedReport;\n /** The all-at-once form's consumed contract report — filed per attestation when federated. */\n contractReport?: ConsumedContractReport;\n attestations?: ServiceAttestation[];\n}\n\n/**\n * Anything carrying an answer, whichever surface holds it: a recorded claim, or\n * the read view's per-claim status. Structural on purpose — the same questions\n * are asked of both, and neither should have to convert into the other to be\n * counted.\n */\nexport interface AnsweredClaim {\n id: string;\n kind: string;\n verdict: string;\n answered_by?: string;\n}\n\n/**\n * The confirmed `scenario.tested` claims that no test run answered.\n *\n * A scenario claim's whole premise is that a run answers it — its digest IS the\n * tag `loam gherkin` stamps, and `--results` matches the two mechanically. An\n * agent may still answer one, because a legacy service has no runnable suite\n * for months and that fleet is who loam is for; but the answer is somebody's\n * word about a test, and it must not read as the run. A record written before\n * `--results` existed carries no `answered_by` at all: unknown counts as agent\n * here, because the alternative is to credit a run nobody can point at.\n */\nexport function attestedClaims(claims: readonly AnsweredClaim[]): string[] {\n return claims\n .filter((c) => c.kind === \"scenario.tested\" && c.verdict === \"confirmed\" && c.answered_by !== \"runner\")\n .map((c) => c.id);\n}\n\n/**\n * Of the CONFIRMED answers, one count per `answered_by` value. Anything that is not a runner spelling\n * counts as the agent's word — a record too old to carry the field says nothing, and crediting a run\n * nobody can point at is the error that matters. Never derive it by subtracting {@link attestedClaims}\n * from the confirmed total: that count is `scenario.tested` ALONE, so the fleet scorecard filed every\n * other kind an agent confirmed under a runner that never ran.\n */\nexport function confirmedProvenance(claims: readonly AnsweredClaim[]): Record<AnsweredBy, number> {\n const done = claims.filter((c) => c.verdict === \"confirmed\");\n const by = (who: AnsweredBy): number => done.filter((c) => c.answered_by === who).length;\n const ran = { runner: by(\"runner\"), \"external-runner\": by(\"external-runner\") };\n return { ...ran, agent: done.length - ran.runner - ran[\"external-runner\"] };\n}\n\n/** How a set of answers counts up — recounted from the answers, never read off a `summary`. */\nexport interface RecordTally {\n /** Questions asked: answered, plus the ones a federated record leaves for other repos. */\n claims: number;\n confirmed: number;\n unconfirmed: number;\n unanswered: number;\n /** Of the confirmed, the scenario claims on an agent's word. See {@link attestedClaims}. */\n attested: number;\n}\n\n/**\n * `unanswered` is the count the answers themselves cannot show: a federated\n * record holds only what somebody answered, and the claims still owed to other\n * repositories exist on the checklist, not in `claims[]`. Read views that carry\n * an `unanswered` status inside the list pass 0 and are counted in place.\n */\nexport function tallyAnswers(claims: readonly AnsweredClaim[], unanswered = 0): RecordTally {\n const of = (verdict: string): number => claims.filter((c) => c.verdict === verdict).length;\n return {\n claims: claims.length + unanswered,\n confirmed: of(\"confirmed\"),\n unconfirmed: of(\"unconfirmed\"),\n unanswered: of(\"unanswered\") + unanswered,\n attested: attestedClaims(claims).length,\n };\n}\n\n/**\n * The record's own answers, recounted. Never `v.summary`: the summary is what\n * whoever wrote the file said the answers add up to, and every reader that\n * believed it reported a record full of unconfirmed claims as fully confirmed.\n * `readVerificationState` refuses a record whose summary contradicts this, so a\n * caller holding a Verification may use either — and should use this one.\n */\nexport function tallyRecord(v: Verification): RecordTally {\n return tallyAnswers(v.claims, v.summary.unanswered ?? 0);\n}\n\n/**\n * Three states, because two were a lie. `verified` is the strong claim — every\n * question answered, every answer confirmed, and every scenario answered by a\n * digest-matched green run. `attested` is the same completeness resting, for at\n * least one scenario, on an agent's word. `unverified` is everything else:\n * nothing recorded, a record gone stale, a claim unanswered or unconfirmed, or\n * a checklist that asks nothing at all.\n */\nexport const VERIFICATION_VERDICTS = [\"verified\", \"attested\", \"unverified\"] as const;\nexport type VerificationVerdict = (typeof VERIFICATION_VERDICTS)[number];\n\nexport function verificationVerdict(t: RecordTally, stale = false): VerificationVerdict {\n if (stale || t.claims === 0 || t.confirmed !== t.claims) return \"unverified\";\n return t.attested > 0 ? \"attested\" : \"verified\";\n}\n\n/** A finding a verify surface reports about a record. Stable code, prose that may change. */\nexport interface VerifyNotice {\n code: string;\n severity: \"warn\";\n message: string;\n /** The claims it is about, when it is about claims. */\n claims?: string[];\n}\n\n/**\n * The one notice `verify`, `list` and `status` all show: this record's scenario\n * claims were confirmed without a run.\n *\n * It is a warning and it gates nothing — verify has never gated, and a service\n * with no suite yet has to be able to ship. What it must never do is read the\n * same as a green run.\n */\nexport function attestedNotice(claims: readonly AnsweredClaim[], feature: string): VerifyNotice | null {\n const ids = attestedClaims(claims);\n if (ids.length === 0) return null;\n return {\n code: \"verify.scenario-attested\",\n severity: \"warn\",\n message:\n `${ids.length} scenario claim(s) are confirmed on an agent's word, not on a test run: ${ids.join(\", \")}. ` +\n `Answer them mechanically once the suite runs: \\`loam verify ${feature} --results <cucumber.json>\\`.`,\n claims: ids,\n };\n}\n\n/**\n * The one-line honesty summary of a record that leaves claims open: partial is\n * not a clean result, and the counts alone did not say so — `summary` carries\n * them on every surface, and a reader skimming for the ⚠ lines shipped a\n * half-answered record as done anyway. Null when nothing is open, and the\n * callers fire it only when a record EXISTS: a feature with no record already\n * says \"Not verified\", and calling that \"not a clean result\" would conflate\n * not-started with partial. A pure-attested record (all confirmed, some on an\n * agent's word) never reaches the message either — its counts are closed, and\n * `attestedNotice` above owns that honesty; two notices about one fact is the\n * double-reporting this file's notices exist to prevent.\n */\nexport function openClaimsNotice(tally: RecordTally, feature: string): VerifyNotice | null {\n const open = tally.unconfirmed + tally.unanswered;\n if (open === 0) return null;\n return {\n code: \"verify.claims-open\",\n severity: \"warn\",\n message:\n `not a clean result: of ${tally.claims} claim(s), ${tally.confirmed} confirmed` +\n (tally.attested > 0 ? ` (${tally.attested} on an agent's word)` : \"\") +\n `, ${tally.unconfirmed} unconfirmed, ${tally.unanswered} unanswered — ` +\n `treat ${feature} as unverified until every claim is answered and confirmed.`,\n };\n}\n\n/**\n * Where the record lives: inside the feature, so `archive` carries it into\n * `features/archive/` with everything else and a reviewer finds it next to the\n * delta it is about. YAML rather than frontmatter or JSON — it is data, it has\n * to survive without loam, and one claim per block is a diff a person can read.\n */\nexport function verificationPath(featureDir: string): string {\n return join(featureDir, \"verification.yaml\");\n}\n\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verification record's commit: staged beside the target, compared against
|
|
3
|
+
* the byte pre-image the merge consumed, swapped in by one rename(2).
|
|
4
|
+
*
|
|
5
|
+
* The record used to go down as a plain `writeFile`, which cost three ways at
|
|
6
|
+
* once: two `--record` runs read the same pre-image and the last writer
|
|
7
|
+
* silently discarded the other service's attestation; a process killed
|
|
8
|
+
* mid-write left the ONLY copy truncated, unreadable, and blocking every later
|
|
9
|
+
* record; and an edit that landed between the read and the write was buried
|
|
10
|
+
* under bytes computed from a document that no longer existed. The docs lock
|
|
11
|
+
* (taken by `loam verify` before its authoritative read) closes the first;
|
|
12
|
+
* this module closes the other two, and backstops the lock itself for a writer
|
|
13
|
+
* that never took it.
|
|
14
|
+
*
|
|
15
|
+
* A sub-package of core/verify rather than a sixth file in it, and a consumer
|
|
16
|
+
* of core/staging's primitives rather than a re-implementation: `stageWrites`
|
|
17
|
+
* and `swapStaged` already do temp-beside-target, whole-content compare-and-set
|
|
18
|
+
* and same-directory rename — vouch commits through exactly this shape.
|
|
19
|
+
*/
|
|
20
|
+
import { message, rollbackMessage, rollbackStaged, stageWrites, StagingRaceError, swapStaged } from "../../staging/commit.js";
|
|
21
|
+
import { repoPath } from "../../envelope/json.js";
|
|
22
|
+
import {} from "../../staging/writes.js";
|
|
23
|
+
import { renderVerification } from "./render.js";
|
|
24
|
+
import { verificationPath } from "../record.js";
|
|
25
|
+
/**
|
|
26
|
+
* `preImage` is the exact bytes the caller's merge was computed from, or null
|
|
27
|
+
* when the record was absent at the authoritative read — in which case the
|
|
28
|
+
* commit may only CREATE (link(2) refuses a file that appeared since), never
|
|
29
|
+
* replace one it has not read.
|
|
30
|
+
*/
|
|
31
|
+
export async function commitVerification(featureDir, v, preImage, docsDir) {
|
|
32
|
+
const path = verificationPath(featureDir);
|
|
33
|
+
const display = repoPath(docsDir, path);
|
|
34
|
+
const write = preImage === null ? { path, content: renderVerification(v), exclusive: true } : { path, content: renderVerification(v) };
|
|
35
|
+
// Staging touches the filesystem — a read-only feature directory, a full
|
|
36
|
+
// disk, a pre-image that will not read. None of that is a bug in loam, and
|
|
37
|
+
// letting it escape as `internal` sends the reader looking for one (archive
|
|
38
|
+
// draws the same line around its own `stageWrites`). Nothing was written.
|
|
39
|
+
let staged;
|
|
40
|
+
try {
|
|
41
|
+
staged = await stageWrites([write]);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return { ok: false, code: "merge-failed", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };
|
|
45
|
+
}
|
|
46
|
+
// Joined by path, not by index — vouch's rule, for vouch's reason: one entry
|
|
47
|
+
// per planned write in order is true today and promised nowhere, and a
|
|
48
|
+
// compare-and-set over the wrong pair would vouch for bytes the merge never
|
|
49
|
+
// saw. A missing entry fails closed as a merge that never happened.
|
|
50
|
+
const s = staged.find((x) => x.write.path === path);
|
|
51
|
+
if (s === undefined) {
|
|
52
|
+
await rollbackStaged(staged);
|
|
53
|
+
return { ok: false, code: "merge-failed", message: `staging returned no entry for ${display} — nothing was recorded` };
|
|
54
|
+
}
|
|
55
|
+
// The compare the read promised: the file as staging found it must be the
|
|
56
|
+
// file the merge consumed. `swapStaged` re-checks against ITS read just
|
|
57
|
+
// before the rename, so together the two compares cover the whole window
|
|
58
|
+
// from the authoritative read to the swap.
|
|
59
|
+
const edited = preImage === null ? s.before !== null : s.before === null || !s.before.equals(preImage);
|
|
60
|
+
if (edited) {
|
|
61
|
+
await rollbackStaged(staged);
|
|
62
|
+
return { ok: false, code: "record-raced", message: racedMessage(display) };
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await swapStaged(staged);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const failures = await rollbackStaged(staged);
|
|
69
|
+
if (failures.length > 0) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
code: "rollback-incomplete",
|
|
73
|
+
message: rollbackMessage(err, failures, "written"),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// EEXIST is the exclusive create losing its race — same fact as the CAS
|
|
77
|
+
// refusal, discovered by link(2) instead of by the compare.
|
|
78
|
+
if (err instanceof StagingRaceError || err.code === "EEXIST") {
|
|
79
|
+
return { ok: false, code: "record-raced", message: racedMessage(display) };
|
|
80
|
+
}
|
|
81
|
+
return { ok: false, code: "merge-failed", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };
|
|
82
|
+
}
|
|
83
|
+
return { ok: true, path };
|
|
84
|
+
}
|
|
85
|
+
function racedMessage(display) {
|
|
86
|
+
return (`${display} changed while this record was being written — another writer or an editor landed between the read and the write. ` +
|
|
87
|
+
"Nothing was written and their bytes are untouched. Re-run: the merge is recomputed over the record as it now stands.");
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../../src/core/verify/store/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The verification record's commit: staged beside the target, compared against\n * the byte pre-image the merge consumed, swapped in by one rename(2).\n *\n * The record used to go down as a plain `writeFile`, which cost three ways at\n * once: two `--record` runs read the same pre-image and the last writer\n * silently discarded the other service's attestation; a process killed\n * mid-write left the ONLY copy truncated, unreadable, and blocking every later\n * record; and an edit that landed between the read and the write was buried\n * under bytes computed from a document that no longer existed. The docs lock\n * (taken by `loam verify` before its authoritative read) closes the first;\n * this module closes the other two, and backstops the lock itself for a writer\n * that never took it.\n *\n * A sub-package of core/verify rather than a sixth file in it, and a consumer\n * of core/staging's primitives rather than a re-implementation: `stageWrites`\n * and `swapStaged` already do temp-beside-target, whole-content compare-and-set\n * and same-directory rename — vouch commits through exactly this shape.\n */\nimport { message, rollbackMessage, rollbackStaged, stageWrites, StagingRaceError, swapStaged } from \"../../staging/commit.js\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { type PlannedWrite, type StagedWrite } from \"../../staging/writes.js\";\nimport { renderVerification } from \"./render.js\";\nimport { verificationPath, type Verification } from \"../record.js\";\n\n/**\n * `record-raced` re-runs cleanly: the merge is recomputed over whatever the\n * other writer left. The other two are staging's own vocabulary, with staging's\n * meanings — `merge-failed` trusts the repo, `rollback-incomplete` does not.\n */\nexport type VerificationCommit =\n | { ok: true; path: string }\n | { ok: false; code: \"record-raced\" | \"merge-failed\" | \"rollback-incomplete\"; message: string };\n\n/**\n * `preImage` is the exact bytes the caller's merge was computed from, or null\n * when the record was absent at the authoritative read — in which case the\n * commit may only CREATE (link(2) refuses a file that appeared since), never\n * replace one it has not read.\n */\nexport async function commitVerification(\n featureDir: string,\n v: Verification,\n preImage: Buffer | null,\n docsDir: string,\n): Promise<VerificationCommit> {\n const path = verificationPath(featureDir);\n const display = repoPath(docsDir, path);\n const write: PlannedWrite =\n preImage === null ? { path, content: renderVerification(v), exclusive: true } : { path, content: renderVerification(v) };\n // Staging touches the filesystem — a read-only feature directory, a full\n // disk, a pre-image that will not read. None of that is a bug in loam, and\n // letting it escape as `internal` sends the reader looking for one (archive\n // draws the same line around its own `stageWrites`). Nothing was written.\n let staged: StagedWrite[];\n try {\n staged = await stageWrites([write]);\n } catch (err) {\n return { ok: false, code: \"merge-failed\", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };\n }\n // Joined by path, not by index — vouch's rule, for vouch's reason: one entry\n // per planned write in order is true today and promised nowhere, and a\n // compare-and-set over the wrong pair would vouch for bytes the merge never\n // saw. A missing entry fails closed as a merge that never happened.\n const s = staged.find((x) => x.write.path === path);\n if (s === undefined) {\n await rollbackStaged(staged);\n return { ok: false, code: \"merge-failed\", message: `staging returned no entry for ${display} — nothing was recorded` };\n }\n // The compare the read promised: the file as staging found it must be the\n // file the merge consumed. `swapStaged` re-checks against ITS read just\n // before the rename, so together the two compares cover the whole window\n // from the authoritative read to the swap.\n const edited = preImage === null ? s.before !== null : s.before === null || !s.before.equals(preImage);\n if (edited) {\n await rollbackStaged(staged);\n return { ok: false, code: \"record-raced\", message: racedMessage(display) };\n }\n try {\n await swapStaged(staged);\n } catch (err) {\n const failures = await rollbackStaged(staged);\n if (failures.length > 0) {\n return {\n ok: false,\n code: \"rollback-incomplete\",\n message: rollbackMessage(err, failures, \"written\"),\n };\n }\n // EEXIST is the exclusive create losing its race — same fact as the CAS\n // refusal, discovered by link(2) instead of by the compare.\n if (err instanceof StagingRaceError || (err as NodeJS.ErrnoException).code === \"EEXIST\") {\n return { ok: false, code: \"record-raced\", message: racedMessage(display) };\n }\n return { ok: false, code: \"merge-failed\", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };\n }\n return { ok: true, path };\n}\n\nfunction racedMessage(display: string): string {\n return (\n `${display} changed while this record was being written — another writer or an editor landed between the read and the write. ` +\n \"Nothing was written and their bytes are untouched. Re-run: the merge is recomputed over the record as it now stands.\"\n );\n}\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record as bytes: the header and the YAML that `store/commit.ts` swaps
|
|
3
|
+
* into place. Moved here from `../file.ts` when the contract-report validation
|
|
4
|
+
* pushed that module over the line limit — and the seam is real, not
|
|
5
|
+
* arithmetic: reading was never the inverse of rendering (a record is
|
|
6
|
+
* re-graded on the way in, because anything may have edited it since), so the
|
|
7
|
+
* two were cohabiting rather than forming a codec, while the RENDER and the
|
|
8
|
+
* COMMIT genuinely are one concern — this module produces exactly the bytes
|
|
9
|
+
* whose atomic swap that one performs. `../file.ts` keeps the reading and the
|
|
10
|
+
* grading, and its header says where the render went.
|
|
11
|
+
*/
|
|
12
|
+
import { stringify } from "yaml";
|
|
13
|
+
import {} from "../record.js";
|
|
14
|
+
/**
|
|
15
|
+
* The record as a file. The header explains what the reader is looking at,
|
|
16
|
+
* because the whole point is that this is legible to someone who has never run
|
|
17
|
+
* loam — including the part loam cannot vouch for.
|
|
18
|
+
*/
|
|
19
|
+
export function renderVerification(v) {
|
|
20
|
+
const header = [
|
|
21
|
+
`# Verification record for ${v.feature} — written by \`loam verify ${v.feature}\` (--results / --contract-results / --record).`,
|
|
22
|
+
"#",
|
|
23
|
+
"# Every claim below was derived mechanically from this feature's own artifacts:",
|
|
24
|
+
"# delta.likec4, specs/<svc>/spec.md, specs/<svc>/arch.spec.md and specs/<svc>/",
|
|
25
|
+
"# openapi.yaml. Each verdict names who answered it: `answered_by: runner` means a",
|
|
26
|
+
"# test report's digest-tagged scenarios answered it mechanically — cucumber JSON,",
|
|
27
|
+
"# or loam's own {\"loamScenarioReport\": 1, ...} shape that any runner adapts into;",
|
|
28
|
+
"# `answered_by: external-runner` means a contract-test report's operationId-matched",
|
|
29
|
+
"# entries did, and `contractReport:` records which file a --contract-results run",
|
|
30
|
+
"# read; `answered_by: agent` means somebody's word about the code, which loam did",
|
|
31
|
+
"# not check. Nothing gates on any of them.",
|
|
32
|
+
"#",
|
|
33
|
+
"# A `scenario.tested` claim confirmed by an agent is ATTESTED, not run: loam",
|
|
34
|
+
"# reports it as `verify.scenario-attested` and the feature does not count as",
|
|
35
|
+
"# verified until a report answers it. `report:` records the file a --results run",
|
|
36
|
+
"# read — its sha256 and mtime say WHICH file, not that it came from that commit;",
|
|
37
|
+
"# no digest can say that.",
|
|
38
|
+
"#",
|
|
39
|
+
"# `checklist` is a digest of the claim ids. If `loam verify` stops reporting the same",
|
|
40
|
+
"# one, the feature changed after this was recorded and these answers are stale.",
|
|
41
|
+
...(v.schema === 2
|
|
42
|
+
? [
|
|
43
|
+
"#",
|
|
44
|
+
"# Schema 2 is federated: each service entry under `attestations` binds its claim ids",
|
|
45
|
+
"# and file:line evidence to that repository's git commit. Missing claims are honestly",
|
|
46
|
+
"# unanswered; another service run may add them without rewriting existing attestations.",
|
|
47
|
+
"# `evidence_pins` records each cited file's sha256 (CRLF-normalized) and line text at",
|
|
48
|
+
"# the attested commit, so `loam validate` in that repo can convict drift later; a pin",
|
|
49
|
+
"# identifies what was cited — it does not prove the claim true.",
|
|
50
|
+
]
|
|
51
|
+
: []),
|
|
52
|
+
"",
|
|
53
|
+
].join("\n");
|
|
54
|
+
// lineWidth 0: never fold a claim onto a second line — these are grepped and diffed.
|
|
55
|
+
return header + stringify(v, { lineWidth: 0 });
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../../src/core/verify/store/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAqB,MAAM,cAAc,CAAC;AAEjD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAe;IAChD,MAAM,MAAM,GAAG;QACb,6BAA6B,CAAC,CAAC,OAAO,+BAA+B,CAAC,CAAC,OAAO,iDAAiD;QAC/H,GAAG;QACH,iFAAiF;QACjF,gFAAgF;QAChF,mFAAmF;QACnF,mFAAmF;QACnF,qFAAqF;QACrF,qFAAqF;QACrF,kFAAkF;QAClF,mFAAmF;QACnF,4CAA4C;QAC5C,GAAG;QACH,8EAA8E;QAC9E,8EAA8E;QAC9E,kFAAkF;QAClF,kFAAkF;QAClF,2BAA2B;QAC3B,GAAG;QACH,uFAAuF;QACvF,iFAAiF;QACjF,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC;YAChB,CAAC,CAAC;gBACE,GAAG;gBACH,sFAAsF;gBACtF,uFAAuF;gBACvF,yFAAyF;gBACzF,uFAAuF;gBACvF,uFAAuF;gBACvF,iEAAiE;aAClE;YACH,CAAC,CAAC,EAAE,CAAC;QACP,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,qFAAqF;IACrF,OAAO,MAAM,GAAG,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;AACjD,CAAC","sourcesContent":["/**\n * The record as bytes: the header and the YAML that `store/commit.ts` swaps\n * into place. Moved here from `../file.ts` when the contract-report validation\n * pushed that module over the line limit — and the seam is real, not\n * arithmetic: reading was never the inverse of rendering (a record is\n * re-graded on the way in, because anything may have edited it since), so the\n * two were cohabiting rather than forming a codec, while the RENDER and the\n * COMMIT genuinely are one concern — this module produces exactly the bytes\n * whose atomic swap that one performs. `../file.ts` keeps the reading and the\n * grading, and its header says where the render went.\n */\nimport { stringify } from \"yaml\";\nimport { type Verification } from \"../record.js\";\n\n/**\n * The record as a file. The header explains what the reader is looking at,\n * because the whole point is that this is legible to someone who has never run\n * loam — including the part loam cannot vouch for.\n */\nexport function renderVerification(v: Verification): string {\n const header = [\n `# Verification record for ${v.feature} — written by \\`loam verify ${v.feature}\\` (--results / --contract-results / --record).`,\n \"#\",\n \"# Every claim below was derived mechanically from this feature's own artifacts:\",\n \"# delta.likec4, specs/<svc>/spec.md, specs/<svc>/arch.spec.md and specs/<svc>/\",\n \"# openapi.yaml. Each verdict names who answered it: `answered_by: runner` means a\",\n \"# test report's digest-tagged scenarios answered it mechanically — cucumber JSON,\",\n \"# or loam's own {\\\"loamScenarioReport\\\": 1, ...} shape that any runner adapts into;\",\n \"# `answered_by: external-runner` means a contract-test report's operationId-matched\",\n \"# entries did, and `contractReport:` records which file a --contract-results run\",\n \"# read; `answered_by: agent` means somebody's word about the code, which loam did\",\n \"# not check. Nothing gates on any of them.\",\n \"#\",\n \"# A `scenario.tested` claim confirmed by an agent is ATTESTED, not run: loam\",\n \"# reports it as `verify.scenario-attested` and the feature does not count as\",\n \"# verified until a report answers it. `report:` records the file a --results run\",\n \"# read — its sha256 and mtime say WHICH file, not that it came from that commit;\",\n \"# no digest can say that.\",\n \"#\",\n \"# `checklist` is a digest of the claim ids. If `loam verify` stops reporting the same\",\n \"# one, the feature changed after this was recorded and these answers are stale.\",\n ...(v.schema === 2\n ? [\n \"#\",\n \"# Schema 2 is federated: each service entry under `attestations` binds its claim ids\",\n \"# and file:line evidence to that repository's git commit. Missing claims are honestly\",\n \"# unanswered; another service run may add them without rewriting existing attestations.\",\n \"# `evidence_pins` records each cited file's sha256 (CRLF-normalized) and line text at\",\n \"# the attested commit, so `loam validate` in that repo can convict drift later; a pin\",\n \"# identifies what was cited — it does not prove the claim true.\",\n ]\n : []),\n \"\",\n ].join(\"\\n\");\n // lineWidth 0: never fold a claim onto a second line — these are grepped and diffed.\n return header + stringify(v, { lineWidth: 0 });\n}\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The minimal reader for `services/<svc>/health.yaml` — the moment that file
|
|
3
|
+
* stops being inert. loam reads exactly one KIND of thing out of it: ids. The
|
|
4
|
+
* alert and SLI ids, so the architecture spec axis can ask whether a
|
|
5
|
+
* requirement covers each one (`health.uncovered`); and the dependency ids,
|
|
6
|
+
* so the same axis can weigh and reconcile them against the service's own
|
|
7
|
+
* model — otherwise the file an on-call engineer reaches for first is free text
|
|
8
|
+
* as far as validation is concerned and can hold false operational claims under
|
|
9
|
+
* a green result. Queries, thresholds, checks, criticality — everything else
|
|
10
|
+
* stays authored prose that no check reads, the same stance `operationIds`
|
|
11
|
+
* takes on OpenAPI schemas.
|
|
12
|
+
*
|
|
13
|
+
* The recognized keys (documented in SCHEMA.md, and deliberately few):
|
|
14
|
+
*
|
|
15
|
+
* slis: a YAML sequence; each entry contributes its `name` (or `id`
|
|
16
|
+
* alerts: when there is no name), and a plain string entry is its own id.
|
|
17
|
+
* dependencies: a YAML sequence; each entry contributes its `id` (or
|
|
18
|
+
* `service`, or `name` — accepted inputs use all three forms),
|
|
19
|
+
* and a plain string entry is its own id.
|
|
20
|
+
*
|
|
21
|
+
* Tolerant about absence, honest about breakage: a health.yaml that is missing
|
|
22
|
+
* or declares nothing recognizable yields an empty set — and an empty set
|
|
23
|
+
* produces no findings, because an absent file must not manufacture
|
|
24
|
+
* obligations. A file that EXISTS but cannot be read is different evidence:
|
|
25
|
+
* its ids are unknown, not empty, so the `unreadable` flag travels alongside
|
|
26
|
+
* (parsers never diagnose — validate grades it as `health.invalid`). The old
|
|
27
|
+
* silent empty set turned every `Covers: alert:/sli:` entry of that service
|
|
28
|
+
* into a false `covers.unknown` "typo", when the truth was this file.
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync } from "node:fs";
|
|
31
|
+
import { readFile } from "node:fs/promises";
|
|
32
|
+
import { parse } from "yaml";
|
|
33
|
+
const NO_HEALTH_IDS = { slis: [], alerts: [] };
|
|
34
|
+
export async function readHealth(healthPath) {
|
|
35
|
+
if (!existsSync(healthPath))
|
|
36
|
+
return { ids: NO_HEALTH_IDS, dependencies: [], unreadable: false };
|
|
37
|
+
let doc;
|
|
38
|
+
try {
|
|
39
|
+
doc = parse(await readFile(healthPath, "utf8"));
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
return {
|
|
43
|
+
ids: NO_HEALTH_IDS,
|
|
44
|
+
dependencies: [],
|
|
45
|
+
unreadable: true,
|
|
46
|
+
error: e instanceof Error ? e.message : String(e),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// An empty file parses to null: it declares nothing, and says so honestly.
|
|
50
|
+
// A scalar or sequence document declares nothing READABLE — flag it.
|
|
51
|
+
if (doc === null)
|
|
52
|
+
return { ids: NO_HEALTH_IDS, dependencies: [], unreadable: false };
|
|
53
|
+
if (typeof doc !== "object" || Array.isArray(doc)) {
|
|
54
|
+
return { ids: NO_HEALTH_IDS, dependencies: [], unreadable: true, error: "document is not a YAML mapping" };
|
|
55
|
+
}
|
|
56
|
+
const top = doc;
|
|
57
|
+
return {
|
|
58
|
+
ids: { slis: idsOf(top["slis"], NAME_KEYS), alerts: idsOf(top["alerts"], NAME_KEYS) },
|
|
59
|
+
dependencies: idsOf(top["dependencies"], DEPENDENCY_KEYS),
|
|
60
|
+
unreadable: false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** slis/alerts entries answer to `name` first — the key SCHEMA.md teaches. */
|
|
64
|
+
const NAME_KEYS = ["name", "id"];
|
|
65
|
+
/**
|
|
66
|
+
* Dependency entries answer to `id` first, because it is the explicit
|
|
67
|
+
* identity; `service` and `name` are the spellings found in the wild.
|
|
68
|
+
* Tolerance in reading is not vocabulary — SCHEMA.md still teaches one.
|
|
69
|
+
*/
|
|
70
|
+
const DEPENDENCY_KEYS = ["id", "service", "name"];
|
|
71
|
+
/** The ids one sequence declares — first recognized key wins, deduped, document order. */
|
|
72
|
+
function idsOf(section, keys) {
|
|
73
|
+
if (!Array.isArray(section))
|
|
74
|
+
return [];
|
|
75
|
+
const out = new Set();
|
|
76
|
+
for (const entry of section) {
|
|
77
|
+
if (typeof entry === "string") {
|
|
78
|
+
if (entry.trim().length > 0)
|
|
79
|
+
out.add(entry.trim());
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry))
|
|
83
|
+
continue;
|
|
84
|
+
const rec = entry;
|
|
85
|
+
const id = keys.map((k) => rec[k]).find((v) => typeof v === "string" && v.trim().length > 0);
|
|
86
|
+
if (typeof id === "string")
|
|
87
|
+
out.add(id.trim());
|
|
88
|
+
}
|
|
89
|
+
return [...out];
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../../src/core/vocabulary/health.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The minimal reader for `services/<svc>/health.yaml` — the moment that file\n * stops being inert. loam reads exactly one KIND of thing out of it: ids. The\n * alert and SLI ids, so the architecture spec axis can ask whether a\n * requirement covers each one (`health.uncovered`); and the dependency ids,\n * so the same axis can weigh and reconcile them against the service's own\n * model — otherwise the file an on-call engineer reaches for first is free text\n * as far as validation is concerned and can hold false operational claims under\n * a green result. Queries, thresholds, checks, criticality — everything else\n * stays authored prose that no check reads, the same stance `operationIds`\n * takes on OpenAPI schemas.\n *\n * The recognized keys (documented in SCHEMA.md, and deliberately few):\n *\n * slis: a YAML sequence; each entry contributes its `name` (or `id`\n * alerts: when there is no name), and a plain string entry is its own id.\n * dependencies: a YAML sequence; each entry contributes its `id` (or\n * `service`, or `name` — accepted inputs use all three forms),\n * and a plain string entry is its own id.\n *\n * Tolerant about absence, honest about breakage: a health.yaml that is missing\n * or declares nothing recognizable yields an empty set — and an empty set\n * produces no findings, because an absent file must not manufacture\n * obligations. A file that EXISTS but cannot be read is different evidence:\n * its ids are unknown, not empty, so the `unreadable` flag travels alongside\n * (parsers never diagnose — validate grades it as `health.invalid`). The old\n * silent empty set turned every `Covers: alert:/sli:` entry of that service\n * into a false `covers.unknown` \"typo\", when the truth was this file.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\n\nexport interface HealthIds {\n slis: string[];\n alerts: string[];\n}\n\nconst NO_HEALTH_IDS: HealthIds = { slis: [], alerts: [] };\n\n/** The read of one health.yaml: its declared ids, and whether the file could be read at all. */\nexport interface HealthFile {\n ids: HealthIds;\n /** Ids of the `dependencies:` entries — empty when the file is missing, empty, or unreadable. */\n dependencies: string[];\n /** True when the file exists but is not a readable YAML mapping. Missing and empty files read as empty, not broken. */\n unreadable: boolean;\n /** The parser's own message, when there is one to quote back. */\n error?: string;\n}\n\nexport async function readHealth(healthPath: string): Promise<HealthFile> {\n if (!existsSync(healthPath)) return { ids: NO_HEALTH_IDS, dependencies: [], unreadable: false };\n let doc: unknown;\n try {\n doc = parse(await readFile(healthPath, \"utf8\"));\n } catch (e) {\n return {\n ids: NO_HEALTH_IDS,\n dependencies: [],\n unreadable: true,\n error: e instanceof Error ? e.message : String(e),\n };\n }\n // An empty file parses to null: it declares nothing, and says so honestly.\n // A scalar or sequence document declares nothing READABLE — flag it.\n if (doc === null) return { ids: NO_HEALTH_IDS, dependencies: [], unreadable: false };\n if (typeof doc !== \"object\" || Array.isArray(doc)) {\n return { ids: NO_HEALTH_IDS, dependencies: [], unreadable: true, error: \"document is not a YAML mapping\" };\n }\n const top = doc as Record<string, unknown>;\n return {\n ids: { slis: idsOf(top[\"slis\"], NAME_KEYS), alerts: idsOf(top[\"alerts\"], NAME_KEYS) },\n dependencies: idsOf(top[\"dependencies\"], DEPENDENCY_KEYS),\n unreadable: false,\n };\n}\n\n/** slis/alerts entries answer to `name` first — the key SCHEMA.md teaches. */\nconst NAME_KEYS = [\"name\", \"id\"] as const;\n/**\n * Dependency entries answer to `id` first, because it is the explicit\n * identity; `service` and `name` are the spellings found in the wild.\n * Tolerance in reading is not vocabulary — SCHEMA.md still teaches one.\n */\nconst DEPENDENCY_KEYS = [\"id\", \"service\", \"name\"] as const;\n\n/** The ids one sequence declares — first recognized key wins, deduped, document order. */\nfunction idsOf(section: unknown, keys: readonly string[]): string[] {\n if (!Array.isArray(section)) return [];\n const out = new Set<string>();\n for (const entry of section) {\n if (typeof entry === \"string\") {\n if (entry.trim().length > 0) out.add(entry.trim());\n continue;\n }\n if (entry === null || typeof entry !== \"object\" || Array.isArray(entry)) continue;\n const rec = entry as Record<string, unknown>;\n const id = keys.map((k) => rec[k]).find((v) => typeof v === \"string\" && v.trim().length > 0);\n if (typeof id === \"string\") out.add(id.trim());\n }\n return [...out];\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A coherence breach: something that makes a feature unsafe to merge into the
|
|
3
|
+
* living docs. Shared by every check the archive gate runs.
|
|
4
|
+
*/
|
|
5
|
+
/** Whether `loam archive` refuses on this issue without `--approve`. */
|
|
6
|
+
export function gatesArchive(i) {
|
|
7
|
+
return i.gates ?? i.severity === "error";
|
|
8
|
+
}
|
|
9
|
+
/** One issue in the shared validation/status finding shape. */
|
|
10
|
+
export function issueFinding(i) {
|
|
11
|
+
return {
|
|
12
|
+
severity: i.severity,
|
|
13
|
+
code: i.code,
|
|
14
|
+
gates: gatesArchive(i),
|
|
15
|
+
...(i.subject === undefined ? {} : { subject: i.subject }),
|
|
16
|
+
...(i.locations === undefined ? {} : { locations: i.locations }),
|
|
17
|
+
message: i.message,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether `--approve` can move this issue when it gates. A third axis beside
|
|
22
|
+
* severity and gating: severity answers "is the DOCUMENT valid", gating
|
|
23
|
+
* answers "is the MERGE safe", and this one answers whose call the refusal is
|
|
24
|
+
* — a human's or the grammar's. Almost every gating issue is a judgment about
|
|
25
|
+
* the feature, which is exactly the judgment `--approve` exists to override.
|
|
26
|
+
*
|
|
27
|
+
* The exceptions are the ones where approval could not change what happens.
|
|
28
|
+
* `c4.service-binding-invalid`: the binding IS the path the merge would write,
|
|
29
|
+
* so the fix is the name, never the flag. `glossary.term-exists`: the merge is
|
|
30
|
+
* a whole-file copy over an authored definition, so approving it is approving a
|
|
31
|
+
* deletion nobody described — and the alternative the message names (edit the
|
|
32
|
+
* living definition in the same change) costs one `git mv` and loses nothing.
|
|
33
|
+
* `usecase.flow-exists` is the same act one axis over: the merge would copy a
|
|
34
|
+
* feature's flow over a living one, and an ordered hop sequence is recorded
|
|
35
|
+
* nowhere else either. `usecase.flow-invalid` is the other kind of exception —
|
|
36
|
+
* not a deletion but a BLINDNESS: loam could not read the feature's flows
|
|
37
|
+
* against the map its own merge would leave, so it graded nothing, and
|
|
38
|
+
* `--approve` overrides loam's judgment about coherence and never its ability
|
|
39
|
+
* to read an axis. That is the same line `delta.likec4`'s unparseable case is
|
|
40
|
+
* refused on, one merge over.
|
|
41
|
+
*
|
|
42
|
+
* `archive` refuses these under the same `not-coherent` envelope and spells
|
|
43
|
+
* this verdict per issue as the additive `overridable` key, so a `--json`
|
|
44
|
+
* consumer branches on data rather than on a code list.
|
|
45
|
+
*/
|
|
46
|
+
const NEVER_OVERRIDABLE = new Set([
|
|
47
|
+
"c4.service-binding-invalid",
|
|
48
|
+
"glossary.term-exists",
|
|
49
|
+
"usecase.flow-exists",
|
|
50
|
+
"usecase.flow-invalid",
|
|
51
|
+
]);
|
|
52
|
+
export function approveOverrides(i) {
|
|
53
|
+
return !NEVER_OVERRIDABLE.has(i.code);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=issue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../src/core/vocabulary/issue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuBH,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAC3C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;QACtB,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;QAChE,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,iBAAiB,GAA2B,IAAI,GAAG,CAAY;IACnE,4BAA4B;IAC5B,sBAAsB;IACtB,qBAAqB;IACrB,sBAAsB;CACvB,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB,CAAC,CAAQ;IACvC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC","sourcesContent":["/**\n * A coherence breach: something that makes a feature unsafe to merge into the\n * living docs. Shared by every check the archive gate runs.\n */\n\nimport type { Finding, FindingLocation } from \"./report.js\";\n\nexport interface Issue {\n severity: \"error\" | \"warn\";\n /** Stable machine identifier for the breach — see the labels below. */\n code: IssueCode;\n /** What the breach is about when that is narrower than the feature — usually a service. */\n subject?: string;\n message: string;\n locations?: FindingLocation[];\n /**\n * Does this issue stop `loam archive`? Severity and gating answer two\n * different questions: severity says whether the DOCUMENT is valid (`loam\n * validate` fails on errors), gating says whether the MERGE is safe. They\n * usually agree — errors gate, warnings do not — so the field is stated only\n * where they diverge: a legal document whose merge would drop authored\n * content is a warning that gates.\n */\n gates?: boolean;\n}\n\n/** Whether `loam archive` refuses on this issue without `--approve`. */\nexport function gatesArchive(i: Issue): boolean {\n return i.gates ?? i.severity === \"error\";\n}\n\n/** One issue in the shared validation/status finding shape. */\nexport function issueFinding(i: Issue): Finding {\n return {\n severity: i.severity,\n code: i.code,\n gates: gatesArchive(i),\n ...(i.subject === undefined ? {} : { subject: i.subject }),\n ...(i.locations === undefined ? {} : { locations: i.locations }),\n message: i.message,\n };\n}\n\n/**\n * Whether `--approve` can move this issue when it gates. A third axis beside\n * severity and gating: severity answers \"is the DOCUMENT valid\", gating\n * answers \"is the MERGE safe\", and this one answers whose call the refusal is\n * — a human's or the grammar's. Almost every gating issue is a judgment about\n * the feature, which is exactly the judgment `--approve` exists to override.\n *\n * The exceptions are the ones where approval could not change what happens.\n * `c4.service-binding-invalid`: the binding IS the path the merge would write,\n * so the fix is the name, never the flag. `glossary.term-exists`: the merge is\n * a whole-file copy over an authored definition, so approving it is approving a\n * deletion nobody described — and the alternative the message names (edit the\n * living definition in the same change) costs one `git mv` and loses nothing.\n * `usecase.flow-exists` is the same act one axis over: the merge would copy a\n * feature's flow over a living one, and an ordered hop sequence is recorded\n * nowhere else either. `usecase.flow-invalid` is the other kind of exception —\n * not a deletion but a BLINDNESS: loam could not read the feature's flows\n * against the map its own merge would leave, so it graded nothing, and\n * `--approve` overrides loam's judgment about coherence and never its ability\n * to read an axis. That is the same line `delta.likec4`'s unparseable case is\n * refused on, one merge over.\n *\n * `archive` refuses these under the same `not-coherent` envelope and spells\n * this verdict per issue as the additive `overridable` key, so a `--json`\n * consumer branches on data rather than on a code list.\n */\nconst NEVER_OVERRIDABLE: ReadonlySet<IssueCode> = new Set<IssueCode>([\n \"c4.service-binding-invalid\",\n \"glossary.term-exists\",\n \"usecase.flow-exists\",\n \"usecase.flow-invalid\",\n]);\n\nexport function approveOverrides(i: Issue): boolean {\n return !NEVER_OVERRIDABLE.has(i.code);\n}\n\n/**\n * The three grades a CAPABILITY DOCUMENT earns on its own terms, named as a\n * union of their own because both corpora emit them and the two must stay one\n * list: `validate --all` grades the living `capabilities/<id>/spec.md` and\n * files them as Findings, while the archive gate grades a feature's\n * `features/<FEAT>/capabilities/<id>/spec.md` and files the SAME three as\n * archive-gating Issues.\n *\n * Grading the delta is not belt-and-braces. Without it, a capability\n * requirement with no `Requirement-ID:` — or one carrying `Operations:`, the\n * service-altitude line the whole corpus exists to keep out — merges into the\n * living tree and only THEN earns its error, against a document whoever reads\n * the finding did not write. The feature-local delta must not become a second\n * way to write service requirements, and this is the rule that stops it.\n *\n * No code is added to the product here: all three already shipped, are already\n * emitted from `core/capabilities/findings.ts` with literal `code:` values, and\n * are already documented in the `/loam-check` table. What the union member adds\n * is the compiler's permission to file the same finding at the archive gate.\n */\nexport type CapabilityDocCode =\n /** a requirement in a capability document with no `Requirement-ID:` — identity by heading, which every join to it breaks on */\n | \"capability.requirement-unidentified\"\n /** a capability requirement carrying `Operations:`/`Covers:`/`Publishes:`/`Consumes:` — a service requirement filed at the wrong altitude */\n | \"capability.requirement-service-scoped\"\n /** a capability requirement carrying `Capability:` or `Realizes:` — the axis's own joins, which point into the tree and so do nothing written inside it */\n | \"capability.requirement-inert-join\";\n\nexport type IssueCode =\n /* --- cross-axis: C4 <-> requirements <-> OpenAPI --- */\n /** the architecture axis could not be read at all */\n | \"delta.invalid\"\n /** the delta declares elements or edges but none carry the feature tag — loam cannot see any of it */\n | \"delta.nothing-tagged\"\n /** E1 — a requirement governs an operation its service's OpenAPI does not define */\n | \"spec-api.op-undefined\"\n /** E1, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"spec-api.op-pending\"\n /** E2 — a C4 edge calls an operation the target's OpenAPI does not define */\n | \"c4-api.op-undefined\"\n /** E2, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"c4-api.op-pending\"\n /** E2's lifecycle shadow — a NEW tagged edge consumes an operation the living provider contract marks `deprecated: true`; advisory, never a gate */\n | \"c4-api.op-deprecated\"\n /** a NEW tagged edge consumes an operation this same feature removes */\n | \"c4-api.op-removing\"\n /** an operation-removal marker addresses no living path+method */\n | \"openapi.remove-target-missing\"\n /** an operation-removal marker's id differs from the living operation at that path+method */\n | \"openapi.remove-target-mismatch\"\n /** a REMOVED requirement governs an operation but the feature has no matching removal marker */\n | \"openapi.remove-marker-missing\"\n /** an operation-removal marker is not justified by a REMOVED requirement */\n | \"openapi.remove-marker-unjustified\"\n /* --- the event axis: C4 <-> requirements <-> AsyncAPI, joined on the message name --- */\n /** the FEATURE's own asyncapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every event-axis check for the service is suspended */\n | \"asyncapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or one on a slot the living contract does not have; a malformed `x-loam-baselines` record, an entry of it naming a component surface the delta does not declare, or one whose living counterpart is gone */\n | \"asyncapi.baseline-invalid\"\n /** a feature slot or component surface with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it; warn that gates, counted per service */\n | \"asyncapi.baseline-missing\"\n /** the living slot or component surface changed since this delta pinned it — merging would discard whoever landed in between */\n | \"asyncapi.baseline-stale\"\n /** a slot-removal marker addresses a (section, key) the living contract does not have */\n | \"asyncapi.remove-target-missing\"\n /** a message-removal marker's name differs from the living declaration at that key */\n | \"asyncapi.remove-target-mismatch\"\n /** a REMOVED requirement's Publishes:/Consumes: line names a message the living contract still declares, with no matching removal marker in the feature */\n | \"asyncapi.remove-marker-missing\"\n /** a message-removal marker no REMOVED requirement's Publishes:/Consumes: line justifies — message slots only; channel and operation slots need exactness alone */\n | \"asyncapi.remove-marker-unjustified\"\n /** the feature retires a message the LIVING fleet still consumes — a landscape consumes-edge, or another service's living requirement */\n | \"asyncapi.remove-message-consumed\"\n /** another feature in flight adds or edits the same (service, message) — whichever archives second replaces the other's declaration wholesale */\n | \"asyncapi.message-conflict\"\n /** internal `$ref`(s) resolving to nothing — validate's warn on a living contract; a gating plan issue at archive when the MERGED document would carry the dangling reference */\n | \"asyncapi.ref-unresolved\"\n /** a tagged edge publishes/consumes a message its bound service's contract — as the feature's merge would leave it, feature ∪ living where no delta exists — declares no matching send/receive operation for */\n | \"c4-event.message-undefined\"\n /** softened — the message is introduced by another feature still in flight; archive that one first */\n | \"c4-event.message-pending\"\n /** a delta requirement's Publishes:/Consumes: line names a message the service's contract — as the feature's merge would leave it, so a retired declaration stops answering — does not declare in that direction; validate's living-side code, graded in feature scope too */\n | \"spec-event.message-undefined\"\n /** softened — the named message is introduced by another feature still in flight */\n | \"spec-event.message-pending\"\n /** W1 — an operation is called but no requirement governs it */\n | \"c4.op-ungoverned\"\n /** W2 — the feature adds an operation no architecture edge consumes */\n | \"api.op-unconsumed\"\n /** W3 — a new service arrives with no requirement delta */\n | \"service.no-requirement-delta\"\n /** W4 — a \"Calls\" edge carries no operation link */\n | \"c4.op-link-missing\"\n /** an explicit `metadata { service }` binding — a tagged element's, or one riding anywhere inside its authored block — breaks the service-id grammar; the merge would splice the name into the living landscape verbatim, and probe services/<id>/ with it; never overridable, the path is a mechanical fact */\n | \"c4.service-binding-invalid\"\n /* --- delta shape: does the diff apply to the living spec it claims to change? --- */\n /** a heading that nearly matches the delta grammar — its requirements merge as nothing */\n | \"delta.unknown-section\"\n /** a delta file with requirements but no delta section anywhere — the whole file would merge as nothing */\n | \"delta.no-delta-sections\"\n /** a requirement stranded under a prose heading — archive will not merge it */\n | \"delta.requirement-not-merged\"\n /** a Requirement-ID in a delta violates the stable-id grammar */\n | \"delta.requirement-id-invalid\"\n /** one delta requirement declares Requirement-ID more than once */\n | \"delta.requirement-id-repeated\"\n /** one Requirement-ID identifies multiple requirements in a delta */\n | \"delta.requirement-id-duplicate\"\n /** the living spec has malformed/ambiguous IDs and cannot be selected safely */\n | \"delta.living-requirement-id-invalid\"\n /** a delta's stable ID and heading select different living requirements */\n | \"delta.requirement-identity-collision\"\n /** MODIFIED a requirement the living spec does not have */\n | \"delta.modified-unknown\"\n /** REMOVED a requirement the living spec does not have */\n | \"delta.removed-unknown\"\n /** ADDED a requirement the living spec already has — the merge would replace it */\n | \"delta.added-duplicate\"\n /** ADDED a name that differs only in case from a living requirement — the merge matches exactly, so both would coexist */\n | \"delta.added-near-duplicate\"\n /** MODIFIED something another feature in flight introduces — an ordering dependency */\n | \"delta.modified-pending\"\n /** REMOVED something another feature in flight introduces */\n | \"delta.removed-pending\"\n /** two features in flight add the same requirement to the same service */\n | \"delta.added-conflict\"\n /* --- archive plan: breaches only the merge computation itself can see --- */\n /** a LIVING requirement outside `## Requirements` — the merge rewrites only that section, so it would land twice */\n | \"living.requirement-outside-requirements\"\n /** the delta redefines an operation the living OpenAPI already has — the merge overwrites it wholesale */\n | \"openapi.op-modified\"\n /** a component the merge carries — reached by a merged operation, or declared by a delta whose whole change is components — already exists in the living OpenAPI with different content, and the merge overwrites it wholesale */\n | \"openapi.component-modified\"\n /** a $ref reachable from the merged operations or a merged component resolves in neither the feature's OpenAPI nor the living one — merging would write a dangling reference */\n | \"openapi.ref-unresolved\"\n /** the delta redefines a PATH-level key (parameters, servers, summary) the living OpenAPI already has — the overwrite applies to every operation on that path */\n | \"openapi.path-item-modified\"\n /** the delta redefines a `components.messages` slot the living AsyncAPI already has — the merge overwrites the living message wholesale */\n | \"asyncapi.message-modified\"\n /** the delta redefines a `channels` slot the living AsyncAPI already has — the merge overwrites it wholesale, inline channel messages included */\n | \"asyncapi.channel-modified\"\n /** the delta redefines an `operations` slot the living AsyncAPI already has — the merge overwrites it wholesale */\n | \"asyncapi.operation-modified\"\n /** the delta declares a component SURFACE outside `components.messages` — a schema, a security scheme — that the living AsyncAPI already has with different content, and the merge copies the feature's version over it wholesale */\n | \"asyncapi.component-modified\"\n /** an `x-loam-remove: true` marker with no operationId — loam cannot tell which operation it retires, and the marker itself would reach the living contract */\n | \"openapi.remove-marker-anonymous\"\n /** an `x-loam-remove: true` written at PATH level, beside the methods — it addresses no operation, so it retires nothing and is not a contract key either */\n | \"openapi.remove-marker-path-level\"\n /** an `x-loam-remove: true` nested on an INLINE channel message — inline messages are channel interior, never slots, so it retires nothing; when the channel is otherwise unchanged the marker surfaces nowhere else at all */\n | \"asyncapi.remove-marker-inline\"\n /** the living OpenAPI defines one operationId in two (path, method) slots — every join on the id picks one arbitrarily */\n | \"openapi.duplicate-operationid\"\n /** the FEATURE's own openapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every contract-axis check for the service is suspended, and the archive plan refuses the merge mechanically */\n | \"openapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or sits on an operation the living contract does not have; a malformed `x-loam-baselines` record, an entry of it pinning a surface the delta does not restate, or one whose living counterpart is gone */\n | \"openapi.baseline-invalid\"\n /** a feature operation, restated path-level key or component with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it */\n | \"openapi.baseline-missing\"\n /** the living operation, path-level key or component changed since this delta pinned it — merging would discard whoever landed in between */\n | \"openapi.baseline-stale\"\n /** the feature retires an operation the LIVING fleet still consumes — a landscape edge's `metadata { op }`, or another service's living requirement */\n | \"openapi.remove-op-consumed\"\n /** two requirements in the LIVING document share one heading — MODIFIED rewrites the first, REMOVED deletes both, so no delta applies predictably */\n | \"delta.living-duplicate-requirement\"\n /** another feature in flight MODIFIES/REMOVES the same living requirement — whichever archives second replaces the other's text wholesale */\n | \"delta.modified-conflict\"\n /** a `Based-On:` line whose value is not a digest, or declared twice — the pin cannot be compared, so it protects nothing */\n | \"delta.baseline-invalid\"\n /** a MODIFIED/REMOVED requirement with no `Based-On:` — nothing can tell whether the living text moved under it */\n | \"delta.baseline-missing\"\n /** the living requirement changed since this delta was written — merging it would silently discard whoever landed in between */\n | \"delta.baseline-stale\"\n /** the archive creates `services/<id>/` but nothing writes its `model.likec4` — the fleet gate will report the service incomplete */\n | \"service.no-model\"\n /* --- capability axis: the Capability: join against architecture/capabilities.yaml --- */\n /** a delta requirement's `Capability:` entry that the fleet vocabulary does not declare — the merge would land a join that resolves to nothing, so it blocks archive like other errors, and `--approve` overrides it */\n | \"capability.unknown\"\n /** a delta requirement's `Realizes:` entry that names no capability requirement — same argument as `capability.unknown` one join over: the merge would land a pointer at a promise that does not exist, looking exactly like a working one */\n | \"capability.realizes-unknown\"\n /** a capability requirement this feature ADDS that no `Realizes:` line in the same feature's service deltas names — the merge would land a business promise nothing in this change keeps. A warning that GATES, `--approve` overrides: the document is legal (writing ahead of the fleet is the intended use, which is why `capability.requirement-unrealized` warns and never gates), the MERGE is what is unsafe. Joined by `Realizes:`, NOT by `Covers:` — the name is the roadmap's, the join is the other one */\n | \"capability.uncovered\"\n /** the same join in the removal direction — this feature RETIRES a capability requirement that something the merge leaves behind still realizes, so the archive would leave `capability.realizes-unknown` standing against a document nobody in the feature touched. An error, exactly as `openapi.remove-op-consumed` is for the identical shape one axis over */\n | \"capability.remove-requirement-realized\"\n | CapabilityDocCode\n /* --- the domain glossary: a term a feature introduces --- */\n /** a `features/<FEAT>/glossary/<term>.md` whose term the living glossary already defines — the merge is a whole-file copy, so it would replace an authored definition wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local glossary document INTRODUCES a term, and rewriting one belongs in a pull request where git produces the conflict */\n | \"glossary.term-exists\"\n /* --- the use-case axis: a flow a feature introduces --- */\n /** a `features/<FEAT>/usecases/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The flows were not graded, and archiving would copy them into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it */\n | \"usecase.flow-invalid\"\n /** a `features/<FEAT>/usecases/<name>.likec4` whose flow the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored hop sequence wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local flow INTRODUCES a use case, and rewriting one belongs in a pull request where git produces the conflict */\n | \"usecase.flow-exists\"\n /* --- authoring: did a person actually write this? --- */\n /** a document `loam new` scaffolded still carries its exact placeholder text — the merge would publish a requirement, scenario or description nobody authored */\n | \"scaffold.placeholder\"\n /** intent.md is missing, or says nothing outside the scaffold's own comments — nothing states why the feature exists */\n | \"intent.empty\"\n /* --- docs-repo contract: --all only, never a merge question --- */\n /** AGENTS.md carries no version stamp, or one older than the running binary — the agent contract may have drifted */\n | \"agents.stale\";\n"]}
|