@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13
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 +3448 -0
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +47 -23
- package/QUICKSTART.md +189 -12
- package/README.md +274 -65
- package/ROADMAP.md +572 -31
- package/SCHEMA.md +1709 -155
- package/WORKFLOW.md +298 -72
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/adopt/render.js +50 -9
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/archive.js +16 -19
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +77 -43
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +21 -19
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +167 -30
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +14 -19
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +137 -56
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +3 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +134 -30
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +85 -18
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +29 -7
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +102 -14
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +17 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/doctor.js +60 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +89 -13
- package/dist/commands/explain/explain.js.map +1 -1
- package/dist/commands/explore.js +16 -10
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +5 -7
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +44 -29
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +52 -9
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +37 -34
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +158 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +13 -11
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +9 -5
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +35 -6
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +41 -33
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/show/show.js +17 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +2 -0
- package/dist/commands/status/json.js.map +1 -1
- package/dist/commands/status/print.js +89 -37
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +103 -23
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +106 -46
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +289 -74
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +147 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +200 -21
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +42 -26
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +83 -33
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +78 -27
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +34 -126
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +11 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +26 -20
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +144 -126
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +44 -14
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +10 -2
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +50 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +40 -47
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +36 -160
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +21 -107
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +36 -14
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +121 -61
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +88 -28
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +38 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +85 -60
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +55 -52
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +81 -106
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +21 -9
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +24 -8
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +80 -8
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +88 -10
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +49 -22
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +39 -6
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/protocol.js +1 -1
- package/dist/core/agent/protocol.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/support/report.js +46 -5
- package/dist/core/agent/support/report.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +158 -11
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +78 -39
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +88 -27
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +128 -13
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +96 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +106 -4
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +6 -3
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +68 -27
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +32 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +33 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +12 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +96 -36
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +209 -130
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +109 -31
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +14 -3
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/arch.js +90 -182
- package/dist/core/c4/arch.js.map +1 -1
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +169 -14
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +123 -7
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +43 -12
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +79 -43
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +78 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/seed/template.js +65 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +297 -10
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +222 -141
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +79 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js +20 -0
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +20 -20
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +37 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +182 -114
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +160 -56
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +13 -19
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/delta/uncovered.js +2 -2
- package/dist/core/capabilities/delta/uncovered.js.map +1 -1
- package/dist/core/capabilities/findings.js +18 -22
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +103 -22
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +147 -16
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +28 -87
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.js.map +1 -0
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +51 -6
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/dependencies/fanin.js +2 -2
- package/dist/core/dependencies/fanin.js.map +1 -1
- package/dist/core/deployment/delta.js +191 -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 +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +49 -21
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/docs.js +31 -22
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +91 -11
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/document/apply.js +158 -11
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +26 -21
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/lookup.js +14 -1
- package/dist/core/explain/lookup.js.map +1 -1
- package/dist/core/explain/refusals.js +5 -5
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +68 -109
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +40 -10
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +81 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +11 -4
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +69 -28
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/delta.js +10 -7
- package/dist/core/glossary/delta.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/kernel/path-safety.js +38 -0
- package/dist/core/kernel/path-safety.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +51 -37
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/links/findings.js.map +1 -1
- package/dist/core/links/parse.js +38 -9
- package/dist/core/links/parse.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +40 -10
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +301 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +28 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +17 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +43 -4
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +50 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/authored/paths.js +18 -0
- package/dist/core/repo/authored/paths.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +92 -6
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +74 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +53 -32
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +121 -13
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.js.map +1 -0
- package/dist/core/staging/commit.js +26 -15
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +84 -6
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +24 -14
- package/dist/core/staging/txn/forward.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/contracts.js +39 -15
- package/dist/core/status/contracts.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +33 -8
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +24 -17
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/feature/next.js +20 -7
- package/dist/core/status/feature/next.js.map +1 -1
- package/dist/core/status/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +43 -14
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +153 -58
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/scan.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +199 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/claims.js +31 -7
- package/dist/core/usecases/delta/claims.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +32 -191
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +195 -36
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +26 -14
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/place.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +70 -12
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +4 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +75 -4
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/dist/core/vocabulary/report.js +16 -1
- package/dist/core/vocabulary/report.js.map +1 -1
- package/examples/README.md +54 -11
- package/examples/docs/architecture/landscape.likec4 +62 -6
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +7 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -37
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -31
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -220
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -165
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -247
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/repo/tree/views.js +0 -172
- package/dist/core/repo/tree/views.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The retired index: what every ARCHIVED feature took out of the fleet's living
|
|
3
|
+
* contracts, and who took it.
|
|
4
|
+
*
|
|
5
|
+
* The question it answers is one the living contract cannot: is an operation
|
|
6
|
+
* the living contract does not have NEW, or was it RETIRED? Both read as
|
|
7
|
+
* absent. Before this index existed the feature gate could only treat absent
|
|
8
|
+
* as new — so a delta quoting an operation that a parallel feature retired and
|
|
9
|
+
* archived first was graded as an addition (`api.op-unconsumed`, advisory) and
|
|
10
|
+
* archived at exit 0, putting the retired operation straight back into the
|
|
11
|
+
* living contract. The baseline gate's unpinned count (openapi/baseline/gate.ts)
|
|
12
|
+
* could not see it either: it counts only quotes of operations the living
|
|
13
|
+
* contract still has, so that a genuinely new operation never trips it.
|
|
14
|
+
*
|
|
15
|
+
* The record of a retirement is files, not history. An archived feature keeps
|
|
16
|
+
* its delta under `features/archive/<FEAT>/specs/<svc>/`, and the removal
|
|
17
|
+
* markers in it say what that merge was ASKED to delete — read through the
|
|
18
|
+
* same rule the gate grades an in-flight delta by (./retirement.ts), so
|
|
19
|
+
* relocations are not retirements here either. No git: the docs semantics stay
|
|
20
|
+
* a property of the tree.
|
|
21
|
+
*
|
|
22
|
+
* Asked is not done. The OpenAPI merge deletes only when the living operation
|
|
23
|
+
* AT the marker's method and path carries the marker's operationId
|
|
24
|
+
* (openapi/merge/merge.ts); the event merge deletes a channel or operation slot
|
|
25
|
+
* only when the living contract has that key, and a `components.messages` slot
|
|
26
|
+
* only when the message declared there carries the marker's name
|
|
27
|
+
* (asyncapi/merge/merge.ts). A marker that missed — `remove-target-missing` or
|
|
28
|
+
* `remove-target-mismatch`, archived past under `--approve` — deleted nothing,
|
|
29
|
+
* and counting it called a genuinely new operation a revival and refused it.
|
|
30
|
+
* So every marker is checked against the contract the merge ran against: the
|
|
31
|
+
* feature's snapshot pre-image (`.loam-before/`, staging/snapshot/manifest.ts).
|
|
32
|
+
* A contract the archive CREATED, or never wrote at all, lost nothing to that
|
|
33
|
+
* merge, and reads as an empty pre-image. "Never wrote" is the event merge's
|
|
34
|
+
* case alone: it returns no text when it changes nothing (an asyncapi delta
|
|
35
|
+
* whose every marker missed), and archive snapshots only what it writes. The
|
|
36
|
+
* OpenAPI merge returns text whenever the delta has a path entry, so a
|
|
37
|
+
* contract it left unchanged has an ordinary pre-image equal to what it
|
|
38
|
+
* wrote — read like any other, and every missed marker misses it again. A
|
|
39
|
+
* feature with no intact snapshot (archived before the snapshot existed, or
|
|
40
|
+
* the snapshot edited or removed since) cannot be checked, and its markers
|
|
41
|
+
* count as written — what the index did before it read pre-images at all.
|
|
42
|
+
*
|
|
43
|
+
* The pre-image answers one more thing the delta does not spell: a
|
|
44
|
+
* CHANNEL-slot removal retires the messages its living channel declared
|
|
45
|
+
* inline, and those names exist only in the contract the merge ran against. A
|
|
46
|
+
* feature archived without a pre-image contributes its channel slot and not
|
|
47
|
+
* the inline names.
|
|
48
|
+
*
|
|
49
|
+
* What the index records is every ADDRESS an archived feature vacated, per
|
|
50
|
+
* operationId or message name: where each executed marker stood, whether the
|
|
51
|
+
* relocation rule calls it a retirement or a move. A relocation retires
|
|
52
|
+
* nothing, but it does vacate an address: the id lives on at its new slot,
|
|
53
|
+
* and a later delta quoting the old one would put it back there too — one
|
|
54
|
+
* operationId at two addresses, which the merge cannot tell from an addition
|
|
55
|
+
* any more than it can a retired operation, and which the slot-keyed
|
|
56
|
+
* baseline gate and an id-keyed check both miss (the id is living, the slot
|
|
57
|
+
* is empty). A retirement vacates its address the same way once a later
|
|
58
|
+
* feature re-adds the id somewhere else: indexed by id alone, as this once
|
|
59
|
+
* was, a quote of the retired address read as harmless because the id was
|
|
60
|
+
* living, and archived the id at both. So a vacation carries its address,
|
|
61
|
+
* and for a move where the delta put the entry — kept, because where it
|
|
62
|
+
* lives NOW may be somewhere a later feature put it (`Vacated`). The event
|
|
63
|
+
* axis vacates message addresses the same two ways: a `components.messages`
|
|
64
|
+
* marker, and a channel removal taking its inline declarations. A channel or
|
|
65
|
+
* operation slot's identity IS its key, so a "moved" channel is a retired key
|
|
66
|
+
* plus a new one, and the slot index catches a quote of the old key.
|
|
67
|
+
*
|
|
68
|
+
* What it cannot see, and SCHEMA.md says so: a removal made by hand-editing a
|
|
69
|
+
* living contract leaves no marker anywhere, and an archived feature deleted
|
|
70
|
+
* from disk takes its markers with it. Both read as "never retired" — the gate
|
|
71
|
+
* falls back to calling the entry new, which is what it did before.
|
|
72
|
+
*
|
|
73
|
+
* OpenAPI markers count only when they name an operationId. A marker with
|
|
74
|
+
* none (`openapi.remove-marker-anonymous`) or one written at PATH level
|
|
75
|
+
* (`openapi.remove-marker-path-level`) retires nothing at archive — the merge
|
|
76
|
+
* matches a marker to the living operation by the operationId it names, at
|
|
77
|
+
* its own method and path, and these name none — so an archived feature that
|
|
78
|
+
* carried one under `--approve` left the operation living, and indexing its
|
|
79
|
+
* slot would attribute a retirement that never happened.
|
|
80
|
+
*/
|
|
81
|
+
import { existsSync, statSync } from "node:fs";
|
|
82
|
+
import { resolve } from "node:path";
|
|
83
|
+
import { parse } from "yaml";
|
|
84
|
+
import { inOrder, memo } from "../../kernel/concurrency.js";
|
|
85
|
+
import { archiveDir, ARTIFACT_FILES, featureSpecPaths } from "../../repo/paths.js";
|
|
86
|
+
import { listFeatures } from "../../repo/repo.js";
|
|
87
|
+
import { openapiFromText, readOpenapi } from "../../openapi/doc.js";
|
|
88
|
+
import { readOr } from "../../openapi/contract-text.js";
|
|
89
|
+
import { asyncapiSlots, slotKey, slotsByKey } from "../../asyncapi/digest.js";
|
|
90
|
+
import { asyncapiFromText, readAsyncapi } from "../../asyncapi/read.js";
|
|
91
|
+
import { readSnapshotPreImage } from "../../staging/snapshot/manifest.js";
|
|
92
|
+
import { messageRetirements, operationRetirements } from "./retirement.js";
|
|
93
|
+
/** `METHOD /path` — how the index records, and every finding names, an operation's address. */
|
|
94
|
+
export function operationAddress(op) {
|
|
95
|
+
return `${op.method.toUpperCase()} ${op.path}`;
|
|
96
|
+
}
|
|
97
|
+
/** The index's one key spelling. NUL-joined: no id, section, key or message name can contain it. */
|
|
98
|
+
export function retiredKey(...parts) {
|
|
99
|
+
return parts.join("\0");
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* One index per invocation, keyed by the invocation's own FleetContext.
|
|
103
|
+
*
|
|
104
|
+
* A module-level cache is normally the wrong shape (AGENTS.md: a value cached
|
|
105
|
+
* at import time leaks across tests and, in a long-running host, across
|
|
106
|
+
* invocations). This one is the `c4/resolve/service.ts` exception for the same
|
|
107
|
+
* reason: the key is the per-invocation context object, so two runs can never
|
|
108
|
+
* see each other's answer and the entry dies with the context. It is here
|
|
109
|
+
* rather than a memo on FleetContext because that class sits at L6 and this
|
|
110
|
+
* package at L8 — the import that memo would need points the wrong way up the
|
|
111
|
+
* DAG (docs/DESIGN.md's level table) — while the reads underneath (the feature
|
|
112
|
+
* list, every contract parse) already go through the context's own memos.
|
|
113
|
+
*/
|
|
114
|
+
const PER_INVOCATION = new WeakMap();
|
|
115
|
+
/**
|
|
116
|
+
* A lazy handle on the index: nothing is read until a grader has a candidate
|
|
117
|
+
* to ask about — an entry the merged contract answers at an address its
|
|
118
|
+
* living contract does not, which is exactly the shape the living contract
|
|
119
|
+
* alone cannot call new or revived. A feature that only edits what the living
|
|
120
|
+
* contract already holds never walks `features/archive/`; one that adds
|
|
121
|
+
* anything genuinely new does, since an addition and a revival read the same
|
|
122
|
+
* until the walk answers. Without a context (archive's gate) the handle
|
|
123
|
+
* memoises for its own lifetime, which is one `featureCoherence` call, so the
|
|
124
|
+
* two contract axes still share one walk.
|
|
125
|
+
*/
|
|
126
|
+
export function retiredLookup(docsDir, context) {
|
|
127
|
+
let own;
|
|
128
|
+
return () => {
|
|
129
|
+
if (context === undefined)
|
|
130
|
+
return (own ??= readRetiredIndex(docsDir));
|
|
131
|
+
let store = PER_INVOCATION.get(context);
|
|
132
|
+
if (store === undefined)
|
|
133
|
+
PER_INVOCATION.set(context, (store = new Map()));
|
|
134
|
+
return memo(store, resolve(docsDir), () => readRetiredIndex(docsDir, context));
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** The index's parts, in the one order `readRetiredIndex` records them. */
|
|
138
|
+
const PARTS = ["operations", "slots", "messages"];
|
|
139
|
+
/** Walk every archived feature's contract deltas and record every address each one vacated. */
|
|
140
|
+
async function readRetiredIndex(docsDir, context) {
|
|
141
|
+
const index = {
|
|
142
|
+
operations: new Map(),
|
|
143
|
+
slots: new Map(),
|
|
144
|
+
messages: new Map(),
|
|
145
|
+
};
|
|
146
|
+
// A `features/archive` that is not a directory holds no archived feature,
|
|
147
|
+
// and listing it would throw ENOTDIR out of the feature gate — refusing an
|
|
148
|
+
// archive over a question about OTHER features' history, before the plan
|
|
149
|
+
// that names the real problem (the final move into that path) is reached.
|
|
150
|
+
if (!isDirectory(archiveDir(docsDir)))
|
|
151
|
+
return index;
|
|
152
|
+
const archived = (await listFeatures(docsDir, { includeArchived: true }, context)).filter((f) => f.archived);
|
|
153
|
+
const units = archived.flatMap((feature) => feature.services.map((service) => ({ feature, service })));
|
|
154
|
+
// Every (feature, service) pair is independent of every other, so they are
|
|
155
|
+
// read concurrently; the only order that matters is the order retirers are
|
|
156
|
+
// RECORDED in, and `inOrder` hands the answers back in input order — the
|
|
157
|
+
// features sorted by `compareIds`, as `listFeatures` returns them.
|
|
158
|
+
for (const found of await inOrder(units, ({ feature, service }) => serviceRetirements(feature, service, context))) {
|
|
159
|
+
for (const part of PARTS) {
|
|
160
|
+
for (const { key, at, to } of found[part])
|
|
161
|
+
record(index[part], key, { by: found.featureId, at, to });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return index;
|
|
165
|
+
}
|
|
166
|
+
/** One archived feature's vacations on one service — only the markers its merge carried out. */
|
|
167
|
+
async function serviceRetirements(feature, service, context) {
|
|
168
|
+
const found = { featureId: feature.id, operations: [], slots: [], messages: [] };
|
|
169
|
+
const paths = featureSpecPaths(feature.dir, service);
|
|
170
|
+
// An absent delta reads as an empty document; an unreadable one retired
|
|
171
|
+
// nothing loam could have merged (archive refuses `merge-failed`).
|
|
172
|
+
const api = await readOpenapi(paths.openapi, context);
|
|
173
|
+
if (!api.unreadable && api.ops.some((op) => op.remove)) {
|
|
174
|
+
const before = await preImage(feature.dir, service, ARTIFACT_FILES.openapi);
|
|
175
|
+
const held = before === undefined ? undefined : openapiFromText(before);
|
|
176
|
+
// The merge's own match: the operation the pre-image held AT the marker's
|
|
177
|
+
// slot carries the marker's id. Markers that missed are dropped before the
|
|
178
|
+
// rule runs, so a relocation whose old-slot marker missed is no
|
|
179
|
+
// relocation either — it moved nothing out.
|
|
180
|
+
const carried = api.ops.filter((op) => !op.remove ||
|
|
181
|
+
held === undefined ||
|
|
182
|
+
held.unreadable ||
|
|
183
|
+
held.ops.some((h) => !h.remove && h.id === op.id && h.method === op.method && h.path === op.path));
|
|
184
|
+
// Every carried marker stands on an address it vacated. The relocation
|
|
185
|
+
// rule says which kind: an id in `net` is retired, and one the rule took
|
|
186
|
+
// out of `net` moved — the delta's definitions of it are where it went.
|
|
187
|
+
const { net } = operationRetirements(carried);
|
|
188
|
+
for (const op of carried) {
|
|
189
|
+
if (!op.remove)
|
|
190
|
+
continue;
|
|
191
|
+
const to = net.has(op.id) ? [] : carried.filter((d) => !d.remove && d.id === op.id).map(operationAddress);
|
|
192
|
+
found.operations.push({ key: retiredKey(service, op.id), at: operationAddress(op), to });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!existsSync(paths.asyncapi))
|
|
196
|
+
return found;
|
|
197
|
+
const events = await readAsyncapi(paths.asyncapi, context);
|
|
198
|
+
const text = events.unreadable ? undefined : await readOr(paths.asyncapi, context);
|
|
199
|
+
if (text === undefined)
|
|
200
|
+
return found;
|
|
201
|
+
const eventSlots = asyncapiSlots(parse(text));
|
|
202
|
+
if (!eventSlots.some((s) => s.remove))
|
|
203
|
+
return found;
|
|
204
|
+
const before = await preImageEvents(feature.dir, service);
|
|
205
|
+
// Presence is the whole match for a channel or operation slot — its
|
|
206
|
+
// identity IS the key. A message marker must also name the message the
|
|
207
|
+
// pre-image declared at that key, the merge's exactness on the message axis.
|
|
208
|
+
const carriedSlots = eventSlots.filter((s) => !s.remove || before === undefined || before.slots.has(slotKey(s)));
|
|
209
|
+
const carriedEvents = {
|
|
210
|
+
...events,
|
|
211
|
+
messages: events.messages.filter((m) => m.remove !== true ||
|
|
212
|
+
before === undefined ||
|
|
213
|
+
before.doc.messages.some((b) => b.remove !== true && b.slot === m.slot && b.name === m.name)),
|
|
214
|
+
};
|
|
215
|
+
for (const slot of carriedSlots) {
|
|
216
|
+
if (!slot.remove || slot.section === "components.messages")
|
|
217
|
+
continue;
|
|
218
|
+
found.slots.push({ key: retiredKey(service, slot.section, slot.key), at: `${slot.section}.${slot.key}`, to: [] });
|
|
219
|
+
}
|
|
220
|
+
// The operation axis's rule on the message name: a vacated declaration
|
|
221
|
+
// whose name the delta declares again was relocated — to another key, or
|
|
222
|
+
// inline under a channel — and those declarations are where it went.
|
|
223
|
+
const retired = messageRetirements(carriedEvents, carriedSlots, before?.doc ?? NOTHING_DECLARED);
|
|
224
|
+
for (const m of retired.vacated) {
|
|
225
|
+
const to = retired.net.has(m.name)
|
|
226
|
+
? []
|
|
227
|
+
: carriedEvents.messages.filter((d) => d.remove !== true && d.name === m.name).map((d) => d.slot);
|
|
228
|
+
found.messages.push({ key: retiredKey(service, m.name), at: m.slot, to });
|
|
229
|
+
}
|
|
230
|
+
return found;
|
|
231
|
+
}
|
|
232
|
+
const NOTHING_DECLARED = asyncapiFromText("");
|
|
233
|
+
/**
|
|
234
|
+
* The contract one artifact held as this feature's merge found it, or
|
|
235
|
+
* undefined when the snapshot holds nothing to trust. An artifact the merge
|
|
236
|
+
* created, or never wrote, lost nothing to it — which is exactly what an empty
|
|
237
|
+
* pre-image says: every marker in the delta missed, and no channel took inline
|
|
238
|
+
* names with it.
|
|
239
|
+
*/
|
|
240
|
+
async function preImage(featureDir, service, artifact) {
|
|
241
|
+
const found = await readSnapshotPreImage(featureDir, { service, artifact });
|
|
242
|
+
if (found === null)
|
|
243
|
+
return undefined;
|
|
244
|
+
return found.kind === "held" ? found.text : "";
|
|
245
|
+
}
|
|
246
|
+
/** The event pre-image as the reader and the slot walk see it — undefined when absent or unreadable. */
|
|
247
|
+
async function preImageEvents(featureDir, service) {
|
|
248
|
+
const text = await preImage(featureDir, service, ARTIFACT_FILES.asyncapi);
|
|
249
|
+
if (text === undefined)
|
|
250
|
+
return undefined;
|
|
251
|
+
const doc = asyncapiFromText(text);
|
|
252
|
+
return doc.unreadable ? undefined : { doc, slots: slotsByKey(asyncapiSlots(parse(text))) };
|
|
253
|
+
}
|
|
254
|
+
function isDirectory(path) {
|
|
255
|
+
try {
|
|
256
|
+
return statSync(path).isDirectory();
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Add one vacation. Already in id order without a sort: the walk records the
|
|
264
|
+
* features in `listFeatures`' `compareIds` order. Distinct per feature,
|
|
265
|
+
* address and kind, because two archived directories can carry one feature
|
|
266
|
+
* id (the id is read off the directory name), and "archived FEAT-7 and
|
|
267
|
+
* FEAT-7" names one feature twice — a second directory's move away from the
|
|
268
|
+
* same address widens that feature's destinations instead.
|
|
269
|
+
*/
|
|
270
|
+
function record(into, key, vacated) {
|
|
271
|
+
const all = into.get(key);
|
|
272
|
+
if (all === undefined) {
|
|
273
|
+
into.set(key, [vacated]);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const same = (v) => v.by === vacated.by && v.at === vacated.at && (v.to.length === 0) === (vacated.to.length === 0);
|
|
277
|
+
if (!all.some(same)) {
|
|
278
|
+
into.set(key, [...all, vacated]);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const widen = (v) => (same(v) ? { ...v, to: [...new Set([...v.to, ...vacated.to])] } : v);
|
|
282
|
+
const [first, ...rest] = all;
|
|
283
|
+
into.set(key, [widen(first), ...rest.map(widen)]);
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=archived.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archived.js","sourceRoot":"","sources":["../../../../src/core/coherence/retired/archived.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The retired index: what every ARCHIVED feature took out of the fleet's living\n * contracts, and who took it.\n *\n * The question it answers is one the living contract cannot: is an operation\n * the living contract does not have NEW, or was it RETIRED? Both read as\n * absent. Before this index existed the feature gate could only treat absent\n * as new — so a delta quoting an operation that a parallel feature retired and\n * archived first was graded as an addition (`api.op-unconsumed`, advisory) and\n * archived at exit 0, putting the retired operation straight back into the\n * living contract. The baseline gate's unpinned count (openapi/baseline/gate.ts)\n * could not see it either: it counts only quotes of operations the living\n * contract still has, so that a genuinely new operation never trips it.\n *\n * The record of a retirement is files, not history. An archived feature keeps\n * its delta under `features/archive/<FEAT>/specs/<svc>/`, and the removal\n * markers in it say what that merge was ASKED to delete — read through the\n * same rule the gate grades an in-flight delta by (./retirement.ts), so\n * relocations are not retirements here either. No git: the docs semantics stay\n * a property of the tree.\n *\n * Asked is not done. The OpenAPI merge deletes only when the living operation\n * AT the marker's method and path carries the marker's operationId\n * (openapi/merge/merge.ts); the event merge deletes a channel or operation slot\n * only when the living contract has that key, and a `components.messages` slot\n * only when the message declared there carries the marker's name\n * (asyncapi/merge/merge.ts). A marker that missed — `remove-target-missing` or\n * `remove-target-mismatch`, archived past under `--approve` — deleted nothing,\n * and counting it called a genuinely new operation a revival and refused it.\n * So every marker is checked against the contract the merge ran against: the\n * feature's snapshot pre-image (`.loam-before/`, staging/snapshot/manifest.ts).\n * A contract the archive CREATED, or never wrote at all, lost nothing to that\n * merge, and reads as an empty pre-image. \"Never wrote\" is the event merge's\n * case alone: it returns no text when it changes nothing (an asyncapi delta\n * whose every marker missed), and archive snapshots only what it writes. The\n * OpenAPI merge returns text whenever the delta has a path entry, so a\n * contract it left unchanged has an ordinary pre-image equal to what it\n * wrote — read like any other, and every missed marker misses it again. A\n * feature with no intact snapshot (archived before the snapshot existed, or\n * the snapshot edited or removed since) cannot be checked, and its markers\n * count as written — what the index did before it read pre-images at all.\n *\n * The pre-image answers one more thing the delta does not spell: a\n * CHANNEL-slot removal retires the messages its living channel declared\n * inline, and those names exist only in the contract the merge ran against. A\n * feature archived without a pre-image contributes its channel slot and not\n * the inline names.\n *\n * What the index records is every ADDRESS an archived feature vacated, per\n * operationId or message name: where each executed marker stood, whether the\n * relocation rule calls it a retirement or a move. A relocation retires\n * nothing, but it does vacate an address: the id lives on at its new slot,\n * and a later delta quoting the old one would put it back there too — one\n * operationId at two addresses, which the merge cannot tell from an addition\n * any more than it can a retired operation, and which the slot-keyed\n * baseline gate and an id-keyed check both miss (the id is living, the slot\n * is empty). A retirement vacates its address the same way once a later\n * feature re-adds the id somewhere else: indexed by id alone, as this once\n * was, a quote of the retired address read as harmless because the id was\n * living, and archived the id at both. So a vacation carries its address,\n * and for a move where the delta put the entry — kept, because where it\n * lives NOW may be somewhere a later feature put it (`Vacated`). The event\n * axis vacates message addresses the same two ways: a `components.messages`\n * marker, and a channel removal taking its inline declarations. A channel or\n * operation slot's identity IS its key, so a \"moved\" channel is a retired key\n * plus a new one, and the slot index catches a quote of the old key.\n *\n * What it cannot see, and SCHEMA.md says so: a removal made by hand-editing a\n * living contract leaves no marker anywhere, and an archived feature deleted\n * from disk takes its markers with it. Both read as \"never retired\" — the gate\n * falls back to calling the entry new, which is what it did before.\n *\n * OpenAPI markers count only when they name an operationId. A marker with\n * none (`openapi.remove-marker-anonymous`) or one written at PATH level\n * (`openapi.remove-marker-path-level`) retires nothing at archive — the merge\n * matches a marker to the living operation by the operationId it names, at\n * its own method and path, and these name none — so an archived feature that\n * carried one under `--approve` left the operation living, and indexing its\n * slot would attribute a retirement that never happened.\n */\nimport { existsSync, statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { parse } from \"yaml\";\nimport { inOrder, memo } from \"../../kernel/concurrency.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../../kernel/ids/service.js\";\nimport type { FeatureEntry } from \"../../repo/entries.js\";\nimport { archiveDir, ARTIFACT_FILES, featureSpecPaths } from \"../../repo/paths.js\";\nimport { listFeatures } from \"../../repo/repo.js\";\nimport { openapiFromText, readOpenapi } from \"../../openapi/doc.js\";\nimport { readOr } from \"../../openapi/contract-text.js\";\nimport { asyncapiSlots, slotKey, slotsByKey, type AsyncapiSlot } from \"../../asyncapi/digest.js\";\nimport { asyncapiFromText, readAsyncapi } from \"../../asyncapi/read.js\";\nimport type { AsyncapiDoc } from \"../../asyncapi/model.js\";\nimport { readSnapshotPreImage } from \"../../staging/snapshot/manifest.js\";\nimport type { FleetContext } from \"../../fleet-context.js\";\nimport { messageRetirements, operationRetirements } from \"./retirement.js\";\n\n/**\n * One archived feature taking one entry out of one address: a retirement\n * (`to` empty) or a move (`to`, every address its delta put the entry at).\n * A move's destination is recorded apart from where the entry lives NOW,\n * because the two part ways as soon as anything lands after the move — A\n * moves an operation from /x to /y, B retires it at /y, D re-adds it at /z —\n * and a finding that named the living address as the mover's destination\n * said \"A moved it to /z\", an address A never wrote.\n */\nexport interface Vacated {\n by: string;\n /** `METHOD /path` for an operation, the slot for a message, `<section>.<key>` for a channel or operation slot. */\n at: string;\n to: readonly string[];\n}\n\n/**\n * Every vacation of one entry, in feature id order and distinct per feature,\n * address and kind — never empty, because an entry is recorded only together\n * with its first vacation. The tuple says so to the compiler, so the one\n * sentence that names the features (./revival/revived.ts) cannot be handed nobody.\n */\nexport type Vacations = readonly [Vacated, ...Vacated[]];\n\n/** `METHOD /path` — how the index records, and every finding names, an operation's address. */\nexport function operationAddress(op: { method: string; path: string }): string {\n return `${op.method.toUpperCase()} ${op.path}`;\n}\n\n/** Everything the archived features retired or moved away, each entry naming the features that did it and where. */\nexport interface RetiredIndex {\n /** `retiredKey(service, operationId)` → every address an archived openapi delta took it out of. */\n operations: ReadonlyMap<string, Vacations>;\n /** `retiredKey(service, section, key)` for a `channels` or `operations` slot — always a retirement. */\n slots: ReadonlyMap<string, Vacations>;\n /** `retiredKey(service, messageName)` — by the join token, every slot it was taken out of. */\n messages: ReadonlyMap<string, Vacations>;\n}\n\n/** The index's one key spelling. NUL-joined: no id, section, key or message name can contain it. */\nexport function retiredKey(...parts: string[]): string {\n return parts.join(\"\\0\");\n}\n\n/** The index, built at most once per caller — what the gate hands both contract axes. */\nexport type RetiredLookup = () => Promise<RetiredIndex>;\n\n/**\n * One index per invocation, keyed by the invocation's own FleetContext.\n *\n * A module-level cache is normally the wrong shape (AGENTS.md: a value cached\n * at import time leaks across tests and, in a long-running host, across\n * invocations). This one is the `c4/resolve/service.ts` exception for the same\n * reason: the key is the per-invocation context object, so two runs can never\n * see each other's answer and the entry dies with the context. It is here\n * rather than a memo on FleetContext because that class sits at L6 and this\n * package at L8 — the import that memo would need points the wrong way up the\n * DAG (docs/DESIGN.md's level table) — while the reads underneath (the feature\n * list, every contract parse) already go through the context's own memos.\n */\nconst PER_INVOCATION = new WeakMap<FleetContext, Map<string, Promise<RetiredIndex>>>();\n\n/**\n * A lazy handle on the index: nothing is read until a grader has a candidate\n * to ask about — an entry the merged contract answers at an address its\n * living contract does not, which is exactly the shape the living contract\n * alone cannot call new or revived. A feature that only edits what the living\n * contract already holds never walks `features/archive/`; one that adds\n * anything genuinely new does, since an addition and a revival read the same\n * until the walk answers. Without a context (archive's gate) the handle\n * memoises for its own lifetime, which is one `featureCoherence` call, so the\n * two contract axes still share one walk.\n */\nexport function retiredLookup(docsDir: DocsDir, context?: FleetContext): RetiredLookup {\n let own: Promise<RetiredIndex> | undefined;\n return () => {\n if (context === undefined) return (own ??= readRetiredIndex(docsDir));\n let store = PER_INVOCATION.get(context);\n if (store === undefined) PER_INVOCATION.set(context, (store = new Map()));\n return memo(store, resolve(docsDir), () => readRetiredIndex(docsDir, context));\n };\n}\n\n/** One vacation, as one archived feature's walk finds it: the index key of the entry, the address, and any destinations. */\ninterface Found {\n key: string;\n at: string;\n to: string[];\n}\n\n/** What one archived feature's deltas for one service took out, per index part. */\ninterface ServiceRetirements {\n featureId: string;\n operations: Found[];\n slots: Found[];\n messages: Found[];\n}\n\n/** The index's parts, in the one order `readRetiredIndex` records them. */\nconst PARTS = [\"operations\", \"slots\", \"messages\"] as const;\n\n/** Walk every archived feature's contract deltas and record every address each one vacated. */\nasync function readRetiredIndex(docsDir: DocsDir, context?: FleetContext): Promise<RetiredIndex> {\n const index = {\n operations: new Map<string, Vacations>(),\n slots: new Map<string, Vacations>(),\n messages: new Map<string, Vacations>(),\n };\n // A `features/archive` that is not a directory holds no archived feature,\n // and listing it would throw ENOTDIR out of the feature gate — refusing an\n // archive over a question about OTHER features' history, before the plan\n // that names the real problem (the final move into that path) is reached.\n if (!isDirectory(archiveDir(docsDir))) return index;\n const archived = (await listFeatures(docsDir, { includeArchived: true }, context)).filter((f) => f.archived);\n const units = archived.flatMap((feature) => feature.services.map((service) => ({ feature, service })));\n // Every (feature, service) pair is independent of every other, so they are\n // read concurrently; the only order that matters is the order retirers are\n // RECORDED in, and `inOrder` hands the answers back in input order — the\n // features sorted by `compareIds`, as `listFeatures` returns them.\n for (const found of await inOrder(units, ({ feature, service }) => serviceRetirements(feature, service, context))) {\n for (const part of PARTS) {\n for (const { key, at, to } of found[part]) record(index[part], key, { by: found.featureId, at, to });\n }\n }\n return index;\n}\n\n/** One archived feature's vacations on one service — only the markers its merge carried out. */\nasync function serviceRetirements(\n feature: FeatureEntry,\n service: RawServiceId,\n context: FleetContext | undefined,\n): Promise<ServiceRetirements> {\n const found: ServiceRetirements = { featureId: feature.id, operations: [], slots: [], messages: [] };\n const paths = featureSpecPaths(feature.dir, service);\n // An absent delta reads as an empty document; an unreadable one retired\n // nothing loam could have merged (archive refuses `merge-failed`).\n const api = await readOpenapi(paths.openapi, context);\n if (!api.unreadable && api.ops.some((op) => op.remove)) {\n const before = await preImage(feature.dir, service, ARTIFACT_FILES.openapi);\n const held = before === undefined ? undefined : openapiFromText(before);\n // The merge's own match: the operation the pre-image held AT the marker's\n // slot carries the marker's id. Markers that missed are dropped before the\n // rule runs, so a relocation whose old-slot marker missed is no\n // relocation either — it moved nothing out.\n const carried = api.ops.filter(\n (op) =>\n !op.remove ||\n held === undefined ||\n held.unreadable ||\n held.ops.some((h) => !h.remove && h.id === op.id && h.method === op.method && h.path === op.path),\n );\n // Every carried marker stands on an address it vacated. The relocation\n // rule says which kind: an id in `net` is retired, and one the rule took\n // out of `net` moved — the delta's definitions of it are where it went.\n const { net } = operationRetirements(carried);\n for (const op of carried) {\n if (!op.remove) continue;\n const to = net.has(op.id) ? [] : carried.filter((d) => !d.remove && d.id === op.id).map(operationAddress);\n found.operations.push({ key: retiredKey(service, op.id), at: operationAddress(op), to });\n }\n }\n if (!existsSync(paths.asyncapi)) return found;\n const events = await readAsyncapi(paths.asyncapi, context);\n const text = events.unreadable ? undefined : await readOr(paths.asyncapi, context);\n if (text === undefined) return found;\n const eventSlots = asyncapiSlots(parse(text));\n if (!eventSlots.some((s) => s.remove)) return found;\n const before = await preImageEvents(feature.dir, service);\n // Presence is the whole match for a channel or operation slot — its\n // identity IS the key. A message marker must also name the message the\n // pre-image declared at that key, the merge's exactness on the message axis.\n const carriedSlots = eventSlots.filter((s) => !s.remove || before === undefined || before.slots.has(slotKey(s)));\n const carriedEvents: AsyncapiDoc = {\n ...events,\n messages: events.messages.filter(\n (m) =>\n m.remove !== true ||\n before === undefined ||\n before.doc.messages.some((b) => b.remove !== true && b.slot === m.slot && b.name === m.name),\n ),\n };\n for (const slot of carriedSlots) {\n if (!slot.remove || slot.section === \"components.messages\") continue;\n found.slots.push({ key: retiredKey(service, slot.section, slot.key), at: `${slot.section}.${slot.key}`, to: [] });\n }\n // The operation axis's rule on the message name: a vacated declaration\n // whose name the delta declares again was relocated — to another key, or\n // inline under a channel — and those declarations are where it went.\n const retired = messageRetirements(carriedEvents, carriedSlots, before?.doc ?? NOTHING_DECLARED);\n for (const m of retired.vacated) {\n const to = retired.net.has(m.name)\n ? []\n : carriedEvents.messages.filter((d) => d.remove !== true && d.name === m.name).map((d) => d.slot);\n found.messages.push({ key: retiredKey(service, m.name), at: m.slot, to });\n }\n return found;\n}\n\nconst NOTHING_DECLARED: AsyncapiDoc = asyncapiFromText(\"\");\n\n/**\n * The contract one artifact held as this feature's merge found it, or\n * undefined when the snapshot holds nothing to trust. An artifact the merge\n * created, or never wrote, lost nothing to it — which is exactly what an empty\n * pre-image says: every marker in the delta missed, and no channel took inline\n * names with it.\n */\nasync function preImage(featureDir: string, service: string, artifact: string): Promise<string | undefined> {\n const found = await readSnapshotPreImage(featureDir, { service, artifact });\n if (found === null) return undefined;\n return found.kind === \"held\" ? found.text : \"\";\n}\n\n/** The event pre-image as the reader and the slot walk see it — undefined when absent or unreadable. */\nasync function preImageEvents(\n featureDir: string,\n service: string,\n): Promise<{ doc: AsyncapiDoc; slots: ReadonlyMap<string, AsyncapiSlot> } | undefined> {\n const text = await preImage(featureDir, service, ARTIFACT_FILES.asyncapi);\n if (text === undefined) return undefined;\n const doc = asyncapiFromText(text);\n return doc.unreadable ? undefined : { doc, slots: slotsByKey(asyncapiSlots(parse(text))) };\n}\n\nfunction isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Add one vacation. Already in id order without a sort: the walk records the\n * features in `listFeatures`' `compareIds` order. Distinct per feature,\n * address and kind, because two archived directories can carry one feature\n * id (the id is read off the directory name), and \"archived FEAT-7 and\n * FEAT-7\" names one feature twice — a second directory's move away from the\n * same address widens that feature's destinations instead.\n */\nfunction record(into: Map<string, Vacations>, key: string, vacated: Vacated): void {\n const all = into.get(key);\n if (all === undefined) {\n into.set(key, [vacated]);\n return;\n }\n const same = (v: Vacated): boolean =>\n v.by === vacated.by && v.at === vacated.at && (v.to.length === 0) === (vacated.to.length === 0);\n if (!all.some(same)) {\n into.set(key, [...all, vacated]);\n return;\n }\n const widen = (v: Vacated): Vacated => (same(v) ? { ...v, to: [...new Set([...v.to, ...vacated.to])] } : v);\n const [first, ...rest] = all;\n into.set(key, [widen(first), ...rest.map(widen)]);\n}\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One service's pair of event contracts — the feature delta and the living
|
|
3
|
+
* contract — read the one way every slot judgement on the event axis reads
|
|
4
|
+
* them.
|
|
5
|
+
*
|
|
6
|
+
* Two readers build it, and they must never disagree: the feature gate's walk
|
|
7
|
+
* (../events/declared.ts), which grades baselines and markers and collects the
|
|
8
|
+
* revival candidates, and `serviceRevivals` (./revival/revived.ts), which asks the
|
|
9
|
+
* revival question standalone for `loam rebase`. The second used to re-spell
|
|
10
|
+
* the first's guards — existence, decode, an unreadable living side — and its
|
|
11
|
+
* own living-slot map, so a change to the gate's guard would have left rebase
|
|
12
|
+
* and validate giving two answers about one tree with no test comparing them.
|
|
13
|
+
*
|
|
14
|
+
* Here, in retired/, because it is the lowest package both readers import: the
|
|
15
|
+
* event walk already imports this package (for the revival candidates), and a
|
|
16
|
+
* value edge from here up into events/ would be a package cycle. The merge
|
|
17
|
+
* simulation (./merged.ts) is here for the same reason, and rides on the pair
|
|
18
|
+
* as `merged()`: the walk grades the post-archive contract with it, and the
|
|
19
|
+
* revival check compares it with the living contract — one simulation per
|
|
20
|
+
* pair, whichever asks first.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync } from "node:fs";
|
|
23
|
+
import { parse } from "yaml";
|
|
24
|
+
import { readOr } from "../../openapi/contract-text.js";
|
|
25
|
+
import { asyncapiSlots, slotsByKey } from "../../asyncapi/digest.js";
|
|
26
|
+
import { mergedAsyncapiDoc } from "./merged.js";
|
|
27
|
+
/**
|
|
28
|
+
* Read both sides' texts and slots. Each text is parsed ONCE and kept: the
|
|
29
|
+
* slot walk and the surface half of the baseline grade read the same resolved
|
|
30
|
+
* tree, and a second `parse` of the same bytes is a second chance for the two
|
|
31
|
+
* halves to disagree about what the delta declares.
|
|
32
|
+
*/
|
|
33
|
+
export async function readEventPair(sides, context) {
|
|
34
|
+
const { featPath, featDoc, livingPath, livingDoc, service } = sides;
|
|
35
|
+
const featText = sides.featText ?? (await readOr(featPath, context));
|
|
36
|
+
const featPlain = featText === undefined ? {} : parse(featText);
|
|
37
|
+
const livingExists = existsSync(livingPath);
|
|
38
|
+
const livingText = !livingExists || livingDoc.unreadable ? undefined : await readOr(livingPath, context);
|
|
39
|
+
const livingPlain = livingText === undefined ? {} : parse(livingText);
|
|
40
|
+
const livingAnswers = livingText !== undefined || !livingExists;
|
|
41
|
+
// Lazy: most pairs never need it on the rebase path, and the walk asks for
|
|
42
|
+
// it once. Boxed, so an `undefined` answer is remembered too.
|
|
43
|
+
let simulated;
|
|
44
|
+
const merged = () => (simulated ??= {
|
|
45
|
+
merged: !livingAnswers || featText === undefined
|
|
46
|
+
? undefined
|
|
47
|
+
: mergedAsyncapiDoc({ livingText, livingDoc, livingRoot: livingPlain, featureText: featText, service }),
|
|
48
|
+
}).merged;
|
|
49
|
+
return {
|
|
50
|
+
featDoc,
|
|
51
|
+
featSlots: asyncapiSlots(featPlain),
|
|
52
|
+
featPlain,
|
|
53
|
+
featText,
|
|
54
|
+
livingDoc,
|
|
55
|
+
livingSlots: slotsByKey(asyncapiSlots(livingPlain)),
|
|
56
|
+
livingText,
|
|
57
|
+
livingPlain,
|
|
58
|
+
livingAnswers,
|
|
59
|
+
merged,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=event-pair.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-pair.js","sourceRoot":"","sources":["../../../../src/core/coherence/retired/event-pair.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAqB,MAAM,0BAA0B,CAAC;AAGxF,OAAO,EAAE,iBAAiB,EAAqB,MAAM,aAAa,CAAC;AAiEnE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAgB,EAAE,OAAsB;IAC1E,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IACpE,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,CAAC,MAAM,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE,MAAM,SAAS,GAAY,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzE,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,CAAC,YAAY,IAAI,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACzG,MAAM,WAAW,GAAY,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/E,MAAM,aAAa,GAAG,UAAU,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC;IAChE,2EAA2E;IAC3E,8DAA8D;IAC9D,IAAI,SAA2D,CAAC;IAChE,MAAM,MAAM,GAAG,GAA6B,EAAE,CAC5C,CAAC,SAAS,KAAK;QACb,MAAM,EAAE,CAAC,aAAa,IAAI,QAAQ,KAAK,SAAS;YAC9C,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,iBAAiB,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;KAC1G,CAAC,CAAC,MAAM,CAAC;IACZ,OAAO;QACL,OAAO;QACP,SAAS,EAAE,aAAa,CAAC,SAAS,CAAC;QACnC,SAAS;QACT,QAAQ;QACR,SAAS;QACT,WAAW,EAAE,UAAU,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;QACnD,UAAU;QACV,WAAW;QACX,aAAa;QACb,MAAM;KACP,CAAC;AACJ,CAAC","sourcesContent":["/**\n * One service's pair of event contracts — the feature delta and the living\n * contract — read the one way every slot judgement on the event axis reads\n * them.\n *\n * Two readers build it, and they must never disagree: the feature gate's walk\n * (../events/declared.ts), which grades baselines and markers and collects the\n * revival candidates, and `serviceRevivals` (./revival/revived.ts), which asks the\n * revival question standalone for `loam rebase`. The second used to re-spell\n * the first's guards — existence, decode, an unreadable living side — and its\n * own living-slot map, so a change to the gate's guard would have left rebase\n * and validate giving two answers about one tree with no test comparing them.\n *\n * Here, in retired/, because it is the lowest package both readers import: the\n * event walk already imports this package (for the revival candidates), and a\n * value edge from here up into events/ would be a package cycle. The merge\n * simulation (./merged.ts) is here for the same reason, and rides on the pair\n * as `merged()`: the walk grades the post-archive contract with it, and the\n * revival check compares it with the living contract — one simulation per\n * pair, whichever asks first.\n */\nimport { existsSync } from \"node:fs\";\nimport { parse } from \"yaml\";\nimport { readOr } from \"../../openapi/contract-text.js\";\nimport { asyncapiSlots, slotsByKey, type AsyncapiSlot } from \"../../asyncapi/digest.js\";\nimport type { AsyncapiDoc } from \"../../asyncapi/model.js\";\nimport type { FleetContext } from \"../../fleet-context.js\";\nimport { mergedAsyncapiDoc, type MergedEvents } from \"./merged.js\";\n\n/** The four facts every slot judgement reads: each side as the reader parsed it, and as its slots. */\nexport interface EventPair {\n featDoc: AsyncapiDoc;\n featSlots: AsyncapiSlot[];\n livingDoc: AsyncapiDoc;\n /** The living contract's slots by `slotKey`; empty where it is absent or did not read. */\n livingSlots: Map<string, AsyncapiSlot>;\n}\n\n/** The pair, plus the texts and trees the gate's other graders read from the same bytes. */\nexport interface EventPairRead extends EventPair {\n /** The feature delta's resolved parse — `{}` when its text did not decode. */\n featPlain: unknown;\n featText: string | undefined;\n /** The living contract's text — undefined when it is ABSENT or did not read; `livingAnswers` tells the two apart. */\n livingText: string | undefined;\n /** The living contract's resolved parse; `{}` where there is no living text. */\n livingPlain: unknown;\n /**\n * Can anything be graded against the living side? Absent is a real answer —\n * everything the delta spells is new, and a baseline only means something\n * for a slot that already exists. Unreadable is NOT an answer: grading pins,\n * markers or revivals against an empty parse would call every restatement\n * new and every marker stale, so each of those checks stands down and the\n * (later) merge refuses the unreadable side by name.\n */\n livingAnswers: boolean;\n /**\n * The living contract as the archive would leave it (./merged.ts), computed\n * on first ask and kept — the create branch's document where no living\n * contract exists; undefined where the living side does not answer, or the\n * merge refuses the pair. The walk asks it only with a living text in hand.\n * Its tree rides along for the one reader that follows a `$ref` back.\n */\n merged(): MergedEvents | undefined;\n}\n\n/**\n * Where one service's two contracts of one axis live, and their parses — read\n * by the caller, which needs them before this. The OpenAPI revival read\n * (./revival/arrivals.ts) takes the same shape.\n */\nexport interface PairSides<Doc = AsyncapiDoc> {\n featPath: string;\n /** Readable: an unreadable feature delta is `asyncapi.invalid` (`openapi.invalid`), and nothing is graded against it. */\n featDoc: Doc;\n livingPath: string;\n livingDoc: Doc;\n /** The service the pair belongs to — what a refusal of the simulated merge would name. */\n service: string;\n /**\n * The delta's text when the caller holds a version of it that is not on\n * disk yet — `loam rebase`'s, with the pins it is about to write; read from\n * `featPath` when absent. The merge's quote/edit verdict turns on the pins,\n * so the revival question asked of the delta as it stood before a repin can\n * answer for a document rebase is about to replace: a quote pinned to its\n * own content is skipped, and repinned over another operation it is an edit\n * that lands. A pin changes no identity or address, so `featDoc`, read from\n * disk, still names what the text declares.\n */\n featText?: string;\n}\n\n/**\n * Read both sides' texts and slots. Each text is parsed ONCE and kept: the\n * slot walk and the surface half of the baseline grade read the same resolved\n * tree, and a second `parse` of the same bytes is a second chance for the two\n * halves to disagree about what the delta declares.\n */\nexport async function readEventPair(sides: PairSides, context?: FleetContext): Promise<EventPairRead> {\n const { featPath, featDoc, livingPath, livingDoc, service } = sides;\n const featText = sides.featText ?? (await readOr(featPath, context));\n const featPlain: unknown = featText === undefined ? {} : parse(featText);\n const livingExists = existsSync(livingPath);\n const livingText = !livingExists || livingDoc.unreadable ? undefined : await readOr(livingPath, context);\n const livingPlain: unknown = livingText === undefined ? {} : parse(livingText);\n const livingAnswers = livingText !== undefined || !livingExists;\n // Lazy: most pairs never need it on the rebase path, and the walk asks for\n // it once. Boxed, so an `undefined` answer is remembered too.\n let simulated: { merged: MergedEvents | undefined } | undefined;\n const merged = (): MergedEvents | undefined =>\n (simulated ??= {\n merged: !livingAnswers || featText === undefined\n ? undefined\n : mergedAsyncapiDoc({ livingText, livingDoc, livingRoot: livingPlain, featureText: featText, service }),\n }).merged;\n return {\n featDoc,\n featSlots: asyncapiSlots(featPlain),\n featPlain,\n featText,\n livingDoc,\n livingSlots: slotsByKey(asyncapiSlots(livingPlain)),\n livingText,\n livingPlain,\n livingAnswers,\n merged,\n };\n}\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract as the feature would leave it: the REAL merge
|
|
3
|
+
* (core/asyncapi/merge/merge.ts, core/openapi/merge/merge.ts) run over the
|
|
4
|
+
* two texts and read back, nothing written. On the event axis one simulation
|
|
5
|
+
* feeds three gate questions — the `declares()` join in ../events/events.ts
|
|
6
|
+
* (a retired or edited-away send must stop answering "the service declares
|
|
7
|
+
* this" BEFORE archive, or a leftover `Publishes:` line passes the gate and
|
|
8
|
+
* breaks the post-archive fleet), the consumer gate's wire half (a channel or
|
|
9
|
+
* operation removal that stops the service SENDING a message the fleet still
|
|
10
|
+
* consumes), and the revival check (./revival/arrivals.ts) — which asks it on both
|
|
11
|
+
* axes: where does each operationId and message name stand once the delta
|
|
12
|
+
* merges, against where it stands now.
|
|
13
|
+
*
|
|
14
|
+
* Calling the merge rather than re-deriving its verdicts is the point: a
|
|
15
|
+
* second spelling of quote/edit/removal here would eventually disagree with
|
|
16
|
+
* the merge about what archive actually writes, and the gate would be
|
|
17
|
+
* promising a contract the archive does not produce. The revival check is
|
|
18
|
+
* the proof: it re-derived the merge case by case — a relocation marker
|
|
19
|
+
* anywhere, a swap by a named operation, a channel restated with a `$ref` —
|
|
20
|
+
* and every review found a shape the derivation missed (a marker at an
|
|
21
|
+
* address the id never lived at, a replacement with no operationId).
|
|
22
|
+
*
|
|
23
|
+
* A living contract that does not exist is the archive's CREATE branch: it
|
|
24
|
+
* writes the delta with its markers stripped, and that is what the merged
|
|
25
|
+
* contract is.
|
|
26
|
+
*/
|
|
27
|
+
import { parse } from "yaml";
|
|
28
|
+
import { asyncapiDocOf } from "../../asyncapi/read.js";
|
|
29
|
+
import { AsyncapiMergeError } from "../../asyncapi/merge/error.js";
|
|
30
|
+
import { stripAsyncapiMarkers } from "../../asyncapi/merge/markers.js";
|
|
31
|
+
import { mergeAsyncapiSlots } from "../../asyncapi/merge/merge.js";
|
|
32
|
+
import { openapiFromText } from "../../openapi/doc.js";
|
|
33
|
+
import { OpenapiMergeError } from "../../openapi/merge/error.js";
|
|
34
|
+
import { stripOpenapiRemovalMarkers } from "../../openapi/merge/markers.js";
|
|
35
|
+
import { mergeOpenapiPaths } from "../../openapi/merge/merge.js";
|
|
36
|
+
/**
|
|
37
|
+
* Simulate the archive-time event merge, or stand down. Both documents are
|
|
38
|
+
* known readable here (the pair, ./event-pair.ts, asks only with the delta's
|
|
39
|
+
* text in hand and a living side that answers — readable, or absent — and
|
|
40
|
+
* both of its readers, the walk in ../events/declared.ts and rebase's
|
|
41
|
+
* `serviceRevivals`, refuse an unreadable delta first), so the only
|
|
42
|
+
* refusals left are the merge's own judgement calls — an aliased section, a
|
|
43
|
+
* section spelling one slot key twice. `undefined` on those rather than a
|
|
44
|
+
* throw: the walk falls back to the feature ∪ living union view, which can
|
|
45
|
+
* only be too lenient, the revival check grades no message, and the
|
|
46
|
+
* archive's own plan surfaces the refusal by name — a refusal `--approve`
|
|
47
|
+
* cannot pass, so nothing it would have graded can land.
|
|
48
|
+
*/
|
|
49
|
+
export function mergedAsyncapiDoc(input) {
|
|
50
|
+
const { livingText, livingDoc, livingRoot, featureText, service } = input;
|
|
51
|
+
try {
|
|
52
|
+
const text = livingText === undefined ? stripAsyncapiMarkers(featureText, service) : mergeAsyncapiSlots(livingText, featureText, service).text;
|
|
53
|
+
if (text === null)
|
|
54
|
+
return { doc: livingDoc, root: livingRoot };
|
|
55
|
+
const root = parse(text);
|
|
56
|
+
return { doc: asyncapiDocOf(root), root };
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error instanceof AsyncapiMergeError)
|
|
60
|
+
return undefined;
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** The OpenAPI twin, through the same function archive's plan calls — `undefined` where that merge refuses. */
|
|
65
|
+
export function mergedOpenapiDoc(input) {
|
|
66
|
+
const { livingText, livingDoc, featureText, service } = input;
|
|
67
|
+
try {
|
|
68
|
+
if (livingText === undefined)
|
|
69
|
+
return openapiFromText(stripOpenapiRemovalMarkers(featureText, service));
|
|
70
|
+
const out = mergeOpenapiPaths(livingText, featureText, service);
|
|
71
|
+
return out.text === null ? livingDoc : openapiFromText(out.text);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error instanceof OpenapiMergeError)
|
|
75
|
+
return undefined;
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=merged.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merged.js","sourceRoot":"","sources":["../../../../src/core/coherence/retired/merged.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,eAAe,EAAmB,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAgCjE;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAsB;IACtD,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAC1E,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,oBAAoB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;QAC/I,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QAC/D,MAAM,IAAI,GAAY,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,OAAO,EAAE,GAAG,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,kBAAkB;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,gBAAgB,CAAC,KAAuC;IACtE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAC9D,IAAI,CAAC;QACH,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,eAAe,CAAC,0BAA0B,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACvG,MAAM,GAAG,GAAG,iBAAiB,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;QAChE,OAAO,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,iBAAiB;YAAE,OAAO,SAAS,CAAC;QACzD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["/**\n * The contract as the feature would leave it: the REAL merge\n * (core/asyncapi/merge/merge.ts, core/openapi/merge/merge.ts) run over the\n * two texts and read back, nothing written. On the event axis one simulation\n * feeds three gate questions — the `declares()` join in ../events/events.ts\n * (a retired or edited-away send must stop answering \"the service declares\n * this\" BEFORE archive, or a leftover `Publishes:` line passes the gate and\n * breaks the post-archive fleet), the consumer gate's wire half (a channel or\n * operation removal that stops the service SENDING a message the fleet still\n * consumes), and the revival check (./revival/arrivals.ts) — which asks it on both\n * axes: where does each operationId and message name stand once the delta\n * merges, against where it stands now.\n *\n * Calling the merge rather than re-deriving its verdicts is the point: a\n * second spelling of quote/edit/removal here would eventually disagree with\n * the merge about what archive actually writes, and the gate would be\n * promising a contract the archive does not produce. The revival check is\n * the proof: it re-derived the merge case by case — a relocation marker\n * anywhere, a swap by a named operation, a channel restated with a `$ref` —\n * and every review found a shape the derivation missed (a marker at an\n * address the id never lived at, a replacement with no operationId).\n *\n * A living contract that does not exist is the archive's CREATE branch: it\n * writes the delta with its markers stripped, and that is what the merged\n * contract is.\n */\nimport { parse } from \"yaml\";\nimport { asyncapiDocOf } from \"../../asyncapi/read.js\";\nimport type { AsyncapiDoc } from \"../../asyncapi/model.js\";\nimport { AsyncapiMergeError } from \"../../asyncapi/merge/error.js\";\nimport { stripAsyncapiMarkers } from \"../../asyncapi/merge/markers.js\";\nimport { mergeAsyncapiSlots } from \"../../asyncapi/merge/merge.js\";\nimport { openapiFromText, type OpenapiDoc } from \"../../openapi/doc.js\";\nimport { OpenapiMergeError } from \"../../openapi/merge/error.js\";\nimport { stripOpenapiRemovalMarkers } from \"../../openapi/merge/markers.js\";\nimport { mergeOpenapiPaths } from \"../../openapi/merge/merge.js\";\n\n/** The two texts the simulation merges, plus the living side's existing parse. */\nexport interface MergeSimulationInput<Doc> {\n /** undefined when no living contract exists — the archive then creates one from the delta. */\n livingText: string | undefined;\n /** The reader's parse of livingText — reused verbatim when the merge writes nothing. */\n livingDoc: Doc;\n featureText: string;\n service: string;\n}\n\n/** The event simulation's input: the pair's texts, plus the living side's tree. */\nexport interface EventMergeInput extends MergeSimulationInput<AsyncapiDoc> {\n /** The reader's parse of livingText — the merged tree, verbatim, when the merge writes nothing. */\n livingRoot: unknown;\n}\n\n/** The contract the event merge would leave: as read, and the tree it was read from. */\nexport interface MergedEvents {\n doc: AsyncapiDoc;\n /**\n * The merged document's parse — where a `$ref` can be followed back to the\n * slot holding it. The read model keeps no pointer, and the move-back\n * remedy (./revival/revived.ts) has to name the channels still pointing at\n * a key it tells the author to retire: told only to retire it, an author\n * left a living channel `$ref`-ing nothing and its send operation stopped\n * sending a message the fleet consumes.\n */\n root: unknown;\n}\n\n/**\n * Simulate the archive-time event merge, or stand down. Both documents are\n * known readable here (the pair, ./event-pair.ts, asks only with the delta's\n * text in hand and a living side that answers — readable, or absent — and\n * both of its readers, the walk in ../events/declared.ts and rebase's\n * `serviceRevivals`, refuse an unreadable delta first), so the only\n * refusals left are the merge's own judgement calls — an aliased section, a\n * section spelling one slot key twice. `undefined` on those rather than a\n * throw: the walk falls back to the feature ∪ living union view, which can\n * only be too lenient, the revival check grades no message, and the\n * archive's own plan surfaces the refusal by name — a refusal `--approve`\n * cannot pass, so nothing it would have graded can land.\n */\nexport function mergedAsyncapiDoc(input: EventMergeInput): MergedEvents | undefined {\n const { livingText, livingDoc, livingRoot, featureText, service } = input;\n try {\n const text = livingText === undefined ? stripAsyncapiMarkers(featureText, service) : mergeAsyncapiSlots(livingText, featureText, service).text;\n if (text === null) return { doc: livingDoc, root: livingRoot };\n const root: unknown = parse(text);\n return { doc: asyncapiDocOf(root), root };\n } catch (error) {\n if (error instanceof AsyncapiMergeError) return undefined;\n throw error;\n }\n}\n\n/** The OpenAPI twin, through the same function archive's plan calls — `undefined` where that merge refuses. */\nexport function mergedOpenapiDoc(input: MergeSimulationInput<OpenapiDoc>): OpenapiDoc | undefined {\n const { livingText, livingDoc, featureText, service } = input;\n try {\n if (livingText === undefined) return openapiFromText(stripOpenapiRemovalMarkers(featureText, service));\n const out = mergeOpenapiPaths(livingText, featureText, service);\n return out.text === null ? livingDoc : openapiFromText(out.text);\n } catch (error) {\n if (error instanceof OpenapiMergeError) return undefined;\n throw error;\n }\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A relocation — same operationId, removal marker on the old slot, upsert on
|
|
3
|
+
* the new one — retires nothing. Every rule that asks "is this operation going
|
|
4
|
+
* away" must therefore ask the NET set (markers this feature does not
|
|
5
|
+
* redefine), while "did the author write a marker at all" asks the raw one.
|
|
6
|
+
* Conflating them made moving an endpoint fail three checks at once for a
|
|
7
|
+
* change that removes nothing.
|
|
8
|
+
*
|
|
9
|
+
* A marker with no `operationId` is not here at all: the reader lists it apart
|
|
10
|
+
* (`anonymousRemovals`), and the merge deletes a living operation only when the
|
|
11
|
+
* one at the marker's own method and path carries the id the marker names — a
|
|
12
|
+
* marker naming none matches nothing, so it retires nothing either. That match
|
|
13
|
+
* is also why a marker is a REQUEST rather than a record: one that missed its
|
|
14
|
+
* slot deleted nothing, and the retired index (./archived.ts) checks each
|
|
15
|
+
* against the contract its merge ran against before counting it.
|
|
16
|
+
*/
|
|
17
|
+
export function operationRetirements(ops) {
|
|
18
|
+
const redefined = new Set(ops.filter((op) => !op.remove).map((op) => op.id));
|
|
19
|
+
const markerIds = new Set(ops.filter((op) => op.remove).map((op) => op.id));
|
|
20
|
+
const net = new Set([...markerIds].filter((id) => !redefined.has(id)));
|
|
21
|
+
return { markerIds, net };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A relocation — same message name, marker on the old key, declaration at a
|
|
25
|
+
* new one — retires nothing. "Is this message going away" asks the NET set;
|
|
26
|
+
* "did the author write a marker" asks the raw one, the OpenAPI distinction
|
|
27
|
+
* above verbatim. Two raw sets, because the two marker shapes carry different
|
|
28
|
+
* debts: a message-slot marker must be justified by a REMOVED requirement,
|
|
29
|
+
* while a channel-slot removal needs exactness only (requirements join on
|
|
30
|
+
* message names, never channel keys — SCHEMA.md) and its living inline
|
|
31
|
+
* interior leaves with it.
|
|
32
|
+
*
|
|
33
|
+
* `living` is the contract the delta was merged INTO (or is about to be): a
|
|
34
|
+
* channel removal's inline interior is written there and nowhere in the delta,
|
|
35
|
+
* which carries only the marker. The feature gate passes the living contract
|
|
36
|
+
* on disk; the retired index passes the archived feature's snapshot pre-image,
|
|
37
|
+
* the living contract as it stood the moment that merge ran.
|
|
38
|
+
*/
|
|
39
|
+
export function messageRetirements(featDoc, featSlots, living) {
|
|
40
|
+
const markers = featDoc.messages.filter((m) => m.remove === true && m.slot.startsWith("components.messages."));
|
|
41
|
+
const channelRemovals = featSlots.filter((s) => s.section === "channels" && s.remove).map((s) => s.key);
|
|
42
|
+
const inline = living.messages.filter((m) => channelRemovals.some((ck) => m.slot.startsWith(`channels.${ck}.messages.`)));
|
|
43
|
+
const markerNames = new Set(markers.map((m) => m.name));
|
|
44
|
+
const inlineRetired = new Set(inline.map((m) => m.name));
|
|
45
|
+
const redeclared = new Set(featDoc.messages.filter((m) => m.remove !== true).map((m) => m.name));
|
|
46
|
+
const net = new Set([...markerNames, ...inlineRetired].filter((name) => !redeclared.has(name)));
|
|
47
|
+
return { markerNames, inlineRetired, redeclared, net, vacated: [...markers, ...inline] };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=retirement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retirement.js","sourceRoot":"","sources":["../../../../src/core/coherence/retired/retirement.ts"],"names":[],"mappings":"AA6BA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAyB;IAC5D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,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,CAAC,CAAC;IAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5E,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;AAC5B,CAAC;AAqBD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAoB,EACpB,SAAkC,EAClC,MAAmB;IAEnB,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC;IAC/G,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxG,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1H,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACxD,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACjG,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChG,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;AAC3F,CAAC","sourcesContent":["/**\n * What ONE contract delta retires — the rule, spelled once for every reader\n * that asks it.\n *\n * Two readers ask it of different features, and they must never disagree. The\n * feature gate asks it of the delta being graded (`../declared.ts` for the\n * OpenAPI axis, `../events/declared.ts` for the event axis): which operations\n * and messages is THIS feature taking out of the living contract. The retired\n * index (`./archived.ts`) asks the same question of every feature already in\n * `features/archive/`: which operations and messages did a shipped feature take\n * out, so a later delta spelling one of them is a revival rather than an\n * addition. A second spelling of the relocation rule in the index is how a\n * moved endpoint would read as retired there while the gate called it moved.\n *\n * Moved here from the two walks verbatim, comments and all; the walks keep the\n * checks that USE the sets, this module keeps the sets.\n */\nimport type { Operation } from \"../../openapi/doc.js\";\nimport type { AsyncapiSlot } from \"../../asyncapi/digest.js\";\nimport type { AsyncapiDoc, EventMessage } from \"../../asyncapi/model.js\";\n\n/** The OpenAPI half: every id a removal marker names, and the ones genuinely retired. */\nexport interface OperationRetirements {\n /** Every operationId a removal marker names, relocations included — \"did the author write a marker\". */\n markerIds: Set<string>;\n /** Markers minus relocations — \"is this operation going away\". */\n net: Set<string>;\n}\n\n/**\n * A relocation — same operationId, removal marker on the old slot, upsert on\n * the new one — retires nothing. Every rule that asks \"is this operation going\n * away\" must therefore ask the NET set (markers this feature does not\n * redefine), while \"did the author write a marker at all\" asks the raw one.\n * Conflating them made moving an endpoint fail three checks at once for a\n * change that removes nothing.\n *\n * A marker with no `operationId` is not here at all: the reader lists it apart\n * (`anonymousRemovals`), and the merge deletes a living operation only when the\n * one at the marker's own method and path carries the id the marker names — a\n * marker naming none matches nothing, so it retires nothing either. That match\n * is also why a marker is a REQUEST rather than a record: one that missed its\n * slot deleted nothing, and the retired index (./archived.ts) checks each\n * against the contract its merge ran against before counting it.\n */\nexport function operationRetirements(ops: readonly Operation[]): OperationRetirements {\n const redefined = new Set(ops.filter((op) => !op.remove).map((op) => op.id));\n const markerIds = new Set(ops.filter((op) => op.remove).map((op) => op.id));\n const net = new Set([...markerIds].filter((id) => !redefined.has(id)));\n return { markerIds, net };\n}\n\n/** The event half's four sets — the raw marker shapes, the redeclarations, and what is genuinely going away. */\nexport interface MessageRetirements {\n /** Names a `components.messages` marker retires, relocations included. */\n markerNames: Set<string>;\n /** Names the LIVING contract declares inline under a channel this delta removes. */\n inlineRetired: Set<string>;\n /** Names the delta declares again, anywhere — a marker on one of these is a relocation. */\n redeclared: Set<string>;\n /** `markerNames ∪ inlineRetired`, minus `redeclared`. */\n net: Set<string>;\n /**\n * The declarations behind the first two sets, each where it stood — the\n * `components.messages` markers, and the living inline declarations under\n * a removed channel. What the retired index (./archived.ts) records as the\n * addresses an archived feature vacated, read off this one spelling.\n */\n vacated: EventMessage[];\n}\n\n/**\n * A relocation — same message name, marker on the old key, declaration at a\n * new one — retires nothing. \"Is this message going away\" asks the NET set;\n * \"did the author write a marker\" asks the raw one, the OpenAPI distinction\n * above verbatim. Two raw sets, because the two marker shapes carry different\n * debts: a message-slot marker must be justified by a REMOVED requirement,\n * while a channel-slot removal needs exactness only (requirements join on\n * message names, never channel keys — SCHEMA.md) and its living inline\n * interior leaves with it.\n *\n * `living` is the contract the delta was merged INTO (or is about to be): a\n * channel removal's inline interior is written there and nowhere in the delta,\n * which carries only the marker. The feature gate passes the living contract\n * on disk; the retired index passes the archived feature's snapshot pre-image,\n * the living contract as it stood the moment that merge ran.\n */\nexport function messageRetirements(\n featDoc: AsyncapiDoc,\n featSlots: readonly AsyncapiSlot[],\n living: AsyncapiDoc,\n): MessageRetirements {\n const markers = featDoc.messages.filter((m) => m.remove === true && m.slot.startsWith(\"components.messages.\"));\n const channelRemovals = featSlots.filter((s) => s.section === \"channels\" && s.remove).map((s) => s.key);\n const inline = living.messages.filter((m) => channelRemovals.some((ck) => m.slot.startsWith(`channels.${ck}.messages.`)));\n const markerNames = new Set(markers.map((m) => m.name));\n const inlineRetired = new Set(inline.map((m) => m.name));\n const redeclared = new Set(featDoc.messages.filter((m) => m.remove !== true).map((m) => m.name));\n const net = new Set([...markerNames, ...inlineRetired].filter((name) => !redeclared.has(name)));\n return { markerNames, inlineRetired, redeclared, net, vacated: [...markers, ...inline] };\n}\n"]}
|