@ybotok/loam 0.1.0-beta.2 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2570 -61
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2362 -245
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +82 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/agent/agents-stamp.js +155 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/asyncapi/read.js +239 -0
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +59 -55
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +132 -77
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -291
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -254
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1591
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -343
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2065
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js +0 -68
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/brief.js +0 -733
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -559
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -479
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -720
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -436
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -481
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1104
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stamp itself: verify every spec, then write them all or none.
|
|
3
|
+
*
|
|
4
|
+
* Separate from the command because this half has nothing to say to a terminal.
|
|
5
|
+
* It refuses on any spec it cannot verify and stamps none of them — a vouch is
|
|
6
|
+
* a person's claim that the documents match the code, and a partial one would
|
|
7
|
+
* put that claim on files nobody checked.
|
|
8
|
+
*
|
|
9
|
+
* What lands in each file: `status`, `last_verified`, `vouched_by`,
|
|
10
|
+
* `sources_digest`, the `sources_files` index, `content_digest` — and
|
|
11
|
+
* `vouch_scope` when the person read a sample rather than the document — with
|
|
12
|
+
* every byte below the frontmatter left identical. Nothing is written unless
|
|
13
|
+
* all of it can be stamped truthfully for EVERY file: a half-stamp (verified
|
|
14
|
+
* with no digest behind it, or one file stamped and its sibling not) is
|
|
15
|
+
* exactly the claim this command exists to stop being possible, so every
|
|
16
|
+
* present file is verified before any is written, and the writing itself is
|
|
17
|
+
* staged and swapped in like archive's merge, so a failure between the pair's
|
|
18
|
+
* two writes rolls the first back instead of leaving it. The two digests are
|
|
19
|
+
* the two halves of one promise — `sources_digest` pins the code that was
|
|
20
|
+
* read, `content_digest` pins the words it was read against — so
|
|
21
|
+
* `loam validate` can see either side move. (This paragraph came from a
|
|
22
|
+
* docblock stranded at the foot of `vouch.ts` when the stamp moved down here.)
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync } from "node:fs";
|
|
25
|
+
import { withFrontmatterFields } from "../../core/document/frontmatter.js";
|
|
26
|
+
import { encodeVouchScope } from "../../core/provenance/sample/scope.js";
|
|
27
|
+
import { contentDigest, encodeSourceIndex } from "../../core/provenance/stamp.js";
|
|
28
|
+
import { SPEC_AXES } from "../../core/repo/paths.js";
|
|
29
|
+
import { locateServicePaths } from "../../core/repo/service-target.js";
|
|
30
|
+
import { rollbackStaged, stageWrites } from "../../core/staging/commit.js";
|
|
31
|
+
import { join } from "node:path";
|
|
32
|
+
import { COMMIT_INTENT, InterruptedCommitError } from "../../core/staging/interrupted.js";
|
|
33
|
+
import { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from "../../core/staging/lock.js";
|
|
34
|
+
import { recoverInterruptedCommit } from "../../core/staging/recovery/recover.js";
|
|
35
|
+
import { commitStaged } from "../../core/staging/txn/transaction.js";
|
|
36
|
+
import {} from "../../core/staging/writes.js";
|
|
37
|
+
import {} from "./contract.js";
|
|
38
|
+
import { axisScope, sampleDrift } from "./sample/plan.js";
|
|
39
|
+
import { noLivingSpecMessage, verifySpec } from "./vet/verify.js";
|
|
40
|
+
export async function vouch(req) {
|
|
41
|
+
// A predecessor's journal is rolled forward BEFORE verification reads a
|
|
42
|
+
// byte. Recovery under the commit lock alone was tried first and kept as
|
|
43
|
+
// the backstop, but it changes files verification has already read, so the
|
|
44
|
+
// run's own raced check then refused a sound stamp and the fix doctor
|
|
45
|
+
// prints took two runs instead of one. Cheap when there is nothing to do:
|
|
46
|
+
// one existsSync.
|
|
47
|
+
let recoveredEarly = null;
|
|
48
|
+
if (existsSync(join(req.docsDir, COMMIT_INTENT))) {
|
|
49
|
+
let release;
|
|
50
|
+
try {
|
|
51
|
+
release = await acquireDocsLockWaiting(req.docsDir, LOCK_WAIT_MS);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (err instanceof DocsBusyError)
|
|
55
|
+
return { ok: false, code: "docs-busy", message: err.message };
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
recoveredEarly = await recoverInterruptedCommit(req.docsDir);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
if (err instanceof InterruptedCommitError)
|
|
63
|
+
return { ok: false, code: "commit-interrupted", message: err.message };
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await release();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const paths = await locateServicePaths(req.docsDir, req.service);
|
|
71
|
+
if (!existsSync(paths.spec)) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
code: "unknown-target",
|
|
75
|
+
message: noLivingSpecMessage(paths.spec, req.service),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Verify first, stamp after: spec.md is required (checked above), arch.spec.md
|
|
79
|
+
// rides when it exists, and one file that cannot be verified refuses the run
|
|
80
|
+
// before anything is written. The two axes are walked by name rather than
|
|
81
|
+
// accumulated in a loop, because everything downstream needs to know WHICH
|
|
82
|
+
// file it is holding — the required one or the optional one — and a loop can
|
|
83
|
+
// only say that positionally.
|
|
84
|
+
const [specAxis, archAxis] = SPEC_AXES;
|
|
85
|
+
const specVerified = await verifySpec(req, paths[specAxis.key], specAxis.file);
|
|
86
|
+
if (!specVerified.ok)
|
|
87
|
+
return specVerified;
|
|
88
|
+
let archVerified = null;
|
|
89
|
+
if (existsSync(paths[archAxis.key])) {
|
|
90
|
+
const outcome = await verifySpec(req, paths[archAxis.key], archAxis.file);
|
|
91
|
+
if (!outcome.ok)
|
|
92
|
+
return outcome;
|
|
93
|
+
archVerified = outcome;
|
|
94
|
+
}
|
|
95
|
+
const verified = [specVerified, ...(archVerified === null ? [] : [archVerified])];
|
|
96
|
+
// The READING window's race, which the commit window's byte compare cannot
|
|
97
|
+
// see. Under `--sample` a person spends minutes on the list before they
|
|
98
|
+
// answer, and the sample they were shown was chosen from digests taken
|
|
99
|
+
// before that. If either digest moved — or a second axis file appeared —
|
|
100
|
+
// the sections they read are not the sections this stamp would claim, and
|
|
101
|
+
// `vouch_scope` would record a read that did not happen. Nothing is written.
|
|
102
|
+
const raced = req.sample === undefined ? null : sampleDrift(req.sample, verified, req.service);
|
|
103
|
+
if (raced !== null)
|
|
104
|
+
return { ok: false, code: "vouch-raced", message: raced };
|
|
105
|
+
const specPlan = planStamp(specVerified, req);
|
|
106
|
+
const archPlan = archVerified === null ? null : planStamp(archVerified, req);
|
|
107
|
+
const writes = [specPlan.write, ...(archPlan === null ? [] : [archPlan.write])];
|
|
108
|
+
// The slow half — reading specs, hashing sources, composing stamps — ran
|
|
109
|
+
// UNLOCKED above, so vouches for independent services do not serialise
|
|
110
|
+
// behind one repo-wide mutex. Only the commit window takes the docs lock:
|
|
111
|
+
// the same one archive, rebase and verify --record hold, in the waiting
|
|
112
|
+
// form, because two vouches for different services are both supposed to
|
|
113
|
+
// land. This is the lock the roadmap called missing: without it a vouch
|
|
114
|
+
// could stage against bytes an archive was mid-swap over, and the byte
|
|
115
|
+
// compare below would refuse a perfectly sound stamp — or worse, an
|
|
116
|
+
// interrupted commit's journal would be silently written over.
|
|
117
|
+
let releaseLock;
|
|
118
|
+
try {
|
|
119
|
+
releaseLock = await acquireDocsLockWaiting(req.docsDir, LOCK_WAIT_MS);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
if (err instanceof DocsBusyError)
|
|
123
|
+
return { ok: false, code: "docs-busy", message: err.message };
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
// A journal under the lock means the last writer never finished; recover
|
|
128
|
+
// or refuse before reading the pre-images this commit will compare.
|
|
129
|
+
let recovered;
|
|
130
|
+
try {
|
|
131
|
+
recovered = await recoverInterruptedCommit(req.docsDir);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (err instanceof InterruptedCommitError)
|
|
135
|
+
return { ok: false, code: "commit-interrupted", message: err.message };
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
return await commitStamp(req, { writes, specPlan, archPlan, verified }, recovered ?? recoveredEarly);
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
await releaseLock();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The commit window: stage, compare against what verification read, journal,
|
|
146
|
+
* swap. Split from `vouch` so the lock's extent is visible in the code shape —
|
|
147
|
+
* everything in here holds it, nothing above does.
|
|
148
|
+
*/
|
|
149
|
+
async function commitStamp(req, plan, recovered) {
|
|
150
|
+
const { writes, specPlan, archPlan, verified } = plan;
|
|
151
|
+
const staged = await stageWrites(writes);
|
|
152
|
+
// One shared docs repo, ten service repos, and nothing stopping two of them
|
|
153
|
+
// from vouching at once. Between reading a spec and swapping the stamp in
|
|
154
|
+
// there is a window in which somebody else's vouch (or an editor, or a merge)
|
|
155
|
+
// can land in the same file — and because the new bytes were computed from
|
|
156
|
+
// the OLD ones, swapping them in would take that stamp back out without a
|
|
157
|
+
// word. `stageWrites` has already read what is on disk right now, so the
|
|
158
|
+
// check is a comparison, not another read: if the file is not what was
|
|
159
|
+
// verified, this run is describing a document that no longer exists.
|
|
160
|
+
// Bytes, not text: `stageWrites` reads the file as a Buffer (a string
|
|
161
|
+
// comparison against it is never equal, which made every vouch report
|
|
162
|
+
// `vouch-raced` and stamp nothing). A file that is GONE is still a race —
|
|
163
|
+
// `null` is not "empty", it is "the document this run described is not
|
|
164
|
+
// there any more".
|
|
165
|
+
// Joined by path, not by index: pairing the two lists positionally assumed
|
|
166
|
+
// `stageWrites` returns one entry per planned write in order — true today,
|
|
167
|
+
// promised nowhere, and a compare-and-set that silently compares the wrong
|
|
168
|
+
// pair would stamp over somebody else's vouch. A staged write no verified
|
|
169
|
+
// file claims has nothing to be compared against, so it counts as raced;
|
|
170
|
+
// fail-closed is the only safe direction here.
|
|
171
|
+
const verifiedByPath = new Map(verified.map((v) => [v.path, v]));
|
|
172
|
+
const raced = staged.filter((s) => {
|
|
173
|
+
const v = verifiedByPath.get(s.write.path);
|
|
174
|
+
return v === undefined || s.before === null || !s.before.equals(Buffer.from(v.raw, "utf8"));
|
|
175
|
+
});
|
|
176
|
+
if (raced.length > 0) {
|
|
177
|
+
// Nothing has swapped yet, so the rollback is only the temp files going
|
|
178
|
+
// away — the other writer's stamp is left exactly as it landed.
|
|
179
|
+
await rollbackStaged(staged);
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
code: "vouch-raced",
|
|
183
|
+
message: `${req.service}: ${raced.map((s) => s.write.path).join(", ")} changed while this vouch was running — ` +
|
|
184
|
+
`another vouch or an edit landed first. Nothing was stamped: re-read the document and re-run.`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// The journaled commit: intent fsynced before the first rename, so a kill
|
|
188
|
+
// between the pair's two swaps is recoverable instead of invisible — the
|
|
189
|
+
// exact half-stamped state the all-or-nothing verification exists to rule
|
|
190
|
+
// out used to be reachable through that one window. The codes are archive's,
|
|
191
|
+
// reused rather than minted: rolled back → nothing changed, re-running can
|
|
192
|
+
// work; incomplete → the files listed need a human.
|
|
193
|
+
const committed = await commitStaged({
|
|
194
|
+
root: req.docsDir,
|
|
195
|
+
command: "vouch",
|
|
196
|
+
// Runnable where vouch runs — the SERVICE repo, with --yes because the
|
|
197
|
+
// journal's reader may be unattended. Any other journaled docs-repo
|
|
198
|
+
// writer recovers this journal on its next run too.
|
|
199
|
+
rerun: `loam vouch --service ${req.service} --yes`,
|
|
200
|
+
target: req.service,
|
|
201
|
+
}, staged, "stamped");
|
|
202
|
+
if (!committed.ok) {
|
|
203
|
+
return committed.raced
|
|
204
|
+
? {
|
|
205
|
+
ok: false,
|
|
206
|
+
code: "vouch-raced",
|
|
207
|
+
message: `${req.service}: a document changed while this vouch was committing — ` +
|
|
208
|
+
`another writer landed first. Nothing was stamped: re-read and re-run.`,
|
|
209
|
+
}
|
|
210
|
+
: { ok: false, code: committed.code, message: committed.message };
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
ok: true,
|
|
214
|
+
status: "verified",
|
|
215
|
+
lastVerified: req.today,
|
|
216
|
+
vouchedBy: req.vouchedBy,
|
|
217
|
+
recovered,
|
|
218
|
+
stamped: { spec: specPlan.stamped, archSpec: archPlan === null ? null : archPlan.stamped },
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* One verified file's share of the commit: the bytes to write, and the record
|
|
223
|
+
* of what was stamped into them. Kept beside each other because they are the
|
|
224
|
+
* same computation — the report must describe the file that was actually
|
|
225
|
+
* written, not a second guess at it.
|
|
226
|
+
*/
|
|
227
|
+
export function planStamp(v, req) {
|
|
228
|
+
// Two passes on purpose: `content_digest` hashes the body BELOW the
|
|
229
|
+
// frontmatter, and withFrontmatterFields promises that body byte-identical —
|
|
230
|
+
// so hashing after the first stamp and writing the hash in a second one
|
|
231
|
+
// yields a digest that is true of the file exactly as written. A re-vouch
|
|
232
|
+
// takes the same road and refreshes every field, this one included.
|
|
233
|
+
//
|
|
234
|
+
// The scope this file's stamp will carry — null both when the run is an
|
|
235
|
+
// ordinary vouch and when a sampled run's `--sample <n>` covered the whole
|
|
236
|
+
// file. Joined to the plan by FILENAME rather than taken positionally: the
|
|
237
|
+
// two axes are stamped by two separate calls, and a scope written onto the
|
|
238
|
+
// wrong file would claim a person read four sections of a document they
|
|
239
|
+
// never opened.
|
|
240
|
+
const planned = req.sample?.axes.find((axis) => axis.file === v.file);
|
|
241
|
+
// A verified file with no planned axis is unreachable — `sampleDrift`
|
|
242
|
+
// refuses that run before this line — and null is the safe direction if it
|
|
243
|
+
// ever were: a full stamp that CLEARS any prior scope, never one claiming a
|
|
244
|
+
// sample nobody was shown.
|
|
245
|
+
const scope = planned === undefined ? null : axisScope(planned);
|
|
246
|
+
const restamped = withFrontmatterFields(v.raw, {
|
|
247
|
+
status: "verified",
|
|
248
|
+
last_verified: req.today,
|
|
249
|
+
// WHO, beside when. Without it `status: verified` recorded that the word
|
|
250
|
+
// had been written and nothing about who wrote it, so a vouch and an
|
|
251
|
+
// agent's own draft left the same trace in the document — the one
|
|
252
|
+
// distinction this command exists to make.
|
|
253
|
+
vouched_by: req.vouchedBy,
|
|
254
|
+
sources_digest: v.digest,
|
|
255
|
+
// Beside the digest, what it was taken over. `sources_digest` alone can
|
|
256
|
+
// only ever say THAT the code moved; the next `loam validate` reads this
|
|
257
|
+
// back to say which files did.
|
|
258
|
+
sources_files: encodeSourceIndex(v.index),
|
|
259
|
+
// Beside `vouched_by`, never inside `status`: the rung string `vouched`
|
|
260
|
+
// and the status `verified` are a published contract, and a sampled read
|
|
261
|
+
// is not a fourth status — it is a qualification of this one.
|
|
262
|
+
...(scope === null ? {} : { vouch_scope: encodeVouchScope(scope) }),
|
|
263
|
+
},
|
|
264
|
+
// A full vouch CLEARS any scope a previous sampled one left, rather than
|
|
265
|
+
// stranding a claim that would keep a fully-read document reading as
|
|
266
|
+
// partial forever. Removing a key that was never there is a no-op, so
|
|
267
|
+
// this rides on every run that is not itself sampling.
|
|
268
|
+
scope === null ? ["vouch_scope"] : []);
|
|
269
|
+
const bodyDigest = contentDigest(restamped);
|
|
270
|
+
return {
|
|
271
|
+
write: { path: v.path, content: withFrontmatterFields(restamped, { content_digest: bodyDigest }) },
|
|
272
|
+
stamped: {
|
|
273
|
+
path: v.path,
|
|
274
|
+
file: v.file,
|
|
275
|
+
digest: v.digest,
|
|
276
|
+
contentDigest: bodyDigest,
|
|
277
|
+
sources: v.sources,
|
|
278
|
+
files: v.index.length,
|
|
279
|
+
skipped: v.skipped,
|
|
280
|
+
vouchScope: scope,
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/** A spec-axis file whose sources all check out, carrying what the stamp needs. */
|
|
285
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/commands/vouch/run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The stamp itself: verify every spec, then write them all or none.\n *\n * Separate from the command because this half has nothing to say to a terminal.\n * It refuses on any spec it cannot verify and stamps none of them — a vouch is\n * a person's claim that the documents match the code, and a partial one would\n * put that claim on files nobody checked.\n *\n * What lands in each file: `status`, `last_verified`, `vouched_by`,\n * `sources_digest`, the `sources_files` index, `content_digest` — and\n * `vouch_scope` when the person read a sample rather than the document — with\n * every byte below the frontmatter left identical. Nothing is written unless\n * all of it can be stamped truthfully for EVERY file: a half-stamp (verified\n * with no digest behind it, or one file stamped and its sibling not) is\n * exactly the claim this command exists to stop being possible, so every\n * present file is verified before any is written, and the writing itself is\n * staged and swapped in like archive's merge, so a failure between the pair's\n * two writes rolls the first back instead of leaving it. The two digests are\n * the two halves of one promise — `sources_digest` pins the code that was\n * read, `content_digest` pins the words it was read against — so\n * `loam validate` can see either side move. (This paragraph came from a\n * docblock stranded at the foot of `vouch.ts` when the stamp moved down here.)\n */\nimport { existsSync } from \"node:fs\";\nimport { withFrontmatterFields } from \"../../core/document/frontmatter.js\";\nimport { encodeVouchScope } from \"../../core/provenance/sample/scope.js\";\nimport { contentDigest, encodeSourceIndex } from \"../../core/provenance/stamp.js\";\nimport { SPEC_AXES } from \"../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../core/repo/service-target.js\";\nimport { rollbackStaged, stageWrites } from \"../../core/staging/commit.js\";\nimport { join } from \"node:path\";\nimport { COMMIT_INTENT, type CommitRecovery, InterruptedCommitError } from \"../../core/staging/interrupted.js\";\nimport { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from \"../../core/staging/lock.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport { type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { type StampedSpec, type VouchOutcome, type VouchRequest } from \"./contract.js\";\nimport { axisScope, sampleDrift } from \"./sample/plan.js\";\nimport { noLivingSpecMessage, verifySpec, type VerifiedSpec } from \"./vet/verify.js\";\n\nexport async function vouch(req: VouchRequest): Promise<VouchOutcome> {\n // A predecessor's journal is rolled forward BEFORE verification reads a\n // byte. Recovery under the commit lock alone was tried first and kept as\n // the backstop, but it changes files verification has already read, so the\n // run's own raced check then refused a sound stamp and the fix doctor\n // prints took two runs instead of one. Cheap when there is nothing to do:\n // one existsSync.\n let recoveredEarly: CommitRecovery | null = null;\n if (existsSync(join(req.docsDir, COMMIT_INTENT))) {\n let release: () => Promise<void>;\n try {\n release = await acquireDocsLockWaiting(req.docsDir, LOCK_WAIT_MS);\n } catch (err) {\n if (err instanceof DocsBusyError) return { ok: false, code: \"docs-busy\", message: err.message };\n throw err;\n }\n try {\n recoveredEarly = await recoverInterruptedCommit(req.docsDir);\n } catch (err) {\n if (err instanceof InterruptedCommitError) return { ok: false, code: \"commit-interrupted\", message: err.message };\n throw err;\n } finally {\n await release();\n }\n }\n const paths = await locateServicePaths(req.docsDir, req.service);\n if (!existsSync(paths.spec)) {\n return {\n ok: false,\n code: \"unknown-target\",\n message: noLivingSpecMessage(paths.spec, req.service),\n };\n }\n\n // Verify first, stamp after: spec.md is required (checked above), arch.spec.md\n // rides when it exists, and one file that cannot be verified refuses the run\n // before anything is written. The two axes are walked by name rather than\n // accumulated in a loop, because everything downstream needs to know WHICH\n // file it is holding — the required one or the optional one — and a loop can\n // only say that positionally.\n const [specAxis, archAxis] = SPEC_AXES;\n const specVerified = await verifySpec(req, paths[specAxis.key], specAxis.file);\n if (!specVerified.ok) return specVerified;\n let archVerified: VerifiedSpec | null = null;\n if (existsSync(paths[archAxis.key])) {\n const outcome = await verifySpec(req, paths[archAxis.key], archAxis.file);\n if (!outcome.ok) return outcome;\n archVerified = outcome;\n }\n const verified: VerifiedSpec[] = [specVerified, ...(archVerified === null ? [] : [archVerified])];\n\n // The READING window's race, which the commit window's byte compare cannot\n // see. Under `--sample` a person spends minutes on the list before they\n // answer, and the sample they were shown was chosen from digests taken\n // before that. If either digest moved — or a second axis file appeared —\n // the sections they read are not the sections this stamp would claim, and\n // `vouch_scope` would record a read that did not happen. Nothing is written.\n const raced = req.sample === undefined ? null : sampleDrift(req.sample, verified, req.service);\n if (raced !== null) return { ok: false, code: \"vouch-raced\", message: raced };\n\n const specPlan = planStamp(specVerified, req);\n const archPlan = archVerified === null ? null : planStamp(archVerified, req);\n const writes: PlannedWrite[] = [specPlan.write, ...(archPlan === null ? [] : [archPlan.write])];\n\n // The slow half — reading specs, hashing sources, composing stamps — ran\n // UNLOCKED above, so vouches for independent services do not serialise\n // behind one repo-wide mutex. Only the commit window takes the docs lock:\n // the same one archive, rebase and verify --record hold, in the waiting\n // form, because two vouches for different services are both supposed to\n // land. This is the lock the roadmap called missing: without it a vouch\n // could stage against bytes an archive was mid-swap over, and the byte\n // compare below would refuse a perfectly sound stamp — or worse, an\n // interrupted commit's journal would be silently written over.\n let releaseLock: () => Promise<void>;\n try {\n releaseLock = await acquireDocsLockWaiting(req.docsDir, LOCK_WAIT_MS);\n } catch (err) {\n if (err instanceof DocsBusyError) return { ok: false, code: \"docs-busy\", message: err.message };\n throw err;\n }\n try {\n // A journal under the lock means the last writer never finished; recover\n // or refuse before reading the pre-images this commit will compare.\n let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(req.docsDir);\n } catch (err) {\n if (err instanceof InterruptedCommitError) return { ok: false, code: \"commit-interrupted\", message: err.message };\n throw err;\n }\n return await commitStamp(req, { writes, specPlan, archPlan, verified }, recovered ?? recoveredEarly);\n } finally {\n await releaseLock();\n }\n}\n\n/**\n * The commit window: stage, compare against what verification read, journal,\n * swap. Split from `vouch` so the lock's extent is visible in the code shape —\n * everything in here holds it, nothing above does.\n */\nasync function commitStamp(\n req: VouchRequest,\n plan: {\n writes: PlannedWrite[];\n specPlan: { write: PlannedWrite; stamped: StampedSpec };\n archPlan: { write: PlannedWrite; stamped: StampedSpec } | null;\n verified: VerifiedSpec[];\n },\n recovered: CommitRecovery | null,\n): Promise<VouchOutcome> {\n const { writes, specPlan, archPlan, verified } = plan;\n const staged = await stageWrites(writes);\n\n // One shared docs repo, ten service repos, and nothing stopping two of them\n // from vouching at once. Between reading a spec and swapping the stamp in\n // there is a window in which somebody else's vouch (or an editor, or a merge)\n // can land in the same file — and because the new bytes were computed from\n // the OLD ones, swapping them in would take that stamp back out without a\n // word. `stageWrites` has already read what is on disk right now, so the\n // check is a comparison, not another read: if the file is not what was\n // verified, this run is describing a document that no longer exists.\n // Bytes, not text: `stageWrites` reads the file as a Buffer (a string\n // comparison against it is never equal, which made every vouch report\n // `vouch-raced` and stamp nothing). A file that is GONE is still a race —\n // `null` is not \"empty\", it is \"the document this run described is not\n // there any more\".\n // Joined by path, not by index: pairing the two lists positionally assumed\n // `stageWrites` returns one entry per planned write in order — true today,\n // promised nowhere, and a compare-and-set that silently compares the wrong\n // pair would stamp over somebody else's vouch. A staged write no verified\n // file claims has nothing to be compared against, so it counts as raced;\n // fail-closed is the only safe direction here.\n const verifiedByPath = new Map(verified.map((v) => [v.path, v] as const));\n const raced = staged.filter((s) => {\n const v = verifiedByPath.get(s.write.path);\n return v === undefined || s.before === null || !s.before.equals(Buffer.from(v.raw, \"utf8\"));\n });\n if (raced.length > 0) {\n // Nothing has swapped yet, so the rollback is only the temp files going\n // away — the other writer's stamp is left exactly as it landed.\n await rollbackStaged(staged);\n return {\n ok: false,\n code: \"vouch-raced\",\n message:\n `${req.service}: ${raced.map((s) => s.write.path).join(\", \")} changed while this vouch was running — ` +\n `another vouch or an edit landed first. Nothing was stamped: re-read the document and re-run.`,\n };\n }\n\n // The journaled commit: intent fsynced before the first rename, so a kill\n // between the pair's two swaps is recoverable instead of invisible — the\n // exact half-stamped state the all-or-nothing verification exists to rule\n // out used to be reachable through that one window. The codes are archive's,\n // reused rather than minted: rolled back → nothing changed, re-running can\n // work; incomplete → the files listed need a human.\n const committed = await commitStaged(\n {\n root: req.docsDir,\n command: \"vouch\",\n // Runnable where vouch runs — the SERVICE repo, with --yes because the\n // journal's reader may be unattended. Any other journaled docs-repo\n // writer recovers this journal on its next run too.\n rerun: `loam vouch --service ${req.service} --yes`,\n target: req.service,\n },\n staged,\n \"stamped\",\n );\n if (!committed.ok) {\n return committed.raced\n ? {\n ok: false,\n code: \"vouch-raced\",\n message:\n `${req.service}: a document changed while this vouch was committing — ` +\n `another writer landed first. Nothing was stamped: re-read and re-run.`,\n }\n : { ok: false, code: committed.code, message: committed.message };\n }\n return {\n ok: true,\n status: \"verified\",\n lastVerified: req.today,\n vouchedBy: req.vouchedBy,\n recovered,\n stamped: { spec: specPlan.stamped, archSpec: archPlan === null ? null : archPlan.stamped },\n };\n}\n\n/**\n * One verified file's share of the commit: the bytes to write, and the record\n * of what was stamped into them. Kept beside each other because they are the\n * same computation — the report must describe the file that was actually\n * written, not a second guess at it.\n */\nexport function planStamp(v: VerifiedSpec, req: VouchRequest): { write: PlannedWrite; stamped: StampedSpec } {\n // Two passes on purpose: `content_digest` hashes the body BELOW the\n // frontmatter, and withFrontmatterFields promises that body byte-identical —\n // so hashing after the first stamp and writing the hash in a second one\n // yields a digest that is true of the file exactly as written. A re-vouch\n // takes the same road and refreshes every field, this one included.\n //\n // The scope this file's stamp will carry — null both when the run is an\n // ordinary vouch and when a sampled run's `--sample <n>` covered the whole\n // file. Joined to the plan by FILENAME rather than taken positionally: the\n // two axes are stamped by two separate calls, and a scope written onto the\n // wrong file would claim a person read four sections of a document they\n // never opened.\n const planned = req.sample?.axes.find((axis) => axis.file === v.file);\n // A verified file with no planned axis is unreachable — `sampleDrift`\n // refuses that run before this line — and null is the safe direction if it\n // ever were: a full stamp that CLEARS any prior scope, never one claiming a\n // sample nobody was shown.\n const scope = planned === undefined ? null : axisScope(planned);\n const restamped = withFrontmatterFields(\n v.raw,\n {\n status: \"verified\",\n last_verified: req.today,\n // WHO, beside when. Without it `status: verified` recorded that the word\n // had been written and nothing about who wrote it, so a vouch and an\n // agent's own draft left the same trace in the document — the one\n // distinction this command exists to make.\n vouched_by: req.vouchedBy,\n sources_digest: v.digest,\n // Beside the digest, what it was taken over. `sources_digest` alone can\n // only ever say THAT the code moved; the next `loam validate` reads this\n // back to say which files did.\n sources_files: encodeSourceIndex(v.index),\n // Beside `vouched_by`, never inside `status`: the rung string `vouched`\n // and the status `verified` are a published contract, and a sampled read\n // is not a fourth status — it is a qualification of this one.\n ...(scope === null ? {} : { vouch_scope: encodeVouchScope(scope) }),\n },\n // A full vouch CLEARS any scope a previous sampled one left, rather than\n // stranding a claim that would keep a fully-read document reading as\n // partial forever. Removing a key that was never there is a no-op, so\n // this rides on every run that is not itself sampling.\n scope === null ? [\"vouch_scope\"] : [],\n );\n const bodyDigest = contentDigest(restamped);\n return {\n write: { path: v.path, content: withFrontmatterFields(restamped, { content_digest: bodyDigest }) },\n stamped: {\n path: v.path,\n file: v.file,\n digest: v.digest,\n contentDigest: bodyDigest,\n sources: v.sources,\n files: v.index.length,\n skipped: v.skipped,\n vouchScope: scope,\n },\n };\n}\n\n/** A spec-axis file whose sources all check out, carrying what the stamp needs. */\n"]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pre-pass behind `loam vouch --sample <n>`: decide, before anyone is
|
|
3
|
+
* asked anything, which sections of which files this vouch will actually
|
|
4
|
+
* cover.
|
|
5
|
+
*
|
|
6
|
+
* It has to happen before the confirmation because a sampled vouch's question
|
|
7
|
+
* is different in kind — "have you read THESE four sections", not "have you
|
|
8
|
+
* read the code" — and a question that names nothing is the reflex this
|
|
9
|
+
* command exists to interrupt. So the reading list is computed first, printed,
|
|
10
|
+
* and only then is the person asked.
|
|
11
|
+
*
|
|
12
|
+
* It runs the vouch's own vetting (`../vet/verify.ts`, the same call
|
|
13
|
+
* `run.ts` and the reading pack make), which means every refusal a vouch can
|
|
14
|
+
* raise about a document — an unreadable header, no `sources`, a glob
|
|
15
|
+
* pattern, a source that is gone, an expansion covering no files — fires HERE,
|
|
16
|
+
* before the prompt, with the same code and the same words. A person should
|
|
17
|
+
* not read four sections and then be told the run was never going to stamp.
|
|
18
|
+
*
|
|
19
|
+
* The cost is that the sources are walked and hashed twice: once to seed the
|
|
20
|
+
* sample, once at the stamp. That is the honesty price of the seed being
|
|
21
|
+
* content-derived, and `run.ts` spends the second walk anyway — what the
|
|
22
|
+
* second walk buys is the drift check, which refuses a stamp whose sample was
|
|
23
|
+
* chosen from a document that has since moved.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import { rawBody } from "../../../core/document/frontmatter.js";
|
|
27
|
+
import { planSample } from "../../../core/provenance/sample/scope.js";
|
|
28
|
+
import { contentDigest } from "../../../core/provenance/stamp.js";
|
|
29
|
+
import { SPEC_AXES } from "../../../core/repo/paths.js";
|
|
30
|
+
import { locateServicePaths } from "../../../core/repo/service-target.js";
|
|
31
|
+
import { noLivingSpecMessage, verifySpec } from "../vet/verify.js";
|
|
32
|
+
/** The scope a planned axis will stamp, or null for one that was read in full. */
|
|
33
|
+
export function axisScope(axis) {
|
|
34
|
+
return axis.mode === "full" ? null : { sections: axis.picked.length, of: axis.of, seed: axis.seed };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* What moved between the reading list and the stamp, as a phrase for the
|
|
38
|
+
* refusal — or null when the plan and the fresh verification describe the same
|
|
39
|
+
* documents.
|
|
40
|
+
*
|
|
41
|
+
* Joined by FILENAME both ways, and both directions matter: a plan entry with
|
|
42
|
+
* no verified file means an axis vanished mid-read, a verified file with no
|
|
43
|
+
* plan entry means one appeared and nobody was shown it. Comparing counts, or
|
|
44
|
+
* zipping the two lists, would call the second case fine.
|
|
45
|
+
*
|
|
46
|
+
* The digests, not the bytes: `content_digest` and `sources_digest` are the
|
|
47
|
+
* seed's own inputs, so a change in either is exactly a change in which
|
|
48
|
+
* sections a sample would pick. It lives here rather than in `run.ts` because
|
|
49
|
+
* it is a question about the PLAN — whether the reading list still describes
|
|
50
|
+
* these files — and the answer has to be computed the same way the plan was.
|
|
51
|
+
*/
|
|
52
|
+
export function sampleDrift(plan, verified, service) {
|
|
53
|
+
const now = new Map(verified.map((v) => [v.file, v]));
|
|
54
|
+
const moved = plan.axes
|
|
55
|
+
.filter((axis) => {
|
|
56
|
+
const v = now.get(axis.file);
|
|
57
|
+
return v === undefined || contentDigest(v.raw) !== axis.contentDigest || v.digest !== axis.sourcesDigest;
|
|
58
|
+
})
|
|
59
|
+
.map((axis) => axis.file);
|
|
60
|
+
const planned = new Set(plan.axes.map((axis) => axis.file));
|
|
61
|
+
const appeared = verified.filter((v) => !planned.has(v.file)).map((v) => v.file);
|
|
62
|
+
const files = [...moved, ...appeared];
|
|
63
|
+
if (files.length === 0)
|
|
64
|
+
return null;
|
|
65
|
+
// The whole sentence, not the file list: `run.ts` returns it verbatim under
|
|
66
|
+
// `vouch-raced`, and the diagnosis — which is about a READING window nothing
|
|
67
|
+
// locks — belongs beside the check that can explain it.
|
|
68
|
+
return (`${service}: ${files.join(", ")} changed while you were reading the sample — the sections you were shown are ` +
|
|
69
|
+
`not the sections a stamp would now cover. Nothing was stamped: re-run for a fresh reading list.`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build the reading list. Same axis walk as `run.ts` — spec.md required,
|
|
73
|
+
* arch.spec.md when it exists — and the same all-or-nothing discipline: one
|
|
74
|
+
* file that cannot be verified refuses the run before a person is asked to
|
|
75
|
+
* read anything.
|
|
76
|
+
*/
|
|
77
|
+
export async function buildSamplePlan(req) {
|
|
78
|
+
const paths = await locateServicePaths(req.docsDir, req.service);
|
|
79
|
+
if (!existsSync(paths.spec)) {
|
|
80
|
+
// One sentence, from `vet/verify.ts`: the same absence and the same fix,
|
|
81
|
+
// and three spellings of it would read as three different problems.
|
|
82
|
+
return { ok: false, code: "unknown-target", message: noLivingSpecMessage(paths.spec, req.service) };
|
|
83
|
+
}
|
|
84
|
+
const axes = [];
|
|
85
|
+
for (const axis of SPEC_AXES) {
|
|
86
|
+
const path = paths[axis.key];
|
|
87
|
+
if (!existsSync(path))
|
|
88
|
+
continue;
|
|
89
|
+
// Sequential, not Promise.all: each iteration hashes a whole source tree
|
|
90
|
+
// through the batched digest, and two of those in flight at once doubles
|
|
91
|
+
// the file descriptors that batching exists to bound. Two files.
|
|
92
|
+
const vetted = await verifySpec({ service: req.service, repoDir: req.repoDir }, path, axis.file);
|
|
93
|
+
if (!vetted.ok)
|
|
94
|
+
return vetted;
|
|
95
|
+
const digest = contentDigest(vetted.raw);
|
|
96
|
+
// The BODY, so no frontmatter line can be picked as a section — and the
|
|
97
|
+
// same slice `content_digest` is taken over, which is what makes the
|
|
98
|
+
// seed's inputs and the sampled text describe one document.
|
|
99
|
+
const body = rawBody(vetted.raw);
|
|
100
|
+
const sample = planSample({
|
|
101
|
+
service: req.service,
|
|
102
|
+
contentDigest: digest,
|
|
103
|
+
sourcesDigest: vetted.digest,
|
|
104
|
+
body,
|
|
105
|
+
n: req.n,
|
|
106
|
+
});
|
|
107
|
+
const base = {
|
|
108
|
+
file: axis.file,
|
|
109
|
+
path,
|
|
110
|
+
contentDigest: digest,
|
|
111
|
+
sourcesDigest: vetted.digest,
|
|
112
|
+
of: sample.of,
|
|
113
|
+
// The header's line count, by subtraction rather than a second parse:
|
|
114
|
+
// `rawBody` is the one definition of where the body starts, and asking
|
|
115
|
+
// it twice is how the two answers drift.
|
|
116
|
+
lineOffset: vetted.raw.slice(0, vetted.raw.length - body.length).split("\n").length - 1,
|
|
117
|
+
sources: { entries: vetted.sources, files: vetted.index.length },
|
|
118
|
+
};
|
|
119
|
+
axes.push(sample.picked.length >= sample.of
|
|
120
|
+
? { ...base, mode: "full" }
|
|
121
|
+
: { ...base, mode: "sampled", seed: sample.seed, picked: sample.picked });
|
|
122
|
+
}
|
|
123
|
+
return { ok: true, plan: { n: req.n, axes } };
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../src/commands/vouch/sample/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The pre-pass behind `loam vouch --sample <n>`: decide, before anyone is\n * asked anything, which sections of which files this vouch will actually\n * cover.\n *\n * It has to happen before the confirmation because a sampled vouch's question\n * is different in kind — \"have you read THESE four sections\", not \"have you\n * read the code\" — and a question that names nothing is the reflex this\n * command exists to interrupt. So the reading list is computed first, printed,\n * and only then is the person asked.\n *\n * It runs the vouch's own vetting (`../vet/verify.ts`, the same call\n * `run.ts` and the reading pack make), which means every refusal a vouch can\n * raise about a document — an unreadable header, no `sources`, a glob\n * pattern, a source that is gone, an expansion covering no files — fires HERE,\n * before the prompt, with the same code and the same words. A person should\n * not read four sections and then be told the run was never going to stamp.\n *\n * The cost is that the sources are walked and hashed twice: once to seed the\n * sample, once at the stamp. That is the honesty price of the seed being\n * content-derived, and `run.ts` spends the second walk anyway — what the\n * second walk buys is the drift check, which refuses a stamp whose sample was\n * chosen from a document that has since moved.\n */\nimport { existsSync } from \"node:fs\";\nimport { rawBody } from \"../../../core/document/frontmatter.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport type { PathableService } from \"../../../core/kernel/ids/service.js\";\nimport type { DocSection } from \"../../../core/provenance/sample/sections.js\";\nimport { planSample, type VouchScope } from \"../../../core/provenance/sample/scope.js\";\nimport { contentDigest } from \"../../../core/provenance/stamp.js\";\nimport { SPEC_AXES } from \"../../../core/repo/paths.js\";\nimport { locateServicePaths } from \"../../../core/repo/service-target.js\";\nimport { noLivingSpecMessage, verifySpec, type SpecRefusal, type VerifiedSpec } from \"../vet/verify.js\";\n\n/**\n * One spec-axis file's share of a `--sample <n>` run, as the pre-pass decided\n * it and the stamp must honour it.\n *\n * A variant rather than an optional `picked?:`, because \"read in full\" and\n * \"sampled\" are two different claims and the difference decides whether the\n * file gets a `vouch_scope` at all. The two digests ride on both arms: they\n * are what the seed was derived from, and the stamp re-checks them against a\n * fresh verification, because a person reads for minutes and nothing locks\n * the document while they do.\n */\ninterface PlannedAxisBase {\n /** The axis's filename — \"spec.md\" or \"arch.spec.md\". The key the stamp joins on. */\n file: string;\n /** Absolute path, for the reading list's own line. */\n path: string;\n /** `contentDigest(raw)` when the reading list was built. */\n contentDigest: string;\n /** The sources digest when the reading list was built. */\n sourcesDigest: string;\n /** Every section the body had. */\n of: number;\n /**\n * How many lines of frontmatter sit above the body. `DocSection.line` is\n * counted within the BODY — that is what the splitter can promise, since it\n * is handed a body — and a reading list that printed those numbers as file\n * lines would send a person to the wrong place in every stamped document.\n * Added once, at the display edge.\n */\n lineOffset: number;\n /** The `sources` entries as the frontmatter spells them, and how many files they expanded to. */\n sources: { entries: string[]; files: number };\n}\n\nexport type PlannedAxis =\n /** `of <= n`: the sample covers the whole file, so this axis is an ordinary full vouch. */\n | (PlannedAxisBase & { mode: \"full\" })\n | (PlannedAxisBase & { mode: \"sampled\"; seed: string; picked: readonly DocSection[] });\n\nexport interface SamplePlan {\n /** `--sample <n>` as the person typed it — carried so the output can say what was asked for. */\n n: number;\n /**\n * Every spec-axis file the pre-pass verified, sampled or not. Complete on\n * purpose: the stamp compares this roster against what it re-verifies, so an\n * arch.spec.md that appeared while the person was reading refuses the run\n * instead of riding along unread.\n */\n axes: PlannedAxis[];\n}\n\n/** The scope a planned axis will stamp, or null for one that was read in full. */\nexport function axisScope(axis: PlannedAxis): VouchScope | null {\n return axis.mode === \"full\" ? null : { sections: axis.picked.length, of: axis.of, seed: axis.seed };\n}\n\n/**\n * What moved between the reading list and the stamp, as a phrase for the\n * refusal — or null when the plan and the fresh verification describe the same\n * documents.\n *\n * Joined by FILENAME both ways, and both directions matter: a plan entry with\n * no verified file means an axis vanished mid-read, a verified file with no\n * plan entry means one appeared and nobody was shown it. Comparing counts, or\n * zipping the two lists, would call the second case fine.\n *\n * The digests, not the bytes: `content_digest` and `sources_digest` are the\n * seed's own inputs, so a change in either is exactly a change in which\n * sections a sample would pick. It lives here rather than in `run.ts` because\n * it is a question about the PLAN — whether the reading list still describes\n * these files — and the answer has to be computed the same way the plan was.\n */\nexport function sampleDrift(\n plan: SamplePlan,\n verified: readonly VerifiedSpec[],\n service: string,\n): string | null {\n const now = new Map(verified.map((v) => [v.file, v] as const));\n const moved = plan.axes\n .filter((axis) => {\n const v = now.get(axis.file);\n return v === undefined || contentDigest(v.raw) !== axis.contentDigest || v.digest !== axis.sourcesDigest;\n })\n .map((axis) => axis.file);\n const planned = new Set(plan.axes.map((axis) => axis.file));\n const appeared = verified.filter((v) => !planned.has(v.file)).map((v) => v.file);\n const files = [...moved, ...appeared];\n if (files.length === 0) return null;\n // The whole sentence, not the file list: `run.ts` returns it verbatim under\n // `vouch-raced`, and the diagnosis — which is about a READING window nothing\n // locks — belongs beside the check that can explain it.\n return (\n `${service}: ${files.join(\", \")} changed while you were reading the sample — the sections you were shown are ` +\n `not the sections a stamp would now cover. Nothing was stamped: re-run for a fresh reading list.`\n );\n}\n\nexport interface SampleRequest {\n docsDir: DocsDir;\n service: PathableService;\n /** The service's own repo — what `sources` resolve against. */\n repoDir: string;\n /** How many sections to read per spec-axis file. A whole number of at least 1; the caller validates. */\n n: number;\n}\n\nexport type SampleOutcome =\n | { ok: true; plan: SamplePlan }\n | { ok: false; code: SpecRefusal[\"code\"] | \"unknown-target\"; message: string };\n\n/**\n * Build the reading list. Same axis walk as `run.ts` — spec.md required,\n * arch.spec.md when it exists — and the same all-or-nothing discipline: one\n * file that cannot be verified refuses the run before a person is asked to\n * read anything.\n */\nexport async function buildSamplePlan(req: SampleRequest): Promise<SampleOutcome> {\n const paths = await locateServicePaths(req.docsDir, req.service);\n if (!existsSync(paths.spec)) {\n // One sentence, from `vet/verify.ts`: the same absence and the same fix,\n // and three spellings of it would read as three different problems.\n return { ok: false, code: \"unknown-target\", message: noLivingSpecMessage(paths.spec, req.service) };\n }\n const axes: PlannedAxis[] = [];\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n if (!existsSync(path)) continue;\n // Sequential, not Promise.all: each iteration hashes a whole source tree\n // through the batched digest, and two of those in flight at once doubles\n // the file descriptors that batching exists to bound. Two files.\n const vetted = await verifySpec({ service: req.service, repoDir: req.repoDir }, path, axis.file);\n if (!vetted.ok) return vetted;\n const digest = contentDigest(vetted.raw);\n // The BODY, so no frontmatter line can be picked as a section — and the\n // same slice `content_digest` is taken over, which is what makes the\n // seed's inputs and the sampled text describe one document.\n const body = rawBody(vetted.raw);\n const sample = planSample({\n service: req.service,\n contentDigest: digest,\n sourcesDigest: vetted.digest,\n body,\n n: req.n,\n });\n const base = {\n file: axis.file,\n path,\n contentDigest: digest,\n sourcesDigest: vetted.digest,\n of: sample.of,\n // The header's line count, by subtraction rather than a second parse:\n // `rawBody` is the one definition of where the body starts, and asking\n // it twice is how the two answers drift.\n lineOffset: vetted.raw.slice(0, vetted.raw.length - body.length).split(\"\\n\").length - 1,\n sources: { entries: vetted.sources, files: vetted.index.length },\n };\n axes.push(\n sample.picked.length >= sample.of\n ? { ...base, mode: \"full\" }\n : { ...base, mode: \"sampled\", seed: sample.seed, picked: sample.picked },\n );\n }\n return { ok: true, plan: { n: req.n, axes } };\n}\n"]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a person is shown before they answer a sampled vouch's question, and
|
|
3
|
+
* the additive `--json` shape of what it stamped.
|
|
4
|
+
*
|
|
5
|
+
* The reading list is the whole interface of `--sample <n>`: the sections
|
|
6
|
+
* picked, where they start, the preamble that is always read, and — said
|
|
7
|
+
* plainly, before the prompt rather than after the stamp — that the sections
|
|
8
|
+
* NOT listed will have been read by nobody, and what every later surface will
|
|
9
|
+
* say about that. A flag that quietly reduces assurance would be a bad trade;
|
|
10
|
+
* one that states the reduction to the person making it is the trade this
|
|
11
|
+
* feature is.
|
|
12
|
+
*
|
|
13
|
+
* Kept out of `vouch.ts` because that file is the flag boundary and is at its
|
|
14
|
+
* line limit, and out of `plan.ts` because a derivation that prints cannot be
|
|
15
|
+
* called by the reading pack, which renders the same sample its own way.
|
|
16
|
+
*/
|
|
17
|
+
import { plural } from "../../policy/format.js";
|
|
18
|
+
/** `spec.md:42` — a body-relative section line put back on the file it came from. */
|
|
19
|
+
function sectionAt(axis, line) {
|
|
20
|
+
return `${axis.file}:${line + axis.lineOffset}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The sampled claim in one sentence, for the reading list and the prompt: what
|
|
24
|
+
* will be stamped, and what every later reader will therefore say.
|
|
25
|
+
*/
|
|
26
|
+
function claim(axis, service) {
|
|
27
|
+
return (`${axis.file} will be stamped \`vouch_scope: sampled ${axis.picked.length}/${axis.of} seed=${axis.seed}\` — ` +
|
|
28
|
+
`the other ${plural(axis.of - axis.picked.length, "section")} will have been read by nobody. \`loam list\` shows ` +
|
|
29
|
+
`it as \`vouched (sampled)\` and \`loam validate\` reports \`sources.sampled-vouch\` until a person runs a full ` +
|
|
30
|
+
`\`loam vouch --service ${service}\`.`);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The reading list, printed before the confirmation. Text mode only — a
|
|
34
|
+
* `--json` run cannot be asked a question at all (`vouch-unattended`), so
|
|
35
|
+
* there is nobody on the other end of this list.
|
|
36
|
+
*/
|
|
37
|
+
export function printReadingList(plan, service) {
|
|
38
|
+
console.log(`\n${service} — sampled vouch: ${plural(plan.n, "section")} per spec file. Read what is listed here.\n`);
|
|
39
|
+
for (const axis of plan.axes) {
|
|
40
|
+
if (axis.mode === "full") {
|
|
41
|
+
// Said, not skipped: somebody who asked for a sample of four and got the
|
|
42
|
+
// whole document needs to know which of the two they are vouching for.
|
|
43
|
+
console.log(` ${axis.file} — ${plural(axis.of, "section")}, at or under the sample: the whole file is the read, ` +
|
|
44
|
+
`and it is stamped as an ordinary vouch with no scope.\n`);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
console.log(` ${axis.file} — ${axis.picked.length} of ${plural(axis.of, "section")}, seed=${axis.seed}`);
|
|
48
|
+
console.log(` the preamble above the first heading — always read, never part of the count`);
|
|
49
|
+
for (const section of axis.picked)
|
|
50
|
+
console.log(` ${sectionAt(axis, section.line)} ${section.heading}`);
|
|
51
|
+
// The FULL declared sources, deliberately not a sample of them: nothing in
|
|
52
|
+
// loam maps a source file to a doc section, and inventing that mapping
|
|
53
|
+
// ("these files back those sections") is exactly the meaning-derivation
|
|
54
|
+
// this tool refuses to do. The sample is of sections; the digest still
|
|
55
|
+
// covers every source, and so does the claim.
|
|
56
|
+
console.log(` sources — ${axis.sources.entries.length} entr${axis.sources.entries.length === 1 ? "y" : "ies"}, ${plural(axis.sources.files, "file")}, all of them under the stamp:`);
|
|
57
|
+
for (const entry of axis.sources.entries)
|
|
58
|
+
console.log(` ${entry}`);
|
|
59
|
+
console.log("");
|
|
60
|
+
}
|
|
61
|
+
for (const axis of plan.axes)
|
|
62
|
+
if (axis.mode === "sampled")
|
|
63
|
+
console.log(` ${claim(axis, service)}\n`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The sampled half of the confirmation's own text, so the question a person
|
|
67
|
+
* answers names the same sections the list above it did.
|
|
68
|
+
*/
|
|
69
|
+
export function promptClaim(plan, service) {
|
|
70
|
+
// A type predicate, not a bare `.filter`: TypeScript does not narrow a union
|
|
71
|
+
// through `filter`, and the alternative at the two use sites below is a cast
|
|
72
|
+
// or a second impossible branch — both of which are a way to be wrong about
|
|
73
|
+
// which arm this is.
|
|
74
|
+
const sampled = plan.axes.filter((axis) => axis.mode === "sampled");
|
|
75
|
+
if (sampled.length === 0) {
|
|
76
|
+
return ("You asked for a sample, and it covers every section of every file — so this is an ordinary full vouch " +
|
|
77
|
+
"and nothing will record a partial read.");
|
|
78
|
+
}
|
|
79
|
+
return [
|
|
80
|
+
`You are vouching for a SAMPLE: ${sampled
|
|
81
|
+
.map((axis) => `${axis.file} ${axis.picked.length}/${axis.of}`)
|
|
82
|
+
.join(", ")}. Everything else stays unread, and the document will say so.`,
|
|
83
|
+
...sampled.map((axis) => claim(axis, service)),
|
|
84
|
+
].join("\n");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The additive `vouchScope` payload — the stamped scope plus the headings it
|
|
88
|
+
* chose, so a consumer never has to re-derive the sample to find out what was
|
|
89
|
+
* read. Null for a file vouched in full, because that IS the whole answer for
|
|
90
|
+
* one, and a consumer that omitted the key could not tell it from an older
|
|
91
|
+
* loam that never wrote one.
|
|
92
|
+
*/
|
|
93
|
+
export function scopeJson(scope, axis) {
|
|
94
|
+
if (scope === null)
|
|
95
|
+
return null;
|
|
96
|
+
return {
|
|
97
|
+
mode: "sampled",
|
|
98
|
+
sections: scope.sections,
|
|
99
|
+
of: scope.of,
|
|
100
|
+
seed: scope.seed,
|
|
101
|
+
// Joined by file at the call site; an axis that does not answer leaves the
|
|
102
|
+
// headings out rather than inventing them — the seed and the counts are
|
|
103
|
+
// still enough to recompute the list.
|
|
104
|
+
...(axis === undefined || axis.mode !== "sampled"
|
|
105
|
+
? {}
|
|
106
|
+
: { headings: axis.picked.map((section) => section.heading) }),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=print.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"print.js","sourceRoot":"","sources":["../../../../src/commands/vouch/sample/print.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a person is shown before they answer a sampled vouch's question, and\n * the additive `--json` shape of what it stamped.\n *\n * The reading list is the whole interface of `--sample <n>`: the sections\n * picked, where they start, the preamble that is always read, and — said\n * plainly, before the prompt rather than after the stamp — that the sections\n * NOT listed will have been read by nobody, and what every later surface will\n * say about that. A flag that quietly reduces assurance would be a bad trade;\n * one that states the reduction to the person making it is the trade this\n * feature is.\n *\n * Kept out of `vouch.ts` because that file is the flag boundary and is at its\n * line limit, and out of `plan.ts` because a derivation that prints cannot be\n * called by the reading pack, which renders the same sample its own way.\n */\nimport { plural } from \"../../policy/format.js\";\nimport type { VouchScope } from \"../../../core/provenance/sample/scope.js\";\nimport type { PlannedAxis, SamplePlan } from \"./plan.js\";\n\n/** `spec.md:42` — a body-relative section line put back on the file it came from. */\nfunction sectionAt(axis: PlannedAxis, line: number): string {\n return `${axis.file}:${line + axis.lineOffset}`;\n}\n\n/**\n * The sampled claim in one sentence, for the reading list and the prompt: what\n * will be stamped, and what every later reader will therefore say.\n */\nfunction claim(axis: PlannedAxis & { mode: \"sampled\" }, service: string): string {\n return (\n `${axis.file} will be stamped \\`vouch_scope: sampled ${axis.picked.length}/${axis.of} seed=${axis.seed}\\` — ` +\n `the other ${plural(axis.of - axis.picked.length, \"section\")} will have been read by nobody. \\`loam list\\` shows ` +\n `it as \\`vouched (sampled)\\` and \\`loam validate\\` reports \\`sources.sampled-vouch\\` until a person runs a full ` +\n `\\`loam vouch --service ${service}\\`.`\n );\n}\n\n/**\n * The reading list, printed before the confirmation. Text mode only — a\n * `--json` run cannot be asked a question at all (`vouch-unattended`), so\n * there is nobody on the other end of this list.\n */\nexport function printReadingList(plan: SamplePlan, service: string): void {\n console.log(`\\n${service} — sampled vouch: ${plural(plan.n, \"section\")} per spec file. Read what is listed here.\\n`);\n for (const axis of plan.axes) {\n if (axis.mode === \"full\") {\n // Said, not skipped: somebody who asked for a sample of four and got the\n // whole document needs to know which of the two they are vouching for.\n console.log(\n ` ${axis.file} — ${plural(axis.of, \"section\")}, at or under the sample: the whole file is the read, ` +\n `and it is stamped as an ordinary vouch with no scope.\\n`,\n );\n continue;\n }\n console.log(` ${axis.file} — ${axis.picked.length} of ${plural(axis.of, \"section\")}, seed=${axis.seed}`);\n console.log(` the preamble above the first heading — always read, never part of the count`);\n for (const section of axis.picked) console.log(` ${sectionAt(axis, section.line)} ${section.heading}`);\n // The FULL declared sources, deliberately not a sample of them: nothing in\n // loam maps a source file to a doc section, and inventing that mapping\n // (\"these files back those sections\") is exactly the meaning-derivation\n // this tool refuses to do. The sample is of sections; the digest still\n // covers every source, and so does the claim.\n console.log(\n ` sources — ${axis.sources.entries.length} entr${axis.sources.entries.length === 1 ? \"y\" : \"ies\"}, ${plural(axis.sources.files, \"file\")}, all of them under the stamp:`,\n );\n for (const entry of axis.sources.entries) console.log(` ${entry}`);\n console.log(\"\");\n }\n for (const axis of plan.axes) if (axis.mode === \"sampled\") console.log(` ${claim(axis, service)}\\n`);\n}\n\n/**\n * The sampled half of the confirmation's own text, so the question a person\n * answers names the same sections the list above it did.\n */\nexport function promptClaim(plan: SamplePlan, service: string): string {\n // A type predicate, not a bare `.filter`: TypeScript does not narrow a union\n // through `filter`, and the alternative at the two use sites below is a cast\n // or a second impossible branch — both of which are a way to be wrong about\n // which arm this is.\n const sampled = plan.axes.filter((axis): axis is PlannedAxis & { mode: \"sampled\" } => axis.mode === \"sampled\");\n if (sampled.length === 0) {\n return (\n \"You asked for a sample, and it covers every section of every file — so this is an ordinary full vouch \" +\n \"and nothing will record a partial read.\"\n );\n }\n return [\n `You are vouching for a SAMPLE: ${sampled\n .map((axis) => `${axis.file} ${axis.picked.length}/${axis.of}`)\n .join(\", \")}. Everything else stays unread, and the document will say so.`,\n ...sampled.map((axis) => claim(axis, service)),\n ].join(\"\\n\");\n}\n\n/**\n * The additive `vouchScope` payload — the stamped scope plus the headings it\n * chose, so a consumer never has to re-derive the sample to find out what was\n * read. Null for a file vouched in full, because that IS the whole answer for\n * one, and a consumer that omitted the key could not tell it from an older\n * loam that never wrote one.\n */\nexport function scopeJson(scope: VouchScope | null, axis: PlannedAxis | undefined): Record<string, unknown> | null {\n if (scope === null) return null;\n return {\n mode: \"sampled\",\n sections: scope.sections,\n of: scope.of,\n seed: scope.seed,\n // Joined by file at the call site; an axis that does not answer leaves the\n // headings out rather than inventing them — the seed and the counts are\n // still enough to recompute the list.\n ...(axis === undefined || axis.mode !== \"sampled\"\n ? {}\n : { headings: axis.picked.map((section) => section.heading) }),\n };\n}\n"]}
|