@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2635 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +599 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +323 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +180 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +252 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +204 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +270 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +251 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +251 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +337 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +48 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +174 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +90 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +158 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +187 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +220 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/service.js +159 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +144 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +139 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +174 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +287 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/deployment/delta.js +188 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +137 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +210 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +277 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +196 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +197 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +72 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +58 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract-depth probes: does an operation say anything about what it
|
|
3
|
+
* returns, and does every internal `$ref` point at something.
|
|
4
|
+
*
|
|
5
|
+
* Form validated and depth did not, and that was invisible from the output:
|
|
6
|
+
* an operation whose one response is `description: OK`, and a `$ref` to a
|
|
7
|
+
* schema nobody ever wrote, both validate exactly as cleanly as a complete
|
|
8
|
+
* contract even though a reader still needs the code. Everything here is a
|
|
9
|
+
* PRESENCE probe over the document's own keys: nothing reads what a schema
|
|
10
|
+
* says, only whether one was declared at all, so the reader's structure-only
|
|
11
|
+
* stance holds.
|
|
12
|
+
*
|
|
13
|
+
* A local pointer walk rather than an import from `merge/components.ts`: the merge
|
|
14
|
+
* package already imports this package (`HTTP_METHODS` from doc.ts), so the
|
|
15
|
+
* reverse import would be a package cycle — the same reason read-side
|
|
16
|
+
* `resolvePointer` copies exist on the asyncapi axis.
|
|
17
|
+
*/
|
|
18
|
+
/** Status codes whose response legitimately carries no body. */
|
|
19
|
+
const NO_CONTENT = new Set(["204", "304"]);
|
|
20
|
+
/**
|
|
21
|
+
* True when no response declares content with a schema while at least one
|
|
22
|
+
* response should carry a body — an operation with no `responses` at all
|
|
23
|
+
* counts, one whose only responses are 204/304 does not. A response that is
|
|
24
|
+
* itself a `$ref` counts as described: somebody wrote the shape down, and the
|
|
25
|
+
* dangling case is the ref probe's finding, not this one's.
|
|
26
|
+
*/
|
|
27
|
+
export function responseUndescribed(op) {
|
|
28
|
+
if (op === null || typeof op !== "object")
|
|
29
|
+
return true;
|
|
30
|
+
const responses = op["responses"];
|
|
31
|
+
if (responses === null || responses === undefined || typeof responses !== "object" || Array.isArray(responses)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
const entries = Object.entries(responses);
|
|
35
|
+
if (entries.length === 0)
|
|
36
|
+
return true;
|
|
37
|
+
let owesBody = false;
|
|
38
|
+
for (const [code, resp] of entries) {
|
|
39
|
+
if (NO_CONTENT.has(code))
|
|
40
|
+
continue;
|
|
41
|
+
owesBody = true;
|
|
42
|
+
if (resp === null || typeof resp !== "object" || Array.isArray(resp))
|
|
43
|
+
continue;
|
|
44
|
+
const rec = resp;
|
|
45
|
+
if ("$ref" in rec)
|
|
46
|
+
return false;
|
|
47
|
+
const content = rec["content"];
|
|
48
|
+
if (content === null || typeof content !== "object" || Array.isArray(content))
|
|
49
|
+
continue;
|
|
50
|
+
for (const media of Object.values(content)) {
|
|
51
|
+
if (media !== null && typeof media === "object" && "schema" in media) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return owesBody;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The non-2xx response codes an operation declares, in document order.
|
|
60
|
+
*
|
|
61
|
+
* Exact three-digit codes only. `default` names no case — it is whatever else
|
|
62
|
+
* happens — and the `4XX`/`5XX` wildcards name a class rather than a refusal,
|
|
63
|
+
* so no scenario could be written for either and demanding one would teach an
|
|
64
|
+
* author to write a scenario about nothing. 1xx and 3xx are excluded with 2xx:
|
|
65
|
+
* a redirect or a continue is not the service saying no.
|
|
66
|
+
*/
|
|
67
|
+
export function failureCodesOf(op) {
|
|
68
|
+
if (op === null || typeof op !== "object")
|
|
69
|
+
return [];
|
|
70
|
+
const responses = op["responses"];
|
|
71
|
+
if (responses === null || typeof responses !== "object" || Array.isArray(responses))
|
|
72
|
+
return [];
|
|
73
|
+
return Object.keys(responses).filter((code) => /^[45]\d\d$/.test(code));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Every internal `#/` reference in the document that resolves to nothing —
|
|
77
|
+
* deduped, document order. External references (URLs, file paths) are out of
|
|
78
|
+
* scope and never graded, matching the merge's documented stance.
|
|
79
|
+
*/
|
|
80
|
+
export function danglingRefs(root) {
|
|
81
|
+
const out = new Set();
|
|
82
|
+
const walk = (node) => {
|
|
83
|
+
if (Array.isArray(node)) {
|
|
84
|
+
for (const value of node)
|
|
85
|
+
walk(value);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (node === null || typeof node !== "object")
|
|
89
|
+
return;
|
|
90
|
+
for (const [key, value] of Object.entries(node)) {
|
|
91
|
+
if (key === "$ref" && typeof value === "string") {
|
|
92
|
+
if (value.startsWith("#/") && !resolves(root, value))
|
|
93
|
+
out.add(value);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
walk(value);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
walk(root);
|
|
100
|
+
return [...out];
|
|
101
|
+
}
|
|
102
|
+
/** Does a local JSON pointer (`#/a/b~1c`) reach a node in this tree? */
|
|
103
|
+
function resolves(root, ref) {
|
|
104
|
+
let current = root;
|
|
105
|
+
for (const raw of ref.slice(2).split("/")) {
|
|
106
|
+
const segment = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
107
|
+
if (Array.isArray(current)) {
|
|
108
|
+
const index = Number(segment);
|
|
109
|
+
if (!Number.isInteger(index) || index < 0 || index >= current.length)
|
|
110
|
+
return false;
|
|
111
|
+
current = current[index];
|
|
112
|
+
}
|
|
113
|
+
else if (current !== null && typeof current === "object" && segment in current) {
|
|
114
|
+
current = current[segment];
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=depth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"depth.js","sourceRoot":"","sources":["../../../src/core/openapi/depth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The contract-depth probes: does an operation say anything about what it\n * returns, and does every internal `$ref` point at something.\n *\n * Form validated and depth did not, and that was invisible from the output:\n * an operation whose one response is `description: OK`, and a `$ref` to a\n * schema nobody ever wrote, both validate exactly as cleanly as a complete\n * contract even though a reader still needs the code. Everything here is a\n * PRESENCE probe over the document's own keys: nothing reads what a schema\n * says, only whether one was declared at all, so the reader's structure-only\n * stance holds.\n *\n * A local pointer walk rather than an import from `merge/components.ts`: the merge\n * package already imports this package (`HTTP_METHODS` from doc.ts), so the\n * reverse import would be a package cycle — the same reason read-side\n * `resolvePointer` copies exist on the asyncapi axis.\n */\n\n/** Status codes whose response legitimately carries no body. */\nconst NO_CONTENT = new Set([\"204\", \"304\"]);\n\n/**\n * True when no response declares content with a schema while at least one\n * response should carry a body — an operation with no `responses` at all\n * counts, one whose only responses are 204/304 does not. A response that is\n * itself a `$ref` counts as described: somebody wrote the shape down, and the\n * dangling case is the ref probe's finding, not this one's.\n */\nexport function responseUndescribed(op: unknown): boolean {\n if (op === null || typeof op !== \"object\") return true;\n const responses = (op as Record<string, unknown>)[\"responses\"];\n if (responses === null || responses === undefined || typeof responses !== \"object\" || Array.isArray(responses)) {\n return true;\n }\n const entries = Object.entries(responses as Record<string, unknown>);\n if (entries.length === 0) return true;\n let owesBody = false;\n for (const [code, resp] of entries) {\n if (NO_CONTENT.has(code)) continue;\n owesBody = true;\n if (resp === null || typeof resp !== \"object\" || Array.isArray(resp)) continue;\n const rec = resp as Record<string, unknown>;\n if (\"$ref\" in rec) return false;\n const content = rec[\"content\"];\n if (content === null || typeof content !== \"object\" || Array.isArray(content)) continue;\n for (const media of Object.values(content as Record<string, unknown>)) {\n if (media !== null && typeof media === \"object\" && \"schema\" in (media as Record<string, unknown>)) {\n return false;\n }\n }\n }\n return owesBody;\n}\n\n/**\n * The non-2xx response codes an operation declares, in document order.\n *\n * Exact three-digit codes only. `default` names no case — it is whatever else\n * happens — and the `4XX`/`5XX` wildcards name a class rather than a refusal,\n * so no scenario could be written for either and demanding one would teach an\n * author to write a scenario about nothing. 1xx and 3xx are excluded with 2xx:\n * a redirect or a continue is not the service saying no.\n */\nexport function failureCodesOf(op: unknown): string[] {\n if (op === null || typeof op !== \"object\") return [];\n const responses = (op as Record<string, unknown>)[\"responses\"];\n if (responses === null || typeof responses !== \"object\" || Array.isArray(responses)) return [];\n return Object.keys(responses as Record<string, unknown>).filter((code) => /^[45]\\d\\d$/.test(code));\n}\n\n/**\n * Every internal `#/` reference in the document that resolves to nothing —\n * deduped, document order. External references (URLs, file paths) are out of\n * scope and never graded, matching the merge's documented stance.\n */\nexport function danglingRefs(root: unknown): string[] {\n const out = new Set<string>();\n const walk = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const value of node) walk(value);\n return;\n }\n if (node === null || typeof node !== \"object\") return;\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n if (key === \"$ref\" && typeof value === \"string\") {\n if (value.startsWith(\"#/\") && !resolves(root, value)) out.add(value);\n continue;\n }\n walk(value);\n }\n };\n walk(root);\n return [...out];\n}\n\n/** Does a local JSON pointer (`#/a/b~1c`) reach a node in this tree? */\nfunction resolves(root: unknown, ref: string): boolean {\n let current: unknown = root;\n for (const raw of ref.slice(2).split(\"/\")) {\n const segment = raw.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n if (Array.isArray(current)) {\n const index = Number(segment);\n if (!Number.isInteger(index) || index < 0 || index >= current.length) return false;\n current = current[index];\n } else if (current !== null && typeof current === \"object\" && segment in (current as Record<string, unknown>)) {\n current = (current as Record<string, unknown>)[segment];\n } else {\n return false;\n }\n }\n return true;\n}\n"]}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What operation content HASHES to, and what a recorded hash means when it is
|
|
3
|
+
* read back.
|
|
4
|
+
*
|
|
5
|
+
* A module of its own because the digest and the comparison it must agree with
|
|
6
|
+
* are a matched pair: key order must not matter here, since the merge's own
|
|
7
|
+
* idea of "this operation differs" is `isDeepStrictEqual`, which ignores it. A
|
|
8
|
+
* second spelling of either would go stale over a reordered `summary` pair the
|
|
9
|
+
* merge calls identical — a false collision on the one check whose whole worth
|
|
10
|
+
* is that it fires only for real ones.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
/**
|
|
14
|
+
* The baseline marker: which living version of this operation a FEATURE delta
|
|
15
|
+
* was written against. Same idea as a requirement's `Based-On:` (core/document/spec.ts),
|
|
16
|
+
* in the vendor-extension shape this axis already uses for `x-loam-remove`.
|
|
17
|
+
*
|
|
18
|
+
* This axis needs it MORE than the requirement axis does, because a feature's
|
|
19
|
+
* openapi.yaml is a COMPLETE document rather than a patch: authors restate the
|
|
20
|
+
* living contract around the slot they are changing, and the merge upserts every
|
|
21
|
+
* operation the document spells. So a feature that never meant to touch
|
|
22
|
+
* `refundOrder` — it merely quoted it — pushes its authoring-time copy back over
|
|
23
|
+
* whatever landed in between, reverting another team's shipped change without
|
|
24
|
+
* either feature overlapping the other at all. The pin is what lets the merge
|
|
25
|
+
* tell a QUOTE from an EDIT: a quote equals its own baseline, and is skipped.
|
|
26
|
+
*/
|
|
27
|
+
export const OPENAPI_BASELINE_KEY = "x-loam-based-on";
|
|
28
|
+
/**
|
|
29
|
+
* The feature-only ROOT record that pins the surfaces the operation pin cannot
|
|
30
|
+
* reach: path-item non-method keys and components. `{ pathItems: {<path>:
|
|
31
|
+
* {<key>: <16-hex>}}, components: {"<kind>/<name>": <16-hex>} }` — written by
|
|
32
|
+
* `loam rebase`, graded by validate/archive, consumed by the merge, and
|
|
33
|
+
* stripped from every living document exactly like the pin. A single root
|
|
34
|
+
* record rather than in-value extensions because a component's value can be a
|
|
35
|
+
* bare boolean (JSON Schema allows `true`), which no in-value key survives.
|
|
36
|
+
*/
|
|
37
|
+
export const OPENAPI_BASELINES_KEY = "x-loam-baselines";
|
|
38
|
+
/** The feature-only explicit removal marker's key. */
|
|
39
|
+
export const OPENAPI_REMOVE_KEY = "x-loam-remove";
|
|
40
|
+
/** Is this operation node a feature-only explicit removal marker? */
|
|
41
|
+
export function isRemoval(node) {
|
|
42
|
+
return node !== null &&
|
|
43
|
+
typeof node === "object" &&
|
|
44
|
+
node[OPENAPI_REMOVE_KEY] === true;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Every key that is an instruction to loam rather than part of the contract.
|
|
48
|
+
* At PATH level none has any meaning — `x-loam-remove` addresses one
|
|
49
|
+
* operation and `x-loam-based-on` pins one — so a path item carrying either
|
|
50
|
+
* is an authoring mistake, and one that must not be published whatever else
|
|
51
|
+
* is done about it. Lives HERE, in the openapi root beside the keys it names,
|
|
52
|
+
* because both `merge/` and `baseline/` consume it and a value edge between
|
|
53
|
+
* those two packages in either direction would be a package cycle.
|
|
54
|
+
*/
|
|
55
|
+
export const FEATURE_ONLY_KEYS = new Set([OPENAPI_REMOVE_KEY, OPENAPI_BASELINE_KEY]);
|
|
56
|
+
/**
|
|
57
|
+
* `value` with every feature-only key removed, at ANY depth — or the value
|
|
58
|
+
* unchanged (same reference) when it carries none. The component closure
|
|
59
|
+
* writes values VERBATIM into the living contract, and a component holding an
|
|
60
|
+
* operation shape (a `pathItems` component, a callback) can nest the markers
|
|
61
|
+
* where the path-level strip never walks: `--approve` once published
|
|
62
|
+
* `x-loam-remove: true` inside a living component, invisible even to
|
|
63
|
+
* `validate`, whose marker sweep reads `paths` alone.
|
|
64
|
+
*/
|
|
65
|
+
export function withoutFeatureKeysDeep(value) {
|
|
66
|
+
if (value === null || typeof value !== "object")
|
|
67
|
+
return value;
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
let changed = false;
|
|
70
|
+
const out = value.map((v) => {
|
|
71
|
+
const stripped = withoutFeatureKeysDeep(v);
|
|
72
|
+
if (stripped !== v)
|
|
73
|
+
changed = true;
|
|
74
|
+
return stripped;
|
|
75
|
+
});
|
|
76
|
+
return changed ? out : value;
|
|
77
|
+
}
|
|
78
|
+
const record = value;
|
|
79
|
+
let changed = false;
|
|
80
|
+
const out = {};
|
|
81
|
+
for (const [key, v] of Object.entries(record)) {
|
|
82
|
+
if (FEATURE_ONLY_KEYS.has(key)) {
|
|
83
|
+
changed = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const stripped = withoutFeatureKeysDeep(v);
|
|
87
|
+
if (stripped !== v)
|
|
88
|
+
changed = true;
|
|
89
|
+
out[key] = stripped;
|
|
90
|
+
}
|
|
91
|
+
return changed ? out : value;
|
|
92
|
+
}
|
|
93
|
+
/** How much of the sha256 an `x-loam-based-on` carries — the length every loam digest uses. */
|
|
94
|
+
export const OPERATION_DIGEST_LENGTH = 16;
|
|
95
|
+
/** The value shape of an `x-loam-based-on`, exactly as `operationDigest` writes it. */
|
|
96
|
+
export const OPERATION_DIGEST_RE = new RegExp(`^[0-9a-f]{${OPERATION_DIGEST_LENGTH}}$`);
|
|
97
|
+
/**
|
|
98
|
+
* Canonical JSON for a resolved YAML value: object keys sorted, arrays left in
|
|
99
|
+
* order, everything else as JSON writes it.
|
|
100
|
+
*
|
|
101
|
+
* Key ORDER must not matter, because the merge's own idea of "this operation
|
|
102
|
+
* differs" is `isDeepStrictEqual` (./merge/merge.ts), which ignores it. A
|
|
103
|
+
* digest that disagreed with that comparison would go stale over a reordered
|
|
104
|
+
* `summary`/`operationId` pair the merge itself calls identical — a false
|
|
105
|
+
* collision on the one check whose worth is that it fires only for real ones.
|
|
106
|
+
*
|
|
107
|
+
* Exported for the ASYNCAPI axis (core/asyncapi/digest.ts), whose slot
|
|
108
|
+
* digests are the same rule over different sections: one spelling of the
|
|
109
|
+
* canonical form for both axes, because two spellings would eventually
|
|
110
|
+
* disagree — the exact drift this module's own header names as the failure
|
|
111
|
+
* mode. The import direction is asyncapi → openapi only; nothing under
|
|
112
|
+
* core/openapi/ may import from core/asyncapi/, or the package graph cycles.
|
|
113
|
+
*/
|
|
114
|
+
export function canonicalJson(node) {
|
|
115
|
+
if (node === null || typeof node !== "object")
|
|
116
|
+
return JSON.stringify(node) ?? "null";
|
|
117
|
+
if (Array.isArray(node))
|
|
118
|
+
return `[${node.map(canonicalJson).join(",")}]`;
|
|
119
|
+
const entries = Object.entries(node)
|
|
120
|
+
.filter(([, value]) => value !== undefined)
|
|
121
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
122
|
+
return `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${canonicalJson(value)}`).join(",")}}`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The operation without its own baseline marker — what a digest is taken over,
|
|
126
|
+
* and what the merge writes.
|
|
127
|
+
*
|
|
128
|
+
* A pin is a statement ABOUT a delta, never part of the operation it describes.
|
|
129
|
+
* Inside the digest input, an operation's identity would depend on the pin
|
|
130
|
+
* pointing at it and no baseline could ever be self-consistent; inside the
|
|
131
|
+
* merged contract, a living operation would carry a pin to a version of itself
|
|
132
|
+
* and the next feature's baseline would hash the previous feature's bookkeeping.
|
|
133
|
+
*/
|
|
134
|
+
export function withoutOperationBaseline(node) {
|
|
135
|
+
if (node === null || typeof node !== "object" || Array.isArray(node))
|
|
136
|
+
return node;
|
|
137
|
+
const record = node;
|
|
138
|
+
if (!(OPENAPI_BASELINE_KEY in record))
|
|
139
|
+
return node;
|
|
140
|
+
const rest = { ...record };
|
|
141
|
+
delete rest[OPENAPI_BASELINE_KEY];
|
|
142
|
+
return rest;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The identity of ANY resolved value: sha256 of its canonical form. The one
|
|
146
|
+
* digest the path-item and component baselines carry — order-of-keys blind,
|
|
147
|
+
* array-order sensitive, legal over scalars and booleans, exactly as the
|
|
148
|
+
* operation digest always was.
|
|
149
|
+
*/
|
|
150
|
+
export function valueDigest(node) {
|
|
151
|
+
return createHash("sha256").update(canonicalJson(node), "utf8").digest("hex").slice(0, OPERATION_DIGEST_LENGTH);
|
|
152
|
+
}
|
|
153
|
+
/** The identity of an operation's CONTENT: sha256 of its canonical form, pin excluded. */
|
|
154
|
+
export function operationDigest(node) {
|
|
155
|
+
return valueDigest(withoutOperationBaseline(node));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* What a pin says, from digests alone — the one definition every decider
|
|
159
|
+
* shares: the gate (./baseline/gate.ts) and the merge
|
|
160
|
+
* (./merge/{merge,components,pin}.ts) alike. Two spellings of
|
|
161
|
+
* this rule would eventually disagree about which operations a merge writes,
|
|
162
|
+
* and the disagreement would be invisible until a contract came back wrong.
|
|
163
|
+
*
|
|
164
|
+
* Order matters: `quote` is settled against the delta's OWN content before the
|
|
165
|
+
* living contract is consulted. An operation equal to its own baseline was not
|
|
166
|
+
* edited, so what living holds now is nobody's business but the feature that
|
|
167
|
+
* put it there.
|
|
168
|
+
*/
|
|
169
|
+
export function classifyBaselineDigests(pin, ownDigest, livingDigest) {
|
|
170
|
+
if (pin === undefined)
|
|
171
|
+
return "unpinned";
|
|
172
|
+
if (livingDigest === undefined)
|
|
173
|
+
return "unfounded";
|
|
174
|
+
if (pin === ownDigest)
|
|
175
|
+
return "quote";
|
|
176
|
+
if (pin === livingDigest)
|
|
177
|
+
return "edit";
|
|
178
|
+
return "stale";
|
|
179
|
+
}
|
|
180
|
+
/** The `x-loam-based-on` a node declares, or undefined — non-strings included, so validate can refuse them. */
|
|
181
|
+
export function operationBaselineOf(node) {
|
|
182
|
+
if (node === null || typeof node !== "object" || Array.isArray(node))
|
|
183
|
+
return undefined;
|
|
184
|
+
const value = node[OPENAPI_BASELINE_KEY];
|
|
185
|
+
if (value === undefined)
|
|
186
|
+
return undefined;
|
|
187
|
+
return typeof value === "string" ? value.trim() : String(value);
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=digest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../../../src/core/openapi/digest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What operation content HASHES to, and what a recorded hash means when it is\n * read back.\n *\n * A module of its own because the digest and the comparison it must agree with\n * are a matched pair: key order must not matter here, since the merge's own\n * idea of \"this operation differs\" is `isDeepStrictEqual`, which ignores it. A\n * second spelling of either would go stale over a reordered `summary` pair the\n * merge calls identical — a false collision on the one check whose whole worth\n * is that it fires only for real ones.\n */\nimport { createHash } from \"node:crypto\";\n\n/**\n * The baseline marker: which living version of this operation a FEATURE delta\n * was written against. Same idea as a requirement's `Based-On:` (core/document/spec.ts),\n * in the vendor-extension shape this axis already uses for `x-loam-remove`.\n *\n * This axis needs it MORE than the requirement axis does, because a feature's\n * openapi.yaml is a COMPLETE document rather than a patch: authors restate the\n * living contract around the slot they are changing, and the merge upserts every\n * operation the document spells. So a feature that never meant to touch\n * `refundOrder` — it merely quoted it — pushes its authoring-time copy back over\n * whatever landed in between, reverting another team's shipped change without\n * either feature overlapping the other at all. The pin is what lets the merge\n * tell a QUOTE from an EDIT: a quote equals its own baseline, and is skipped.\n */\nexport const OPENAPI_BASELINE_KEY = \"x-loam-based-on\";\n\n/**\n * The feature-only ROOT record that pins the surfaces the operation pin cannot\n * reach: path-item non-method keys and components. `{ pathItems: {<path>:\n * {<key>: <16-hex>}}, components: {\"<kind>/<name>\": <16-hex>} }` — written by\n * `loam rebase`, graded by validate/archive, consumed by the merge, and\n * stripped from every living document exactly like the pin. A single root\n * record rather than in-value extensions because a component's value can be a\n * bare boolean (JSON Schema allows `true`), which no in-value key survives.\n */\nexport const OPENAPI_BASELINES_KEY = \"x-loam-baselines\";\n\n/** The feature-only explicit removal marker's key. */\nexport const OPENAPI_REMOVE_KEY = \"x-loam-remove\";\n\n/** Is this operation node a feature-only explicit removal marker? */\nexport function isRemoval(node: unknown): boolean {\n return node !== null &&\n typeof node === \"object\" &&\n (node as Record<string, unknown>)[OPENAPI_REMOVE_KEY] === true;\n}\n\n/**\n * Every key that is an instruction to loam rather than part of the contract.\n * At PATH level none has any meaning — `x-loam-remove` addresses one\n * operation and `x-loam-based-on` pins one — so a path item carrying either\n * is an authoring mistake, and one that must not be published whatever else\n * is done about it. Lives HERE, in the openapi root beside the keys it names,\n * because both `merge/` and `baseline/` consume it and a value edge between\n * those two packages in either direction would be a package cycle.\n */\nexport const FEATURE_ONLY_KEYS: ReadonlySet<string> = new Set([OPENAPI_REMOVE_KEY, OPENAPI_BASELINE_KEY]);\n\n/**\n * `value` with every feature-only key removed, at ANY depth — or the value\n * unchanged (same reference) when it carries none. The component closure\n * writes values VERBATIM into the living contract, and a component holding an\n * operation shape (a `pathItems` component, a callback) can nest the markers\n * where the path-level strip never walks: `--approve` once published\n * `x-loam-remove: true` inside a living component, invisible even to\n * `validate`, whose marker sweep reads `paths` alone.\n */\nexport function withoutFeatureKeysDeep(value: unknown): unknown {\n if (value === null || typeof value !== \"object\") return value;\n if (Array.isArray(value)) {\n let changed = false;\n const out = value.map((v) => {\n const stripped = withoutFeatureKeysDeep(v);\n if (stripped !== v) changed = true;\n return stripped;\n });\n return changed ? out : value;\n }\n const record = value as Record<string, unknown>;\n let changed = false;\n const out: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(record)) {\n if (FEATURE_ONLY_KEYS.has(key)) {\n changed = true;\n continue;\n }\n const stripped = withoutFeatureKeysDeep(v);\n if (stripped !== v) changed = true;\n out[key] = stripped;\n }\n return changed ? out : value;\n}\n\n/** How much of the sha256 an `x-loam-based-on` carries — the length every loam digest uses. */\nexport const OPERATION_DIGEST_LENGTH = 16;\n\n/** The value shape of an `x-loam-based-on`, exactly as `operationDigest` writes it. */\nexport const OPERATION_DIGEST_RE = new RegExp(`^[0-9a-f]{${OPERATION_DIGEST_LENGTH}}$`);\n\n/**\n * Canonical JSON for a resolved YAML value: object keys sorted, arrays left in\n * order, everything else as JSON writes it.\n *\n * Key ORDER must not matter, because the merge's own idea of \"this operation\n * differs\" is `isDeepStrictEqual` (./merge/merge.ts), which ignores it. A\n * digest that disagreed with that comparison would go stale over a reordered\n * `summary`/`operationId` pair the merge itself calls identical — a false\n * collision on the one check whose worth is that it fires only for real ones.\n *\n * Exported for the ASYNCAPI axis (core/asyncapi/digest.ts), whose slot\n * digests are the same rule over different sections: one spelling of the\n * canonical form for both axes, because two spellings would eventually\n * disagree — the exact drift this module's own header names as the failure\n * mode. The import direction is asyncapi → openapi only; nothing under\n * core/openapi/ may import from core/asyncapi/, or the package graph cycles.\n */\nexport function canonicalJson(node: unknown): string {\n if (node === null || typeof node !== \"object\") return JSON.stringify(node) ?? \"null\";\n if (Array.isArray(node)) return `[${node.map(canonicalJson).join(\",\")}]`;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([, value]) => value !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${canonicalJson(value)}`).join(\",\")}}`;\n}\n\n/**\n * The operation without its own baseline marker — what a digest is taken over,\n * and what the merge writes.\n *\n * A pin is a statement ABOUT a delta, never part of the operation it describes.\n * Inside the digest input, an operation's identity would depend on the pin\n * pointing at it and no baseline could ever be self-consistent; inside the\n * merged contract, a living operation would carry a pin to a version of itself\n * and the next feature's baseline would hash the previous feature's bookkeeping.\n */\nexport function withoutOperationBaseline(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const record = node as Record<string, unknown>;\n if (!(OPENAPI_BASELINE_KEY in record)) return node;\n const rest = { ...record };\n delete rest[OPENAPI_BASELINE_KEY];\n return rest;\n}\n\n/**\n * The identity of ANY resolved value: sha256 of its canonical form. The one\n * digest the path-item and component baselines carry — order-of-keys blind,\n * array-order sensitive, legal over scalars and booleans, exactly as the\n * operation digest always was.\n */\nexport function valueDigest(node: unknown): string {\n return createHash(\"sha256\").update(canonicalJson(node), \"utf8\").digest(\"hex\").slice(0, OPERATION_DIGEST_LENGTH);\n}\n\n/** The identity of an operation's CONTENT: sha256 of its canonical form, pin excluded. */\nexport function operationDigest(node: unknown): string {\n return valueDigest(withoutOperationBaseline(node));\n}\n\n/**\n * What a pin says, from digests alone — the one definition every decider\n * shares: the gate (./baseline/gate.ts) and the merge\n * (./merge/{merge,components,pin}.ts) alike. Two spellings of\n * this rule would eventually disagree about which operations a merge writes,\n * and the disagreement would be invisible until a contract came back wrong.\n *\n * Order matters: `quote` is settled against the delta's OWN content before the\n * living contract is consulted. An operation equal to its own baseline was not\n * edited, so what living holds now is nobody's business but the feature that\n * put it there.\n */\nexport function classifyBaselineDigests(\n pin: string | undefined,\n ownDigest: string,\n livingDigest: string | undefined,\n): \"unpinned\" | \"quote\" | \"edit\" | \"stale\" | \"unfounded\" {\n if (pin === undefined) return \"unpinned\";\n if (livingDigest === undefined) return \"unfounded\";\n if (pin === ownDigest) return \"quote\";\n if (pin === livingDigest) return \"edit\";\n return \"stale\";\n}\n\n/** The `x-loam-based-on` a node declares, or undefined — non-strings included, so validate can refuse them. */\nexport function operationBaselineOf(node: unknown): string | undefined {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return undefined;\n const value = (node as Record<string, unknown>)[OPENAPI_BASELINE_KEY];\n if (value === undefined) return undefined;\n return typeof value === \"string\" ? value.trim() : String(value);\n}\n\n"]}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A service's OpenAPI contract as loam reads it: which keys hold operations,
|
|
3
|
+
* what an operation is once loam's own markers are accounted for, and what the
|
|
4
|
+
* fleet will provide after a feature is applied.
|
|
5
|
+
*
|
|
6
|
+
* Split from `./digest.ts` because the two answer different questions about the
|
|
7
|
+
* same file. This one is about STRUCTURE — a path item's operation-bearing
|
|
8
|
+
* keys, an `operationId`, a removal marker — and every reader of a contract
|
|
9
|
+
* comes through here. The digest is about IDENTITY, and only the pin and the
|
|
10
|
+
* merge need it.
|
|
11
|
+
*/
|
|
1
12
|
import { isUtf8 } from "node:buffer";
|
|
2
|
-
import {
|
|
13
|
+
import { danglingRefs, failureCodesOf, responseUndescribed } from "./depth.js";
|
|
14
|
+
import { operationBaselineOf, operationDigest } from "./digest.js";
|
|
3
15
|
import { existsSync } from "node:fs";
|
|
4
16
|
import { readFile } from "node:fs/promises";
|
|
5
17
|
import { parse } from "yaml";
|
|
6
|
-
import { featureSpecPaths
|
|
18
|
+
import { featureSpecPaths } from "../repo/paths.js";
|
|
19
|
+
import { locateServicePaths } from "../repo/service-target.js";
|
|
7
20
|
/**
|
|
8
21
|
* The path-item keys that hold operations. A path item also carries `summary`,
|
|
9
22
|
* `parameters`, `servers` and vendor `x-*` extensions — an object-valued one with
|
|
@@ -13,104 +26,6 @@ import { featureSpecPaths, servicePaths } from "./repo.js";
|
|
|
13
26
|
* of the same keys.
|
|
14
27
|
*/
|
|
15
28
|
export const HTTP_METHODS = new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
16
|
-
/**
|
|
17
|
-
* The baseline marker: which living version of this operation a FEATURE delta
|
|
18
|
-
* was written against. Same idea as a requirement's `Based-On:` (core/spec.ts),
|
|
19
|
-
* in the vendor-extension shape this axis already uses for `x-loam-remove`.
|
|
20
|
-
*
|
|
21
|
-
* This axis needs it MORE than the requirement axis does, because a feature's
|
|
22
|
-
* openapi.yaml is a COMPLETE document rather than a patch: authors restate the
|
|
23
|
-
* living contract around the slot they are changing, and the merge upserts every
|
|
24
|
-
* operation the document spells. So a feature that never meant to touch
|
|
25
|
-
* `refundOrder` — it merely quoted it — pushes its authoring-time copy back over
|
|
26
|
-
* whatever landed in between, reverting another team's shipped change without
|
|
27
|
-
* either feature overlapping the other at all. The pin is what lets the merge
|
|
28
|
-
* tell a QUOTE from an EDIT: a quote equals its own baseline, and is skipped.
|
|
29
|
-
*/
|
|
30
|
-
export const OPENAPI_BASELINE_KEY = "x-loam-based-on";
|
|
31
|
-
/** How much of the sha256 an `x-loam-based-on` carries — the length every loam digest uses. */
|
|
32
|
-
export const OPERATION_DIGEST_LENGTH = 16;
|
|
33
|
-
/** The value shape of an `x-loam-based-on`, exactly as `operationDigest` writes it. */
|
|
34
|
-
export const OPERATION_DIGEST_RE = new RegExp(`^[0-9a-f]{${OPERATION_DIGEST_LENGTH}}$`);
|
|
35
|
-
/**
|
|
36
|
-
* Canonical JSON for a resolved YAML value: object keys sorted, arrays left in
|
|
37
|
-
* order, everything else as JSON writes it.
|
|
38
|
-
*
|
|
39
|
-
* Key ORDER must not matter, because the merge's own idea of "this operation
|
|
40
|
-
* differs" is `isDeepStrictEqual` (core/openapi-merge.ts), which ignores it. A
|
|
41
|
-
* digest that disagreed with that comparison would go stale over a reordered
|
|
42
|
-
* `summary`/`operationId` pair the merge itself calls identical — a false
|
|
43
|
-
* collision on the one check whose worth is that it fires only for real ones.
|
|
44
|
-
*/
|
|
45
|
-
function canonicalJson(node) {
|
|
46
|
-
if (node === null || typeof node !== "object")
|
|
47
|
-
return JSON.stringify(node) ?? "null";
|
|
48
|
-
if (Array.isArray(node))
|
|
49
|
-
return `[${node.map(canonicalJson).join(",")}]`;
|
|
50
|
-
const entries = Object.entries(node)
|
|
51
|
-
.filter(([, value]) => value !== undefined)
|
|
52
|
-
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
53
|
-
return `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${canonicalJson(value)}`).join(",")}}`;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* The operation without its own baseline marker — what a digest is taken over,
|
|
57
|
-
* and what the merge writes.
|
|
58
|
-
*
|
|
59
|
-
* A pin is a statement ABOUT a delta, never part of the operation it describes.
|
|
60
|
-
* Inside the digest input, an operation's identity would depend on the pin
|
|
61
|
-
* pointing at it and no baseline could ever be self-consistent; inside the
|
|
62
|
-
* merged contract, a living operation would carry a pin to a version of itself
|
|
63
|
-
* and the next feature's baseline would hash the previous feature's bookkeeping.
|
|
64
|
-
*/
|
|
65
|
-
export function withoutOperationBaseline(node) {
|
|
66
|
-
if (node === null || typeof node !== "object" || Array.isArray(node))
|
|
67
|
-
return node;
|
|
68
|
-
const record = node;
|
|
69
|
-
if (!(OPENAPI_BASELINE_KEY in record))
|
|
70
|
-
return node;
|
|
71
|
-
const rest = { ...record };
|
|
72
|
-
delete rest[OPENAPI_BASELINE_KEY];
|
|
73
|
-
return rest;
|
|
74
|
-
}
|
|
75
|
-
/** The identity of an operation's CONTENT: sha256 of its canonical form, pin excluded. */
|
|
76
|
-
export function operationDigest(node) {
|
|
77
|
-
return createHash("sha256")
|
|
78
|
-
.update(canonicalJson(withoutOperationBaseline(node)), "utf8")
|
|
79
|
-
.digest("hex")
|
|
80
|
-
.slice(0, OPERATION_DIGEST_LENGTH);
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* What a pin says, from digests alone — the one definition both the gate
|
|
84
|
-
* (core/coherence.ts, which has parsed Operations) and the merge
|
|
85
|
-
* (core/openapi-merge.ts, which has raw YAML trees) decide by. Two spellings of
|
|
86
|
-
* this rule would eventually disagree about which operations a merge writes,
|
|
87
|
-
* and the disagreement would be invisible until a contract came back wrong.
|
|
88
|
-
*
|
|
89
|
-
* Order matters: `quote` is settled against the delta's OWN content before the
|
|
90
|
-
* living contract is consulted. An operation equal to its own baseline was not
|
|
91
|
-
* edited, so what living holds now is nobody's business but the feature that
|
|
92
|
-
* put it there.
|
|
93
|
-
*/
|
|
94
|
-
export function classifyBaselineDigests(pin, ownDigest, livingDigest) {
|
|
95
|
-
if (pin === undefined)
|
|
96
|
-
return "unpinned";
|
|
97
|
-
if (livingDigest === undefined)
|
|
98
|
-
return "unfounded";
|
|
99
|
-
if (pin === ownDigest)
|
|
100
|
-
return "quote";
|
|
101
|
-
if (pin === livingDigest)
|
|
102
|
-
return "edit";
|
|
103
|
-
return "stale";
|
|
104
|
-
}
|
|
105
|
-
/** The `x-loam-based-on` a node declares, or undefined — non-strings included, so validate can refuse them. */
|
|
106
|
-
export function operationBaselineOf(node) {
|
|
107
|
-
if (node === null || typeof node !== "object" || Array.isArray(node))
|
|
108
|
-
return undefined;
|
|
109
|
-
const value = node[OPENAPI_BASELINE_KEY];
|
|
110
|
-
if (value === undefined)
|
|
111
|
-
return undefined;
|
|
112
|
-
return typeof value === "string" ? value.trim() : String(value);
|
|
113
|
-
}
|
|
114
29
|
/**
|
|
115
30
|
* Extract operations from an OpenAPI document by walking the parsed YAML
|
|
116
31
|
* structure (paths.<path>.<method>, method filtered to the HTTP set).
|
|
@@ -149,9 +64,20 @@ export async function readOpenapi(openapiPath, context) {
|
|
|
149
64
|
// reads the contract stays suspended.
|
|
150
65
|
if (!isUtf8(bytes))
|
|
151
66
|
return { ...empty(), unreadable: true, error: "file is not valid UTF-8" };
|
|
67
|
+
return openapiFromText(bytes.toString("utf8"));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The same read over text already in hand — the parse half of `readOpenapi`,
|
|
71
|
+
* split out so `loam diff` can read a contract fetched from a base git ref
|
|
72
|
+
* (core/diff/base-state.ts) without writing it to disk. One spelling of the
|
|
73
|
+
* walk for both entries, so a base-ref contract can never parse differently
|
|
74
|
+
* from the living file the same bytes would produce; the byte concerns
|
|
75
|
+
* (existence, UTF-8) stay with the reader that holds the bytes.
|
|
76
|
+
*/
|
|
77
|
+
export function openapiFromText(text) {
|
|
152
78
|
let doc;
|
|
153
79
|
try {
|
|
154
|
-
doc = parse(
|
|
80
|
+
doc = parse(text);
|
|
155
81
|
}
|
|
156
82
|
catch (e) {
|
|
157
83
|
return { ...empty(), unreadable: true, error: e instanceof Error ? e.message : String(e) };
|
|
@@ -162,9 +88,12 @@ export async function readOpenapi(openapiPath, context) {
|
|
|
162
88
|
if (doc !== null && (typeof doc !== "object" || Array.isArray(doc))) {
|
|
163
89
|
return { ...empty(), unreadable: true, error: "document is not a YAML mapping" };
|
|
164
90
|
}
|
|
91
|
+
// Refs are probed over the WHOLE document: a schema in `components` that
|
|
92
|
+
// `$ref`s a neighbour nobody wrote is exactly as broken as one under a path.
|
|
93
|
+
const refs = danglingRefs(doc);
|
|
165
94
|
const paths = doc?.paths;
|
|
166
95
|
if (!paths || typeof paths !== "object")
|
|
167
|
-
return empty();
|
|
96
|
+
return { ...empty(), danglingRefs: refs };
|
|
168
97
|
const ops = [];
|
|
169
98
|
const anonymousRemovals = [];
|
|
170
99
|
const pathLevelRemovals = [];
|
|
@@ -196,14 +125,23 @@ export async function readOpenapi(openapiPath, context) {
|
|
|
196
125
|
method,
|
|
197
126
|
digest: operationDigest(op),
|
|
198
127
|
...(basedOn === undefined ? {} : { basedOn }),
|
|
128
|
+
...(responseUndescribed(op) ? { undescribed: true } : {}),
|
|
129
|
+
failureCodes: failureCodesOf(op),
|
|
199
130
|
});
|
|
200
131
|
}
|
|
201
132
|
}
|
|
202
133
|
const duplicateIds = [...seen].filter(([, n]) => n > 1).map(([id]) => id);
|
|
203
|
-
return { ops, duplicateIds, anonymousRemovals, pathLevelRemovals, unreadable: false };
|
|
134
|
+
return { ops, duplicateIds, anonymousRemovals, pathLevelRemovals, unreadable: false, danglingRefs: refs };
|
|
204
135
|
}
|
|
205
136
|
function empty() {
|
|
206
|
-
return {
|
|
137
|
+
return {
|
|
138
|
+
ops: [],
|
|
139
|
+
duplicateIds: [],
|
|
140
|
+
anonymousRemovals: [],
|
|
141
|
+
pathLevelRemovals: [],
|
|
142
|
+
unreadable: false,
|
|
143
|
+
danglingRefs: [],
|
|
144
|
+
};
|
|
207
145
|
}
|
|
208
146
|
/** The operations alone — for every caller whose own finding already covers the unreadable case. */
|
|
209
147
|
export async function operations(openapiPath, context) {
|
|
@@ -236,7 +174,7 @@ export async function operationIds(openapiPath, context) {
|
|
|
236
174
|
* it.
|
|
237
175
|
*/
|
|
238
176
|
export async function serviceOperationIds(docsDir, service, featureDir, context) {
|
|
239
|
-
const ids = new Set((await operations(
|
|
177
|
+
const ids = new Set((await operations((await locateServicePaths(docsDir, service, context)).openapi, context)).filter((op) => !op.remove).map((op) => op.id));
|
|
240
178
|
// Absence is `undefined`, the shape the optional parameter declares. The
|
|
241
179
|
// truthiness test also read "" as "no feature", which is a caller mistake
|
|
242
180
|
// worth surfacing rather than answering as though the feature carried no
|
|
@@ -252,4 +190,4 @@ export async function serviceOperationIds(docsDir, service, featureDir, context)
|
|
|
252
190
|
}
|
|
253
191
|
return [...ids];
|
|
254
192
|
}
|
|
255
|
-
//# sourceMappingURL=
|
|
193
|
+
//# sourceMappingURL=doc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc.js","sourceRoot":"","sources":["../../../src/core/openapi/doc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAG7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAoG3G;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,WAAmB,EAAE,OAAsB;IAC3E,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;IACnE,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,EAAE,CAAC;IAC7C,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC1C,4EAA4E;IAC5E,8EAA8E;IAC9E,yEAAyE;IACzE,4EAA4E;IAC5E,sCAAsC;IACtC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC;IAC9F,OAAO,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7F,CAAC;IACD,6EAA6E;IAC7E,2EAA2E;IAC3E,6EAA6E;IAC7E,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACpE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC;IACnF,CAAC;IACD,yEAAyE;IACzE,6EAA6E;IAC7E,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAI,GAAkC,EAAE,KAAK,CAAC;IACzD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IACnF,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,MAAM,iBAAiB,GAAuB,EAAE,CAAC;IACjD,MAAM,iBAAiB,GAAa,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QAC5E,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,SAAS;QAChD,IAAK,IAAgC,CAAC,eAAe,CAAC,KAAK,IAAI;YAAE,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9F,KAAK,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC,EAAE,CAAC;YAC3E,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAS;YACxC,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ;gBAAE,SAAS;YAC5C,MAAM,EAAE,GAAI,EAA8B,CAAC,aAAa,CAAC,CAAC;YAC1D,MAAM,MAAM,GAAI,EAA8B,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC;YACzE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC9C,IAAI,MAAM;oBAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;gBACrD,SAAS;YACX,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACtC,MAAM,OAAO,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAC;YACxC,GAAG,CAAC,IAAI,CAAC;gBACP,EAAE;gBACF,UAAU,EAAG,EAA8B,CAAC,YAAY,CAAC,KAAK,IAAI;gBAClE,MAAM;gBACN,IAAI;gBACJ,MAAM;gBACN,MAAM,EAAE,eAAe,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;gBAC7C,GAAG,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClE,YAAY,EAAE,cAAc,CAAC,EAAE,CAAC;aACjC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,MAAM,YAAY,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1E,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;AAC5G,CAAC;AAED,SAAS,KAAK;IACZ,OAAO;QACL,GAAG,EAAE,EAAE;QACP,YAAY,EAAE,EAAE;QAChB,iBAAiB,EAAE,EAAE;QACrB,iBAAiB,EAAE,EAAE;QACrB,UAAU,EAAE,KAAK;QACjB,YAAY,EAAE,EAAE;KACjB,CAAC;AACJ,CAAC;AAED,oGAAoG;AACpG,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,WAAmB,EAAE,OAAsB;IAC1E,OAAO,CAAC,MAAM,WAAW,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AACvD,CAAC;AAED,6FAA6F;AAC7F,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,WAAmB,EAAE,OAAsB;IAC5E,OAAO,CAAC,MAAM,UAAU,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAgB,EAChB,OAAwB,EACxB,UAAuB,EACvB,OAAsB;IAEtB,MAAM,GAAG,GAAG,IAAI,GAAG,CACjB,CAAC,MAAM,UAAU,CAAC,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CACzI,CAAC;IACF,yEAAyE;IACzE,0EAA0E;IAC1E,yEAAyE;IACzE,kBAAkB;IAClB,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzF,KAAK,MAAM,EAAE,IAAI,OAAO;YAAE,IAAI,EAAE,CAAC,MAAM;gBAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3D,KAAK,MAAM,EAAE,IAAI,OAAO;YAAE,IAAI,CAAC,EAAE,CAAC,MAAM;gBAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;AAClB,CAAC","sourcesContent":["/**\n * A service's OpenAPI contract as loam reads it: which keys hold operations,\n * what an operation is once loam's own markers are accounted for, and what the\n * fleet will provide after a feature is applied.\n *\n * Split from `./digest.ts` because the two answer different questions about the\n * same file. This one is about STRUCTURE — a path item's operation-bearing\n * keys, an `operationId`, a removal marker — and every reader of a contract\n * comes through here. The digest is about IDENTITY, and only the pin and the\n * merge need it.\n */\nimport { isUtf8 } from \"node:buffer\";\nimport { danglingRefs, failureCodesOf, responseUndescribed } from \"./depth.js\";\nimport { operationBaselineOf, operationDigest } from \"./digest.js\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { PathableService } from \"../kernel/ids/service.js\";\nimport { featureSpecPaths } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\n\n/**\n * The path-item keys that hold operations. A path item also carries `summary`,\n * `parameters`, `servers` and vendor `x-*` extensions — an object-valued one with\n * an `operationId` inside (x-legacy and friends) is not an operation, and a\n * phantom id from it would make a broken contract look \"available\" to the\n * op-exists checks. Shared with archive's merge, which asks the same question\n * of the same keys.\n */\nexport const HTTP_METHODS = new Set([\"get\", \"put\", \"post\", \"delete\", \"options\", \"head\", \"patch\", \"trace\"]);\n\n/** One operation as the reader sees it, including loam's feature-only removal marker. */\nexport interface Operation {\n id: string;\n /**\n * The standard per-operation OpenAPI `deprecated` flag — lifecycle\n * visibility, the documented first step of retiring an op.\n */\n deprecated: boolean;\n /**\n * `x-loam-remove: true` asks a FEATURE delta to remove this exact operation\n * on archive. The marker is never a callable operation and is illegal in a\n * living contract.\n */\n remove: boolean;\n /** Exact slot the marker/upsert addresses; removal is deliberately path+method-safe. */\n path: string;\n method: string;\n /**\n * `operationDigest` of this operation as it stands in ITS OWN document —\n * computed here so every caller compares content the one way the merge does.\n */\n digest: string;\n /**\n * The living version this operation was written against, from\n * `x-loam-based-on`. Feature deltas only: a living contract never carries one\n * (the merge strips it), and an adopted contract never had one.\n */\n basedOn?: string;\n /**\n * Present when no response declares content with a schema while at least one\n * should carry a body (`depth.ts`). Set only when true, so the many callers\n * that compare operations structurally never see the field on a described\n * contract.\n */\n undescribed?: true;\n /**\n * Declared non-2xx response codes, in document order — the refusals this\n * operation promises. `default` and any `4XX`/`5XX` wildcard are excluded:\n * neither names a case a scenario could be written for.\n *\n * The contract is the only place in the corpus that enumerates what an\n * operation REFUSES, and until this was read nothing joined those refusals to\n * anything. `api.ungoverned` grades whole operations against requirements,\n * so an operation documented by its happy path alone — one requirement, one\n * scenario, twelve declared failure codes — passed every check loam had.\n */\n failureCodes: string[];\n}\n\n/** A `x-loam-remove: true` marker with no usable operationId — a slot named but no operation named. */\nexport interface AnonymousRemoval {\n path: string;\n method: string;\n}\n\n/** The parse of one OpenAPI document: its operations, and whether the file could be read at all. */\nexport interface OpenapiDoc {\n ops: Operation[];\n /**\n * operationIds this document defines in more than one (path, method) slot.\n * A LIVING contract with two slots claiming one id is ambiguous to every\n * check that joins on the id — a requirement's `Operations:` line, an edge's\n * `metadata { op }`, a removal marker — and the join silently picks one.\n */\n duplicateIds: string[];\n /**\n * Removal markers the operation reader cannot name. They are invisible to\n * every id-keyed check, so a delta carrying one used to archive cleanly and\n * write `x-loam-remove: true` straight into a living contract.\n */\n anonymousRemovals: AnonymousRemoval[];\n /**\n * Paths carrying `x-loam-remove: true` at PATH level, beside the methods\n * rather than inside one.\n *\n * Not an operation, so nothing keyed by (path, method) could see it — and this\n * reader is method-keyed like the merge was. A marker written there addressed\n * no operation, so archive copied it into the living contract verbatim, where\n * it was invisible to `openapi.remove-marker-living` for the same reason and\n * kept the empty-path cleanup from ever firing. Reported as its own list\n * because the answer differs by document: in a FEATURE delta it is an\n * authoring mistake, in a LIVING contract it is a marker that must never have\n * been published.\n */\n pathLevelRemovals: string[];\n /**\n * True when the file EXISTS but cannot be read as an OpenAPI document —\n * broken YAML, or a document that is not a mapping. A missing file is not\n * unreadable (absence is `service.no-openapi`'s question), and an empty one\n * parses to null and honestly defines nothing.\n */\n unreadable: boolean;\n /** The parser's own message, when there is one to quote back. */\n error?: string;\n /** Internal `#/` refs that resolve to nothing in this document (`depth.ts`). */\n danglingRefs: string[];\n}\n\n/**\n * Extract operations from an OpenAPI document by walking the parsed YAML\n * structure (paths.<path>.<method>, method filtered to the HTTP set).\n * Structure-aware on purpose: a regex scan both drops legal ids (kebab-case,\n * dotted) and picks up phantom ids from description text. Each operation\n * carries its id and its `deprecated: true` flag (exactly `true` counts —\n * strings and truthy noise do not). `ops` is the defined set, keyed by the\n * (path, method) SLOT — not by operationId.\n *\n * Keying by id used to drop the second slot claiming a name, which made the\n * single most common contract edit invisible: relocating an endpoint is a\n * removal marker at the old slot plus an upsert at the new one, the SAME\n * operationId twice. Whichever came second in document order vanished, so\n * either the marker addressed a slot loam could not see (`remove-target-missing`\n * on a perfectly good delta) or the new definition did. Two slots claiming one\n * id in a LIVING contract is a real ambiguity rather than a merge failure, so\n * it rides out as `duplicateIds` for the caller to grade.\n *\n * An unreadable document yields [] ops PLUS the `unreadable` flag. Parsers\n * never diagnose — the policy of every reader here — so the failure travels as\n * a flag for validate to grade (`openapi.invalid`). Swallowing it into a bare\n * [] made a broken contract indistinguishable from an EMPTY one, and the spine\n * check then reported every inbound edge broken (`spine.op-undefined`) — a\n * false diagnosis pointing at the landscape when the truth was this file.\n */\nexport async function readOpenapi(openapiPath: string, context?: FleetContext): Promise<OpenapiDoc> {\n if (context !== undefined) return context.readOpenapi(openapiPath);\n if (!existsSync(openapiPath)) return empty();\n const bytes = await readFile(openapiPath);\n // A contract whose bytes are not UTF-8 is as unreadable as broken YAML, and\n // for a worse reason: `toString(\"utf8\")` would substitute U+FFFD and hand the\n // parser a document nobody wrote. It travels as `unreadable` so validate\n // grades it `openapi.invalid` — the file is the error, and every check that\n // reads the contract stays suspended.\n if (!isUtf8(bytes)) return { ...empty(), unreadable: true, error: \"file is not valid UTF-8\" };\n return openapiFromText(bytes.toString(\"utf8\"));\n}\n\n/**\n * The same read over text already in hand — the parse half of `readOpenapi`,\n * split out so `loam diff` can read a contract fetched from a base git ref\n * (core/diff/base-state.ts) without writing it to disk. One spelling of the\n * walk for both entries, so a base-ref contract can never parse differently\n * from the living file the same bytes would produce; the byte concerns\n * (existence, UTF-8) stay with the reader that holds the bytes.\n */\nexport function openapiFromText(text: string): OpenapiDoc {\n let doc: unknown;\n try {\n doc = parse(text);\n } catch (e) {\n return { ...empty(), unreadable: true, error: e instanceof Error ? e.message : String(e) };\n }\n // A scalar or sequence document is as unreadable as broken YAML: there is no\n // mapping to look `paths` up in, so nothing can be concluded from it. null\n // (an empty file) stays readable — it defines nothing, and says so honestly.\n if (doc !== null && (typeof doc !== \"object\" || Array.isArray(doc))) {\n return { ...empty(), unreadable: true, error: \"document is not a YAML mapping\" };\n }\n // Refs are probed over the WHOLE document: a schema in `components` that\n // `$ref`s a neighbour nobody wrote is exactly as broken as one under a path.\n const refs = danglingRefs(doc);\n const paths = (doc as { paths?: unknown } | null)?.paths;\n if (!paths || typeof paths !== \"object\") return { ...empty(), danglingRefs: refs };\n const ops: Operation[] = [];\n const anonymousRemovals: AnonymousRemoval[] = [];\n const pathLevelRemovals: string[] = [];\n const seen = new Map<string, number>();\n for (const [path, item] of Object.entries(paths as Record<string, unknown>)) {\n if (!item || typeof item !== \"object\") continue;\n if ((item as Record<string, unknown>)[\"x-loam-remove\"] === true) pathLevelRemovals.push(path);\n for (const [method, op] of Object.entries(item as Record<string, unknown>)) {\n if (!HTTP_METHODS.has(method)) continue;\n if (!op || typeof op !== \"object\") continue;\n const id = (op as Record<string, unknown>)[\"operationId\"];\n const remove = (op as Record<string, unknown>)[\"x-loam-remove\"] === true;\n if (typeof id !== \"string\" || id.length === 0) {\n if (remove) anonymousRemovals.push({ path, method });\n continue;\n }\n seen.set(id, (seen.get(id) ?? 0) + 1);\n const basedOn = operationBaselineOf(op);\n ops.push({\n id,\n deprecated: (op as Record<string, unknown>)[\"deprecated\"] === true,\n remove,\n path,\n method,\n digest: operationDigest(op),\n ...(basedOn === undefined ? {} : { basedOn }),\n ...(responseUndescribed(op) ? { undescribed: true as const } : {}),\n failureCodes: failureCodesOf(op),\n });\n }\n }\n const duplicateIds = [...seen].filter(([, n]) => n > 1).map(([id]) => id);\n return { ops, duplicateIds, anonymousRemovals, pathLevelRemovals, unreadable: false, danglingRefs: refs };\n}\n\nfunction empty(): OpenapiDoc {\n return {\n ops: [],\n duplicateIds: [],\n anonymousRemovals: [],\n pathLevelRemovals: [],\n unreadable: false,\n danglingRefs: [],\n };\n}\n\n/** The operations alone — for every caller whose own finding already covers the unreadable case. */\nexport async function operations(openapiPath: string, context?: FleetContext): Promise<Operation[]> {\n return (await readOpenapi(openapiPath, context)).ops;\n}\n\n/** The operationIds alone — `operations` for every caller that asks only \"does it exist\". */\nexport async function operationIds(openapiPath: string, context?: FleetContext): Promise<string[]> {\n return (await operations(openapiPath, context)).map((o) => o.id);\n}\n\n/**\n * The operationIds a service will provide after a feature is applied: living\n * operations, plus feature upserts, minus explicit feature removals.\n *\n * Removals are applied BEFORE upserts, which is what makes the answer\n * independent of the order the feature's document happens to spell them in.\n * Interleaving them one operation at a time meant a relocation — the same id\n * removed from `/old` and defined on `/new` — answered \"defined\" or \"gone\"\n * depending on which path sorted first in the YAML, and the requirement\n * governing it was reported undefined (`spec-api.op-undefined`) on exactly one\n * of the two spellings of the same change.\n *\n * A context is threaded into the READS and never asked for the answer. It used\n * to own a second copy of this function, and that copy interleaved removals\n * with upserts — the exact bug the paragraph above describes as fixed. So the\n * commands that pass a context (`validate`, `status`) and the one that does not\n * (`archive`) disagreed about whether a relocated operation still existed, and\n * `spec-api.op-undefined` — an error that gates archive — fired or not\n * depending on which path key the author happened to type first. Memoising is\n * the context's job; the ordering rule is this function's, and there is one of\n * it.\n */\nexport async function serviceOperationIds(\n docsDir: DocsDir,\n service: PathableService,\n featureDir?: FeatureDir,\n context?: FleetContext,\n): Promise<string[]> {\n const ids = new Set(\n (await operations((await locateServicePaths(docsDir, service, context)).openapi, context)).filter((op) => !op.remove).map((op) => op.id),\n );\n // Absence is `undefined`, the shape the optional parameter declares. The\n // truthiness test also read \"\" as \"no feature\", which is a caller mistake\n // worth surfacing rather than answering as though the feature carried no\n // contract delta.\n if (featureDir !== undefined) {\n const featOps = await operations(featureSpecPaths(featureDir, service).openapi, context);\n for (const op of featOps) if (op.remove) ids.delete(op.id);\n for (const op of featOps) if (!op.remove) ids.add(op.id);\n }\n return [...ids];\n}\n\n"]}
|