@ybotok/loam 0.2.0-alpha.7 → 0.2.0-alpha.9
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 +1315 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +83 -7
- package/SCHEMA.md +569 -98
- package/WORKFLOW.md +272 -62
- 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/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- 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 +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +33 -11
- 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 +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -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/explore.js +4 -8
- 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 +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- 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 +28 -33
- 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 +21 -25
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- 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 +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +3 -5
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +5 -8
- 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 +14 -41
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +17 -38
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +32 -5
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- 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 +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- 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 +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +23 -23
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +6 -10
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +1 -3
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- 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/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +8 -28
- package/dist/commands/validate/fleet/views/root-documents.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/report.js +5 -4
- 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 +16 -104
- 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 +1 -1
- 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/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- 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 +54 -51
- 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 +56 -101
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- 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 +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +4 -3
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +32 -18
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +9 -3
- 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/scaffold.js +10 -15
- package/dist/core/agent/scaffold.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 +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +20 -16
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- 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/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +50 -18
- 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 +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -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 +6 -18
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +15 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- 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/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/documents.js +31 -6
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +6 -17
- package/dist/core/c4/project/load.js.map +1 -1
- 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 +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- 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/findings.js +15 -20
- 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/coherence.js +30 -16
- 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/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 +119 -8
- 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 +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- 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 +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +3 -6
- package/dist/core/deployment/delta.js.map +1 -1
- 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 +28 -16
- 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/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- 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 +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +5 -2
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +4 -2
- 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/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/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 +5 -36
- package/dist/core/links/corpus.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 +22 -8
- 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 +56 -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 +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -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 +2 -16
- package/dist/core/pack/living.js.map +1 -1
- 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/entries.js.map +1 -1
- package/dist/core/repo/repo.js +24 -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/walk.js +8 -30
- 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/staging/commit.js +10 -12
- 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 +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.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 +174 -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/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- 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 +3 -0
- 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 +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +12 -2
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +21 -6
- package/examples/docs/architecture/landscape.likec4 +8 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- 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/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- 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 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../../src/core/verify/store/commit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../../src/core/verify/store/commit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC9H,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAuC,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAqB,MAAM,cAAc,CAAC;AA2BjD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAqB,EAAE,OAAe;IAC7E,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CACxB,CAAC,CAAC,EAAgB,EAAE,CAClB,CAAC,CAAC,MAAM,KAAK,IAAI;QACf,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;QACjC,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI;YACnB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE;YAC1E,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAChE,CAAC;IACF,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,0EAA0E;IAC1E,IAAI,MAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,mEAAmE,EAAE,CAAC;IAC1I,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1C,uEAAuE;QACvE,2EAA2E;QAC3E,wEAAwE;QACxE,0EAA0E;QAC1E,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;YAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,iCAAiC,OAAO,yBAAyB,EAAE,CAAC;QACzH,CAAC;QACD,0EAA0E;QAC1E,wEAAwE;QACxE,yEAAyE;QACzE,2CAA2C;QAC3C,MAAM,MAAM,GAAG,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC3G,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;YAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7E,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,qBAAqB;gBAC3B,OAAO,EAAE,eAAe,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC;aACnD,CAAC;QACJ,CAAC;QACD,wEAAwE;QACxE,4DAA4D;QAC5D,IAAI,GAAG,YAAY,gBAAgB,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACxF,MAAM,IAAI,GAAG,GAAG,YAAY,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,MAAM,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC;YAC7H,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;QAC7F,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,mEAAmE,EAAE,CAAC;IAC1I,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,YAAY,CAAC,OAAe;IACnC,OAAO,CACL,GAAG,OAAO,oHAAoH;QAC9H,sHAAsH,CACvH,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The verification record's commit: staged beside the target, compared against\n * the byte pre-image the merge consumed, swapped in by one rename(2) per file.\n * A write is one file, or two: a service's own `verification/<svc>.yaml`, and\n * — once nothing in it counts any more — the removal of the one-file record it\n * supersedes. Never another service's file: those are not this run's to touch.\n *\n * The record used to go down as a plain `writeFile`, which cost three ways at\n * once: two `--record` runs read the same pre-image and the last writer\n * silently discarded the other service's attestation; a process killed\n * mid-write left the ONLY copy truncated, unreadable, and blocking every later\n * record; and an edit that landed between the read and the write was buried\n * under bytes computed from a document that no longer existed. The docs lock\n * (taken by `loam verify` before its authoritative read) closes the first;\n * this module closes the other two, and backstops the lock itself for a writer\n * that never took it.\n *\n * A sub-package of core/verify rather than a sixth file in it, and a consumer\n * of core/staging's primitives rather than a re-implementation: `stageWrites`\n * and `swapStaged` already do temp-beside-target, whole-content compare-and-set\n * and same-directory rename — vouch commits through exactly this shape.\n */\nimport { message, rollbackMessage, rollbackStaged, stageWrites, StagingRaceError, swapStaged } from \"../../staging/commit.js\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { type PlannedWrite, type StagedWrite } from \"../../staging/writes.js\";\nimport { renderVerification } from \"./render.js\";\nimport { type Verification } from \"../record.js\";\n\n/**\n * `record-raced` re-runs cleanly: the merge is recomputed over whatever the\n * other writer left. The other two are staging's own vocabulary, with staging's\n * meanings — `merge-failed` trusts the repo, `rollback-incomplete` does not.\n */\nexport type VerificationCommit =\n | { ok: true }\n | { ok: false; code: \"record-raced\" | \"merge-failed\" | \"rollback-incomplete\"; message: string };\n\n/**\n * One file of the record this commit writes or removes.\n *\n * `preImage` is the exact bytes the caller's merge was computed from, or null\n * when the file was absent at the authoritative read — in which case the\n * commit may only CREATE it (link(2) refuses a file that appeared since), never\n * replace one it has not read. `record: null` removes the file, and only over\n * the bytes that read saw: a delete is a replace with nothing, and it is held\n * to the same compare.\n */\nexport interface RecordWrite {\n path: string;\n record: Verification | null;\n preImage: Buffer | null;\n}\n\n/**\n * Writes land in the order given, so a caller puts what must survive a kill\n * FIRST: a service's own file before the one-file record it supersedes, so a\n * process killed between the two renames leaves a record that still reads\n * whole — the new file counted, the old one superseded by it — rather than one\n * with the old file gone and nothing in its place.\n */\nexport async function commitVerification(writes: RecordWrite[], docsDir: string): Promise<VerificationCommit> {\n const planned = writes.map(\n (w): PlannedWrite =>\n w.record === null\n ? { path: w.path, content: null }\n : w.preImage === null\n ? { path: w.path, content: renderVerification(w.record), exclusive: true }\n : { path: w.path, content: renderVerification(w.record) },\n );\n // Staging touches the filesystem — a read-only feature directory, a full\n // disk, a pre-image that will not read. None of that is a bug in loam, and\n // letting it escape as `internal` sends the reader looking for one (archive\n // draws the same line around its own `stageWrites`). Nothing was written.\n let staged: StagedWrite[];\n try {\n staged = await stageWrites(planned);\n } catch (err) {\n return { ok: false, code: \"merge-failed\", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };\n }\n for (const w of writes) {\n const display = repoPath(docsDir, w.path);\n // Joined by path, not by index — vouch's rule, for vouch's reason: one\n // entry per planned write in order is true today and promised nowhere, and\n // a compare-and-set over the wrong pair would vouch for bytes the merge\n // never saw. A missing entry fails closed as a merge that never happened.\n const s = staged.find((x) => x.write.path === w.path);\n if (s === undefined) {\n await rollbackStaged(staged);\n return { ok: false, code: \"merge-failed\", message: `staging returned no entry for ${display} — nothing was recorded` };\n }\n // The compare the read promised: the file as staging found it must be the\n // file the merge consumed. `swapStaged` re-checks against ITS read just\n // before the rename, so together the two compares cover the whole window\n // from the authoritative read to the swap.\n const edited = w.preImage === null ? s.before !== null : s.before === null || !s.before.equals(w.preImage);\n if (edited) {\n await rollbackStaged(staged);\n return { ok: false, code: \"record-raced\", message: racedMessage(display) };\n }\n }\n try {\n await swapStaged(staged);\n } catch (err) {\n const failures = await rollbackStaged(staged);\n if (failures.length > 0) {\n return {\n ok: false,\n code: \"rollback-incomplete\",\n message: rollbackMessage(err, failures, \"written\"),\n };\n }\n // EEXIST is the exclusive create losing its race — same fact as the CAS\n // refusal, discovered by link(2) instead of by the compare.\n if (err instanceof StagingRaceError || (err as NodeJS.ErrnoException).code === \"EEXIST\") {\n const path = err instanceof StagingRaceError ? err.path : (writes.find((w) => w.preImage === null)?.path ?? writes[0]!.path);\n return { ok: false, code: \"record-raced\", message: racedMessage(repoPath(docsDir, path)) };\n }\n return { ok: false, code: \"merge-failed\", message: `${message(err)} — nothing was recorded; the record is as the last writer left it` };\n }\n return { ok: true };\n}\n\nfunction racedMessage(display: string): string {\n return (\n `${display} changed while this record was being written — another writer or an editor landed between the read and the write. ` +\n \"Nothing was written and their bytes are untouched. Re-run: the merge is recomputed over the record as it now stands.\"\n );\n}\n"]}
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* re-graded on the way in, because anything may have edited it since), so the
|
|
7
7
|
* two were cohabiting rather than forming a codec, while the RENDER and the
|
|
8
8
|
* COMMIT genuinely are one concern — this module produces exactly the bytes
|
|
9
|
-
* whose atomic swap that one performs.
|
|
10
|
-
*
|
|
9
|
+
* whose atomic swap that one performs. The reading went on to a package of its
|
|
10
|
+
* own, `../read/` (`file.ts` finds and grades the files, `shape.ts` says what a
|
|
11
|
+
* file must hold, `union.ts` adds the files up), and `../read/file.ts`'s header
|
|
12
|
+
* says where the render went.
|
|
11
13
|
*/
|
|
12
14
|
import { stringify } from "yaml";
|
|
13
15
|
import {} from "../record.js";
|
|
@@ -42,8 +44,10 @@ export function renderVerification(v) {
|
|
|
42
44
|
? [
|
|
43
45
|
"#",
|
|
44
46
|
"# Schema 2 is federated: each service entry under `attestations` binds its claim ids",
|
|
45
|
-
"# and file:line evidence to that repository's git commit.
|
|
46
|
-
"#
|
|
47
|
+
"# and file:line evidence to that repository's git commit. Each attesting service has a",
|
|
48
|
+
"# file of its own, verification/<svc>.yaml, and the record is the directory: claims",
|
|
49
|
+
"# another service owns are honestly unanswered in this file and answered in that",
|
|
50
|
+
"# service's own, and loam adds the files up when it reads them.",
|
|
47
51
|
"# `evidence_pins` records each cited file's sha256 (CRLF-normalized) and line text at",
|
|
48
52
|
"# the attested commit, so `loam validate` in that repo can convict drift later; a pin",
|
|
49
53
|
"# identifies what was cited — it does not prove the claim true.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../../src/core/verify/store/render.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../../src/core/verify/store/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAqB,MAAM,cAAc,CAAC;AAEjD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAe;IAChD,MAAM,MAAM,GAAG;QACb,6BAA6B,CAAC,CAAC,OAAO,+BAA+B,CAAC,CAAC,OAAO,iDAAiD;QAC/H,GAAG;QACH,iFAAiF;QACjF,gFAAgF;QAChF,mFAAmF;QACnF,mFAAmF;QACnF,qFAAqF;QACrF,qFAAqF;QACrF,kFAAkF;QAClF,mFAAmF;QACnF,4CAA4C;QAC5C,GAAG;QACH,8EAA8E;QAC9E,8EAA8E;QAC9E,kFAAkF;QAClF,kFAAkF;QAClF,2BAA2B;QAC3B,GAAG;QACH,uFAAuF;QACvF,iFAAiF;QACjF,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC;YAChB,CAAC,CAAC;gBACE,GAAG;gBACH,sFAAsF;gBACtF,wFAAwF;gBACxF,qFAAqF;gBACrF,kFAAkF;gBAClF,iEAAiE;gBACjE,uFAAuF;gBACvF,uFAAuF;gBACvF,iEAAiE;aAClE;YACH,CAAC,CAAC,EAAE,CAAC;QACP,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,qFAAqF;IACrF,OAAO,MAAM,GAAG,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;AACjD,CAAC","sourcesContent":["/**\n * The record as bytes: the header and the YAML that `store/commit.ts` swaps\n * into place. Moved here from `../file.ts` when the contract-report validation\n * pushed that module over the line limit — and the seam is real, not\n * arithmetic: reading was never the inverse of rendering (a record is\n * re-graded on the way in, because anything may have edited it since), so the\n * two were cohabiting rather than forming a codec, while the RENDER and the\n * COMMIT genuinely are one concern — this module produces exactly the bytes\n * whose atomic swap that one performs. The reading went on to a package of its\n * own, `../read/` (`file.ts` finds and grades the files, `shape.ts` says what a\n * file must hold, `union.ts` adds the files up), and `../read/file.ts`'s header\n * says where the render went.\n */\nimport { stringify } from \"yaml\";\nimport { type Verification } from \"../record.js\";\n\n/**\n * The record as a file. The header explains what the reader is looking at,\n * because the whole point is that this is legible to someone who has never run\n * loam — including the part loam cannot vouch for.\n */\nexport function renderVerification(v: Verification): string {\n const header = [\n `# Verification record for ${v.feature} — written by \\`loam verify ${v.feature}\\` (--results / --contract-results / --record).`,\n \"#\",\n \"# Every claim below was derived mechanically from this feature's own artifacts:\",\n \"# delta.likec4, specs/<svc>/spec.md, specs/<svc>/arch.spec.md and specs/<svc>/\",\n \"# openapi.yaml. Each verdict names who answered it: `answered_by: runner` means a\",\n \"# test report's digest-tagged scenarios answered it mechanically — cucumber JSON,\",\n \"# or loam's own {\\\"loamScenarioReport\\\": 1, ...} shape that any runner adapts into;\",\n \"# `answered_by: external-runner` means a contract-test report's operationId-matched\",\n \"# entries did, and `contractReport:` records which file a --contract-results run\",\n \"# read; `answered_by: agent` means somebody's word about the code, which loam did\",\n \"# not check. Nothing gates on any of them.\",\n \"#\",\n \"# A `scenario.tested` claim confirmed by an agent is ATTESTED, not run: loam\",\n \"# reports it as `verify.scenario-attested` and the feature does not count as\",\n \"# verified until a report answers it. `report:` records the file a --results run\",\n \"# read — its sha256 and mtime say WHICH file, not that it came from that commit;\",\n \"# no digest can say that.\",\n \"#\",\n \"# `checklist` is a digest of the claim ids. If `loam verify` stops reporting the same\",\n \"# one, the feature changed after this was recorded and these answers are stale.\",\n ...(v.schema === 2\n ? [\n \"#\",\n \"# Schema 2 is federated: each service entry under `attestations` binds its claim ids\",\n \"# and file:line evidence to that repository's git commit. Each attesting service has a\",\n \"# file of its own, verification/<svc>.yaml, and the record is the directory: claims\",\n \"# another service owns are honestly unanswered in this file and answered in that\",\n \"# service's own, and loam adds the files up when it reads them.\",\n \"# `evidence_pins` records each cited file's sha256 (CRLF-normalized) and line text at\",\n \"# the attested commit, so `loam validate` in that repo can convict drift later; a pin\",\n \"# identifies what was cited — it does not prove the claim true.\",\n ]\n : []),\n \"\",\n ].join(\"\\n\");\n // lineWidth 0: never fold a claim onto a second line — these are grepped and diffed.\n return header + stringify(v, { lineWidth: 0 });\n}\n"]}
|
|
@@ -14,6 +14,7 @@ export function issueFinding(i) {
|
|
|
14
14
|
gates: gatesArchive(i),
|
|
15
15
|
...(i.subject === undefined ? {} : { subject: i.subject }),
|
|
16
16
|
...(i.locations === undefined ? {} : { locations: i.locations }),
|
|
17
|
+
...(i.details === undefined ? {} : { details: i.details }),
|
|
17
18
|
message: i.message,
|
|
18
19
|
};
|
|
19
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../src/core/vocabulary/issue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuBH,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAC3C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;QACtB,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;QAChE,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,iBAAiB,GAA2B,IAAI,GAAG,CAAY;IACnE,4BAA4B;IAC5B,sBAAsB;IACtB,qBAAqB;IACrB,uBAAuB;IACvB,wBAAwB;IACxB,yBAAyB;IACzB,sBAAsB;CACvB,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB,CAAC,CAAQ;IACvC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC","sourcesContent":["/**\n * A coherence breach: something that makes a feature unsafe to merge into the\n * living docs. Shared by every check the archive gate runs.\n */\n\nimport type { Finding, FindingLocation } from \"./report.js\";\n\nexport interface Issue {\n severity: \"error\" | \"warn\";\n /** Stable machine identifier for the breach — see the labels below. */\n code: IssueCode;\n /** What the breach is about when that is narrower than the feature — usually a service. */\n subject?: string;\n message: string;\n locations?: FindingLocation[];\n /**\n * Does this issue stop `loam archive`? Severity and gating answer two\n * different questions: severity says whether the DOCUMENT is valid (`loam\n * validate` fails on errors), gating says whether the MERGE is safe. They\n * usually agree — errors gate, warnings do not — so the field is stated only\n * where they diverge: a legal document whose merge would drop authored\n * content is a warning that gates.\n */\n gates?: boolean;\n}\n\n/** Whether `loam archive` refuses on this issue without `--approve`. */\nexport function gatesArchive(i: Issue): boolean {\n return i.gates ?? i.severity === \"error\";\n}\n\n/** One issue in the shared validation/status finding shape. */\nexport function issueFinding(i: Issue): Finding {\n return {\n severity: i.severity,\n code: i.code,\n gates: gatesArchive(i),\n ...(i.subject === undefined ? {} : { subject: i.subject }),\n ...(i.locations === undefined ? {} : { locations: i.locations }),\n message: i.message,\n };\n}\n\n/**\n * Whether `--approve` can move this issue when it gates. A third axis beside\n * severity and gating: severity answers \"is the DOCUMENT valid\", gating\n * answers \"is the MERGE safe\", and this one answers whose call the refusal is\n * — a human's or the grammar's. Almost every gating issue is a judgment about\n * the feature, which is exactly the judgment `--approve` exists to override.\n *\n * The exceptions are the ones where approval could not change what happens.\n * `c4.service-binding-invalid`: the binding IS the path the merge would write,\n * so the fix is the name, never the flag. `glossary.term-exists`: the merge is\n * a whole-file copy over an authored definition, so approving it is approving a\n * deletion nobody described — and the alternative the message names (edit the\n * living definition in the same change) costs one `git mv` and loses nothing.\n * `usecase.flow-exists` is the same act one axis over: the merge would copy a\n * feature's flow over a living one, and an ordered hop sequence is recorded\n * nowhere else either. `usecase.flow-invalid` is the other kind of exception —\n * not a deletion but a BLINDNESS: loam could not read the feature's flows\n * against the map its own merge would leave, so it graded nothing, and\n * `--approve` overrides loam's judgment about coherence and never its ability\n * to read an axis. That is the same line `delta.likec4`'s unparseable case is\n * refused on, one merge over.\n *\n * `archive` refuses these under the same `not-coherent` envelope and spells\n * this verdict per issue as the additive `overridable` key, so a `--json`\n * consumer branches on data rather than on a code list.\n */\nconst NEVER_OVERRIDABLE: ReadonlySet<IssueCode> = new Set<IssueCode>([\n \"c4.service-binding-invalid\",\n \"glossary.term-exists\",\n \"usecase.flow-exists\",\n \"deployment.doc-exists\",\n \"deployment.doc-invalid\",\n \"deployment.doc-reserved\",\n \"usecase.flow-invalid\",\n]);\n\nexport function approveOverrides(i: Issue): boolean {\n return !NEVER_OVERRIDABLE.has(i.code);\n}\n\n/**\n * The three grades a CAPABILITY DOCUMENT earns on its own terms, named as a\n * union of their own because both corpora emit them and the two must stay one\n * list: `validate --all` grades the living `capabilities/<id>/spec.md` and\n * files them as Findings, while the archive gate grades a feature's\n * `features/<FEAT>/capabilities/<id>/spec.md` and files the SAME three as\n * archive-gating Issues.\n *\n * Grading the delta is not belt-and-braces. Without it, a capability\n * requirement with no `Requirement-ID:` — or one carrying `Operations:`, the\n * service-altitude line the whole corpus exists to keep out — merges into the\n * living tree and only THEN earns its error, against a document whoever reads\n * the finding did not write. The feature-local delta must not become a second\n * way to write service requirements, and this is the rule that stops it.\n *\n * No code is added to the product here: all three already shipped, are already\n * emitted from `core/capabilities/findings.ts` with literal `code:` values, and\n * are already documented in the `/loam-check` table. What the union member adds\n * is the compiler's permission to file the same finding at the archive gate.\n */\nexport type CapabilityDocCode =\n /** a requirement in a capability document with no `Requirement-ID:` — identity by heading, which every join to it breaks on */\n | \"capability.requirement-unidentified\"\n /** a capability requirement carrying `Operations:`/`Covers:`/`Publishes:`/`Consumes:` — a service requirement filed at the wrong altitude */\n | \"capability.requirement-service-scoped\"\n /** a capability requirement carrying `Capability:` or `Realizes:` — the axis's own joins, which point into the tree and so do nothing written inside it */\n | \"capability.requirement-inert-join\";\n\nexport type IssueCode =\n /* --- cross-axis: C4 <-> requirements <-> OpenAPI --- */\n /** the architecture axis could not be read at all */\n | \"delta.invalid\"\n /** the delta declares elements or edges but none carry the feature tag — loam cannot see any of it */\n | \"delta.nothing-tagged\"\n /** E1 — a requirement governs an operation its service's OpenAPI does not define */\n | \"spec-api.op-undefined\"\n /** E1, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"spec-api.op-pending\"\n /** E2 — a C4 edge calls an operation the target's OpenAPI does not define */\n | \"c4-api.op-undefined\"\n /** E2, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"c4-api.op-pending\"\n /** E2's lifecycle shadow — a NEW tagged edge consumes an operation the living provider contract marks `deprecated: true`; advisory, never a gate */\n | \"c4-api.op-deprecated\"\n /** a NEW tagged edge consumes an operation this same feature removes */\n | \"c4-api.op-removing\"\n /** an operation-removal marker addresses no living path+method */\n | \"openapi.remove-target-missing\"\n /** an operation-removal marker's id differs from the living operation at that path+method */\n | \"openapi.remove-target-mismatch\"\n /** a REMOVED requirement governs an operation but the feature has no matching removal marker */\n | \"openapi.remove-marker-missing\"\n /** an operation-removal marker is not justified by a REMOVED requirement */\n | \"openapi.remove-marker-unjustified\"\n /* --- the event axis: C4 <-> requirements <-> AsyncAPI, joined on the message name --- */\n /** the FEATURE's own asyncapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every event-axis check for the service is suspended */\n | \"asyncapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or one on a slot the living contract does not have; a malformed `x-loam-baselines` record, an entry of it naming a component surface the delta does not declare, or one whose living counterpart is gone */\n | \"asyncapi.baseline-invalid\"\n /** a feature slot or component surface with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it; warn that gates, counted per service */\n | \"asyncapi.baseline-missing\"\n /** the living slot or component surface changed since this delta pinned it — merging would discard whoever landed in between */\n | \"asyncapi.baseline-stale\"\n /** a slot-removal marker addresses a (section, key) the living contract does not have */\n | \"asyncapi.remove-target-missing\"\n /** a message-removal marker's name differs from the living declaration at that key */\n | \"asyncapi.remove-target-mismatch\"\n /** a REMOVED requirement's Publishes:/Consumes: line names a message the living contract still declares, with no matching removal marker in the feature */\n | \"asyncapi.remove-marker-missing\"\n /** a message-removal marker no REMOVED requirement's Publishes:/Consumes: line justifies — message slots only; channel and operation slots need exactness alone */\n | \"asyncapi.remove-marker-unjustified\"\n /** the feature retires a message the LIVING fleet still consumes — a landscape consumes-edge, or another service's living requirement */\n | \"asyncapi.remove-message-consumed\"\n /** another feature in flight adds or edits the same (service, message) — whichever archives second replaces the other's declaration wholesale */\n | \"asyncapi.message-conflict\"\n /** internal `$ref`(s) resolving to nothing — validate's warn on a living contract; a gating plan issue at archive when the MERGED document would carry the dangling reference */\n | \"asyncapi.ref-unresolved\"\n /** a tagged edge publishes/consumes a message its bound service's contract — as the feature's merge would leave it, feature ∪ living where no delta exists — declares no matching send/receive operation for */\n | \"c4-event.message-undefined\"\n /** softened — the message is introduced by another feature still in flight; archive that one first */\n | \"c4-event.message-pending\"\n /** a delta requirement's Publishes:/Consumes: line names a message the service's contract — as the feature's merge would leave it, so a retired declaration stops answering — does not declare in that direction; validate's living-side code, graded in feature scope too */\n | \"spec-event.message-undefined\"\n /** softened — the named message is introduced by another feature still in flight */\n | \"spec-event.message-pending\"\n /** W1 — an operation is called but no requirement governs it */\n | \"c4.op-ungoverned\"\n /** W2 — the feature adds an operation no architecture edge consumes */\n | \"api.op-unconsumed\"\n /** W3 — a new service arrives with no requirement delta */\n | \"service.no-requirement-delta\"\n /** W4 — a \"Calls\" edge carries no operation link */\n | \"c4.op-link-missing\"\n /** an explicit `metadata { service }` binding — a tagged element's, or one riding anywhere inside its authored block — breaks the service-id grammar; the merge would splice the name into the living landscape verbatim, and probe services/<id>/ with it; never overridable, the path is a mechanical fact */\n | \"c4.service-binding-invalid\"\n /* --- delta shape: does the diff apply to the living spec it claims to change? --- */\n /** a heading that nearly matches the delta grammar — its requirements merge as nothing */\n | \"delta.unknown-section\"\n /** a delta file with requirements but no delta section anywhere — the whole file would merge as nothing */\n | \"delta.no-delta-sections\"\n /** a requirement stranded under a prose heading — archive will not merge it */\n | \"delta.requirement-not-merged\"\n /** a Requirement-ID in a delta violates the stable-id grammar */\n | \"delta.requirement-id-invalid\"\n /** one delta requirement declares Requirement-ID more than once */\n | \"delta.requirement-id-repeated\"\n /** one Requirement-ID identifies multiple requirements in a delta */\n | \"delta.requirement-id-duplicate\"\n /** the living spec has malformed/ambiguous IDs and cannot be selected safely */\n | \"delta.living-requirement-id-invalid\"\n /** a delta's stable ID and heading select different living requirements */\n | \"delta.requirement-identity-collision\"\n /** MODIFIED a requirement the living spec does not have */\n | \"delta.modified-unknown\"\n /** REMOVED a requirement the living spec does not have */\n | \"delta.removed-unknown\"\n /** ADDED a requirement the living spec already has — the merge would replace it */\n | \"delta.added-duplicate\"\n /** ADDED a name that differs only in case from a living requirement — the merge matches exactly, so both would coexist */\n | \"delta.added-near-duplicate\"\n /** MODIFIED something another feature in flight introduces — an ordering dependency */\n | \"delta.modified-pending\"\n /** REMOVED something another feature in flight introduces */\n | \"delta.removed-pending\"\n /** two features in flight add the same requirement to the same service */\n | \"delta.added-conflict\"\n /* --- archive plan: breaches only the merge computation itself can see --- */\n /** a LIVING requirement outside `## Requirements` — the merge rewrites only that section, so it would land twice */\n | \"living.requirement-outside-requirements\"\n /** the delta redefines an operation the living OpenAPI already has — the merge overwrites it wholesale */\n | \"openapi.op-modified\"\n /** a component the merge carries — reached by a merged operation, or declared by a delta whose whole change is components — already exists in the living OpenAPI with different content, and the merge overwrites it wholesale */\n | \"openapi.component-modified\"\n /** a $ref reachable from the merged operations or a merged component resolves in neither the feature's OpenAPI nor the living one — merging would write a dangling reference */\n | \"openapi.ref-unresolved\"\n /** the delta redefines a PATH-level key (parameters, servers, summary) the living OpenAPI already has — the overwrite applies to every operation on that path */\n | \"openapi.path-item-modified\"\n /** the delta redefines a `components.messages` slot the living AsyncAPI already has — the merge overwrites the living message wholesale */\n | \"asyncapi.message-modified\"\n /** the delta redefines a `channels` slot the living AsyncAPI already has — the merge overwrites it wholesale, inline channel messages included */\n | \"asyncapi.channel-modified\"\n /** the delta redefines an `operations` slot the living AsyncAPI already has — the merge overwrites it wholesale */\n | \"asyncapi.operation-modified\"\n /** the delta declares a component SURFACE outside `components.messages` — a schema, a security scheme — that the living AsyncAPI already has with different content, and the merge copies the feature's version over it wholesale */\n | \"asyncapi.component-modified\"\n /** an `x-loam-remove: true` marker with no operationId — loam cannot tell which operation it retires, and the marker itself would reach the living contract */\n | \"openapi.remove-marker-anonymous\"\n /** an `x-loam-remove: true` written at PATH level, beside the methods — it addresses no operation, so it retires nothing and is not a contract key either */\n | \"openapi.remove-marker-path-level\"\n /** an `x-loam-remove: true` nested on an INLINE channel message — inline messages are channel interior, never slots, so it retires nothing; when the channel is otherwise unchanged the marker surfaces nowhere else at all */\n | \"asyncapi.remove-marker-inline\"\n /** the living OpenAPI defines one operationId in two (path, method) slots — every join on the id picks one arbitrarily */\n | \"openapi.duplicate-operationid\"\n /** the FEATURE's own openapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every contract-axis check for the service is suspended, and the archive plan refuses the merge mechanically */\n | \"openapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or sits on an operation the living contract does not have; a malformed `x-loam-baselines` record, an entry of it pinning a surface the delta does not restate, or one whose living counterpart is gone */\n | \"openapi.baseline-invalid\"\n /** a feature operation, restated path-level key or component with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it */\n | \"openapi.baseline-missing\"\n /** the living operation, path-level key or component changed since this delta pinned it — merging would discard whoever landed in between */\n | \"openapi.baseline-stale\"\n /** the feature retires an operation the LIVING fleet still consumes — a landscape edge's `metadata { op }`, or another service's living requirement */\n | \"openapi.remove-op-consumed\"\n /** two requirements in the LIVING document share one heading — MODIFIED rewrites the first, REMOVED deletes both, so no delta applies predictably */\n | \"delta.living-duplicate-requirement\"\n /** another feature in flight MODIFIES/REMOVES the same living requirement — whichever archives second replaces the other's text wholesale */\n | \"delta.modified-conflict\"\n /** a `Based-On:` line whose value is not a digest, or declared twice — the pin cannot be compared, so it protects nothing */\n | \"delta.baseline-invalid\"\n /** a MODIFIED/REMOVED requirement with no `Based-On:` — nothing can tell whether the living text moved under it */\n | \"delta.baseline-missing\"\n /** the living requirement changed since this delta was written — merging it would silently discard whoever landed in between */\n | \"delta.baseline-stale\"\n /** the archive creates `services/<id>/` but nothing writes its `model.likec4` — the fleet gate will report the service incomplete */\n | \"service.no-model\"\n /* --- capability axis: the Capability: join against architecture/capabilities.yaml --- */\n /** a delta requirement's `Capability:` entry that the fleet vocabulary does not declare — the merge would land a join that resolves to nothing, so it blocks archive like other errors, and `--approve` overrides it */\n | \"capability.unknown\"\n /** a delta requirement's `Realizes:` entry that names no capability requirement — same argument as `capability.unknown` one join over: the merge would land a pointer at a promise that does not exist, looking exactly like a working one */\n | \"capability.realizes-unknown\"\n /** a capability requirement this feature ADDS that no `Realizes:` line in the same feature's service deltas names — the merge would land a business promise nothing in this change keeps. A warning that GATES, `--approve` overrides: the document is legal (writing ahead of the fleet is the intended use, which is why `capability.requirement-unrealized` warns and never gates), the MERGE is what is unsafe. Joined by `Realizes:`, NOT by `Covers:` — the name is the roadmap's, the join is the other one */\n | \"capability.uncovered\"\n /** the same join in the removal direction — this feature RETIRES a capability requirement that something the merge leaves behind still realizes, so the archive would leave `capability.realizes-unknown` standing against a document nobody in the feature touched. An error, exactly as `openapi.remove-op-consumed` is for the identical shape one axis over */\n | \"capability.remove-requirement-realized\"\n | CapabilityDocCode\n /* --- the domain glossary: a term a feature introduces --- */\n /** a `features/<FEAT>/glossary/<term>.md` whose term the living glossary already defines — the merge is a whole-file copy, so it would replace an authored definition wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local glossary document INTRODUCES a term, and rewriting one belongs in a pull request where git produces the conflict */\n | \"glossary.term-exists\"\n /* --- the use-case axis: a flow a feature introduces --- */\n /** a `features/<FEAT>/usecases/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The flows were not graded, and archiving would copy them into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it */\n | \"usecase.flow-invalid\"\n /** a `features/<FEAT>/usecases/<name>.likec4` whose flow the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored hop sequence wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local flow INTRODUCES a use case, and rewriting one belongs in a pull request where git produces the conflict */\n | \"usecase.flow-exists\"\n /* --- the deployment axis: the topology a feature introduces --- */\n /** a `features/<FEAT>/deployment/<name>.likec4` whose file the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored topology wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local deployment document INTRODUCES topology, and rewriting one belongs in a pull request where git produces the conflict. The same act, and the same severity, as `usecase.flow-exists` one axis over */\n | \"deployment.doc-exists\"\n /** a `features/<FEAT>/deployment/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The topology was not graded, and archiving would copy it into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it; the twin of `usecase.flow-invalid` one axis over */\n | \"deployment.doc-invalid\"\n /** a `features/<FEAT>/deployment/<name>.likec4` whose name is one loam owns inside `architecture/` — `subsystems.likec4` and `services.likec4` (both generated, overwritten wholesale by `loam subsystem sync`, and parsed by nothing, so anything landing in either is invisible from the moment it archives), `landscape.likec4` (spliced into, never replaced), or a path under `usecases/` (the flow tree). Refused whether or not the target exists, which is the point: three of the four fail silently when it does not. An error with no legal reading, so `--approve` changes nothing about it */\n | \"deployment.doc-reserved\"\n /* --- authoring: did a person actually write this? --- */\n /** a document `loam new` scaffolded still carries its exact placeholder text — the merge would publish a requirement, scenario or description nobody authored */\n | \"scaffold.placeholder\"\n /** intent.md is missing, or says nothing outside the scaffold's own comments — nothing states why the feature exists */\n | \"intent.empty\"\n /* --- docs-repo contract: --all only, never a merge question --- */\n /** AGENTS.md carries no version stamp, or one older than the running binary — the agent contract may have drifted */\n | \"agents.stale\";\n"]}
|
|
1
|
+
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../src/core/vocabulary/issue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+BH,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAC3C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,CAAQ;IACnC,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;QACtB,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;QAChE,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,OAAO,EAAE,CAAC,CAAC,OAAO;KACnB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,iBAAiB,GAA2B,IAAI,GAAG,CAAY;IACnE,4BAA4B;IAC5B,sBAAsB;IACtB,qBAAqB;IACrB,uBAAuB;IACvB,wBAAwB;IACxB,yBAAyB;IACzB,sBAAsB;CACvB,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB,CAAC,CAAQ;IACvC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC","sourcesContent":["/**\n * A coherence breach: something that makes a feature unsafe to merge into the\n * living docs. Shared by every check the archive gate runs.\n */\n\nimport type { Finding, FindingLocation } from \"./report.js\";\n\nexport interface Issue {\n severity: \"error\" | \"warn\";\n /** Stable machine identifier for the breach — see the labels below. */\n code: IssueCode;\n /** What the breach is about when that is narrower than the feature — usually a service. */\n subject?: string;\n message: string;\n /**\n * The evidence lines behind the message, when it has a list to show — the\n * `details` every `Finding` already carries. `loam archive`'s overwrite\n * warnings put the current consumers of the overwritten operation or\n * message here, one per line, so the message stays one sentence however\n * many there are.\n */\n details?: string[];\n locations?: FindingLocation[];\n /**\n * Does this issue stop `loam archive`? Severity and gating answer two\n * different questions: severity says whether the DOCUMENT is valid (`loam\n * validate` fails on errors), gating says whether the MERGE is safe. They\n * usually agree — errors gate, warnings do not — so the field is stated only\n * where they diverge: a legal document whose merge would drop authored\n * content is a warning that gates.\n */\n gates?: boolean;\n}\n\n/** Whether `loam archive` refuses on this issue without `--approve`. */\nexport function gatesArchive(i: Issue): boolean {\n return i.gates ?? i.severity === \"error\";\n}\n\n/** One issue in the shared validation/status finding shape. */\nexport function issueFinding(i: Issue): Finding {\n return {\n severity: i.severity,\n code: i.code,\n gates: gatesArchive(i),\n ...(i.subject === undefined ? {} : { subject: i.subject }),\n ...(i.locations === undefined ? {} : { locations: i.locations }),\n ...(i.details === undefined ? {} : { details: i.details }),\n message: i.message,\n };\n}\n\n/**\n * Whether `--approve` can move this issue when it gates. A third axis beside\n * severity and gating: severity answers \"is the DOCUMENT valid\", gating\n * answers \"is the MERGE safe\", and this one answers whose call the refusal is\n * — a human's or the grammar's. Almost every gating issue is a judgment about\n * the feature, which is exactly the judgment `--approve` exists to override.\n *\n * The exceptions are the ones where approval could not change what happens.\n * `c4.service-binding-invalid`: the binding IS the path the merge would write,\n * so the fix is the name, never the flag. `glossary.term-exists`: the merge is\n * a whole-file copy over an authored definition, so approving it is approving a\n * deletion nobody described — and the alternative the message names (edit the\n * living definition in the same change) costs one `git mv` and loses nothing.\n * `usecase.flow-exists` is the same act one axis over: the merge would copy a\n * feature's flow over a living one, and an ordered hop sequence is recorded\n * nowhere else either. `usecase.flow-invalid` is the other kind of exception —\n * not a deletion but a BLINDNESS: loam could not read the feature's flows\n * against the map its own merge would leave, so it graded nothing, and\n * `--approve` overrides loam's judgment about coherence and never its ability\n * to read an axis. That is the same line `delta.likec4`'s unparseable case is\n * refused on, one merge over.\n *\n * `archive` refuses these under the same `not-coherent` envelope and spells\n * this verdict per issue as the additive `overridable` key, so a `--json`\n * consumer branches on data rather than on a code list.\n */\nconst NEVER_OVERRIDABLE: ReadonlySet<IssueCode> = new Set<IssueCode>([\n \"c4.service-binding-invalid\",\n \"glossary.term-exists\",\n \"usecase.flow-exists\",\n \"deployment.doc-exists\",\n \"deployment.doc-invalid\",\n \"deployment.doc-reserved\",\n \"usecase.flow-invalid\",\n]);\n\nexport function approveOverrides(i: Issue): boolean {\n return !NEVER_OVERRIDABLE.has(i.code);\n}\n\n/**\n * The three grades a CAPABILITY DOCUMENT earns on its own terms, named as a\n * union of their own because both corpora emit them and the two must stay one\n * list: `validate --all` grades the living `capabilities/<id>/spec.md` and\n * files them as Findings, while the archive gate grades a feature's\n * `features/<FEAT>/capabilities/<id>/spec.md` and files the SAME three as\n * archive-gating Issues.\n *\n * Grading the delta is not belt-and-braces. Without it, a capability\n * requirement with no `Requirement-ID:` — or one carrying `Operations:`, the\n * service-altitude line the whole corpus exists to keep out — merges into the\n * living tree and only THEN earns its error, against a document whoever reads\n * the finding did not write. The feature-local delta must not become a second\n * way to write service requirements, and this is the rule that stops it.\n *\n * No code is added to the product here: all three already shipped, are already\n * emitted from `core/capabilities/findings.ts` with literal `code:` values, and\n * are already documented in the `/loam-check` table. What the union member adds\n * is the compiler's permission to file the same finding at the archive gate.\n */\nexport type CapabilityDocCode =\n /** a requirement in a capability document with no `Requirement-ID:` — identity by heading, which every join to it breaks on */\n | \"capability.requirement-unidentified\"\n /** a capability requirement carrying `Operations:`/`Covers:`/`Publishes:`/`Consumes:` — a service requirement filed at the wrong altitude */\n | \"capability.requirement-service-scoped\"\n /** a capability requirement carrying `Capability:` or `Realizes:` — the axis's own joins, which point into the tree and so do nothing written inside it */\n | \"capability.requirement-inert-join\";\n\nexport type IssueCode =\n /* --- cross-axis: C4 <-> requirements <-> OpenAPI --- */\n /** the architecture axis could not be read at all */\n | \"delta.invalid\"\n /** the delta declares elements or edges but none carry the feature tag — loam cannot see any of it */\n | \"delta.nothing-tagged\"\n /** E1 — a requirement governs an operation its service's OpenAPI does not define */\n | \"spec-api.op-undefined\"\n /** E1, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"spec-api.op-pending\"\n /** E2 — a C4 edge calls an operation the target's OpenAPI does not define */\n | \"c4-api.op-undefined\"\n /** E2, softened — the operation is defined by another feature still in flight; archive that one first */\n | \"c4-api.op-pending\"\n /** E2's lifecycle shadow — a NEW tagged edge consumes an operation the living provider contract marks `deprecated: true`; advisory, never a gate */\n | \"c4-api.op-deprecated\"\n /** a NEW tagged edge consumes an operation this same feature removes — retires with a marker, or replaces at its method and path so the merge leaves it defined nowhere */\n | \"c4-api.op-removing\"\n /** an operation-removal marker addresses no living path+method */\n | \"openapi.remove-target-missing\"\n /** an operation-removal marker's id differs from the living operation at that path+method */\n | \"openapi.remove-target-mismatch\"\n /** a REMOVED requirement governs an operation its service's living contract still defines, and the feature has no matching removal marker — owed only while there is one to retire: not for another service's id, not for one this delta already replaces at its address */\n | \"openapi.remove-marker-missing\"\n /** an operation-removal marker is not justified by a REMOVED requirement */\n | \"openapi.remove-marker-unjustified\"\n /* --- the event axis: C4 <-> requirements <-> AsyncAPI, joined on the message name --- */\n /** the FEATURE's own asyncapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every event-axis check for the service is suspended */\n | \"asyncapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or one on a slot the living contract does not have; a malformed `x-loam-baselines` record, an entry of it naming a component surface the delta does not declare, or one whose living counterpart is gone */\n | \"asyncapi.baseline-invalid\"\n /** a feature slot or component surface with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it; warn that gates, counted per service */\n | \"asyncapi.baseline-missing\"\n /** the living slot or component surface changed since this delta pinned it — merging would discard whoever landed in between */\n | \"asyncapi.baseline-stale\"\n /** a `channels`/`operations` slot, or a message by name, that this feature declares, the living contract no longer has, and an ARCHIVED feature's removal retired — or a message declared at a `components.messages` key an archived feature moved it away from — the merge cannot tell a quote of a retired or moved slot from an addition, so archiving would put it back. A warning that GATES; `--approve` re-adds it on purpose */\n | \"asyncapi.slot-revived\"\n /** a slot-removal marker addresses a (section, key) the living contract does not have */\n | \"asyncapi.remove-target-missing\"\n /** a message-removal marker's name differs from the living declaration at that key */\n | \"asyncapi.remove-target-mismatch\"\n /** a REMOVED requirement's Publishes:/Consumes: line names a message the living contract still declares and the merged contract would too, with no matching removal marker in the feature */\n | \"asyncapi.remove-marker-missing\"\n /** a message-removal marker no REMOVED requirement's Publishes:/Consumes: line justifies — message slots only; channel and operation slots need exactness alone */\n | \"asyncapi.remove-marker-unjustified\"\n /** the feature retires a message, or stops sending it, while the fleet would still name it after the merge — the consumer index's consumes-edges, other services' `Consumes:` lines and use-case hops while nothing would still send it after the merge (no service, no `#external` element publishing it), plus the provider's own `Publishes:` lines and `publishes` edges whoever else sends it (core/consumers/) */\n | \"asyncapi.remove-message-consumed\"\n /** another feature in flight adds or edits the same (service, message) — whichever archives second replaces the other's declaration wholesale */\n | \"asyncapi.message-conflict\"\n /** internal `$ref`(s) resolving to nothing — validate's warn on a living contract; a gating plan issue at archive when the MERGED document would carry the dangling reference */\n | \"asyncapi.ref-unresolved\"\n /** a tagged edge publishes/consumes a message its bound service's contract — as the feature's merge would leave it, feature ∪ living where no delta exists — declares no matching send/receive operation for */\n | \"c4-event.message-undefined\"\n /** softened — the message is introduced by another feature still in flight; archive that one first */\n | \"c4-event.message-pending\"\n /** a delta requirement's Publishes:/Consumes: line names a message the service's contract — as the feature's merge would leave it, so a retired declaration stops answering — does not declare in that direction; validate's living-side code, graded in feature scope too */\n | \"spec-event.message-undefined\"\n /** softened — the named message is introduced by another feature still in flight */\n | \"spec-event.message-pending\"\n /** W1 — an operation is called but no requirement governs it */\n | \"c4.op-ungoverned\"\n /** W2 — the feature adds an operation no architecture edge consumes */\n | \"api.op-unconsumed\"\n /** W3 — a new service arrives with no requirement delta */\n | \"service.no-requirement-delta\"\n /** W4 — a \"Calls\" edge carries no operation link */\n | \"c4.op-link-missing\"\n /** an explicit `metadata { service }` binding — a tagged element's, or one riding anywhere inside its authored block — breaks the service-id grammar; the merge would splice the name into the living landscape verbatim, and probe services/<id>/ with it; never overridable, the path is a mechanical fact */\n | \"c4.service-binding-invalid\"\n /* --- delta shape: does the diff apply to the living spec it claims to change? --- */\n /** a heading that nearly matches the delta grammar — its requirements merge as nothing */\n | \"delta.unknown-section\"\n /** a delta file with requirements but no delta section anywhere — the whole file would merge as nothing */\n | \"delta.no-delta-sections\"\n /** a requirement stranded under a prose heading — archive will not merge it */\n | \"delta.requirement-not-merged\"\n /** a Requirement-ID in a delta violates the stable-id grammar */\n | \"delta.requirement-id-invalid\"\n /** one delta requirement declares Requirement-ID more than once */\n | \"delta.requirement-id-repeated\"\n /** one Requirement-ID identifies multiple requirements in a delta */\n | \"delta.requirement-id-duplicate\"\n /** the living spec has malformed/ambiguous IDs and cannot be selected safely */\n | \"delta.living-requirement-id-invalid\"\n /** a delta's stable ID and heading select different living requirements */\n | \"delta.requirement-identity-collision\"\n /** MODIFIED a requirement the living spec does not have */\n | \"delta.modified-unknown\"\n /** REMOVED a requirement the living spec does not have */\n | \"delta.removed-unknown\"\n /** ADDED a requirement the living spec already has — the merge would replace it */\n | \"delta.added-duplicate\"\n /** ADDED a name that differs only in case from a living requirement — the merge matches exactly, so both would coexist */\n | \"delta.added-near-duplicate\"\n /** MODIFIED something another feature in flight introduces — an ordering dependency */\n | \"delta.modified-pending\"\n /** REMOVED something another feature in flight introduces */\n | \"delta.removed-pending\"\n /** two features in flight add the same requirement to the same service */\n | \"delta.added-conflict\"\n /* --- archive plan: breaches only the merge computation itself can see --- */\n /** a LIVING requirement outside `## Requirements` — the merge rewrites only that section, so it would land twice */\n | \"living.requirement-outside-requirements\"\n /** the delta redefines an operation the living OpenAPI already has — the merge overwrites it wholesale */\n | \"openapi.op-modified\"\n /** a component the merge carries — reached by a merged operation, or declared by a delta whose whole change is components — already exists in the living OpenAPI with different content, and the merge overwrites it wholesale */\n | \"openapi.component-modified\"\n /** a $ref reachable from the merged operations or a merged component resolves in neither the feature's OpenAPI nor the living one — merging would write a dangling reference */\n | \"openapi.ref-unresolved\"\n /** the delta redefines a PATH-level key (parameters, servers, summary) the living OpenAPI already has — the overwrite applies to every operation on that path */\n | \"openapi.path-item-modified\"\n /** the delta redefines a `components.messages` slot the living AsyncAPI already has — the merge overwrites the living message wholesale */\n | \"asyncapi.message-modified\"\n /** the delta redefines a `channels` slot the living AsyncAPI already has — the merge overwrites it wholesale, inline channel messages included */\n | \"asyncapi.channel-modified\"\n /** the delta redefines an `operations` slot the living AsyncAPI already has — the merge overwrites it wholesale */\n | \"asyncapi.operation-modified\"\n /** the delta declares a component SURFACE outside `components.messages` — a schema, a security scheme — that the living AsyncAPI already has with different content, and the merge copies the feature's version over it wholesale */\n | \"asyncapi.component-modified\"\n /** an `x-loam-remove: true` marker with no operationId — loam cannot tell which operation it retires, and the marker itself would reach the living contract */\n | \"openapi.remove-marker-anonymous\"\n /** an `x-loam-remove: true` written at PATH level, beside the methods — it addresses no operation, so it retires nothing and is not a contract key either */\n | \"openapi.remove-marker-path-level\"\n /** an `x-loam-remove: true` nested on an INLINE channel message — inline messages are channel interior, never slots, so it retires nothing; when the channel is otherwise unchanged the marker surfaces nowhere else at all */\n | \"asyncapi.remove-marker-inline\"\n /** the living OpenAPI defines one operationId in two (path, method) slots — every join on the id picks one arbitrarily */\n | \"openapi.duplicate-operationid\"\n /** the FEATURE's own openapi.yaml exists but does not parse — the same name validate gives a living contract in that state: every contract-axis check for the service is suspended, and the archive plan refuses the merge mechanically */\n | \"openapi.invalid\"\n /** an `x-loam-based-on` that is not a digest or sits on an operation the living contract does not have; a malformed `x-loam-baselines` record, an entry of it pinning a surface the delta does not restate, or one whose living counterpart is gone */\n | \"openapi.baseline-invalid\"\n /** a feature operation, restated path-level key or component with no baseline pin — the merge cannot tell whether the delta EDITS it or merely quotes it */\n | \"openapi.baseline-missing\"\n /** the living operation, path-level key or component changed since this delta pinned it — merging would discard whoever landed in between */\n | \"openapi.baseline-stale\"\n /** an operation this feature spells, the living contract no longer has, and an ARCHIVED feature's removal marker retired — or spells at an address an archived feature moved it away from — the merge cannot tell a quote of a retired or moved operation from an addition, so archiving would put it back (beside its new address, for a move). A warning that GATES; `--approve` re-adds it on purpose */\n | \"openapi.op-revived\"\n /** the feature takes out of the contract an operation the fleet would still consume after the merge — the consumer index's edges of the `architecture/` project, other services' `Operations:` lines (not one naming its own service's operation of the same id), use-case hops, plus the provider's own requirements and self-edges (core/consumers/). Two shapes: a removal marker retires it, or the delta defines another operation at its method and path and the contract the merge would write defines it nowhere */\n | \"openapi.remove-op-consumed\"\n /** two requirements in the LIVING document share one heading — MODIFIED rewrites the first, REMOVED deletes both, so no delta applies predictably */\n | \"delta.living-duplicate-requirement\"\n /** another feature in flight MODIFIES/REMOVES the same living requirement — whichever archives second replaces the other's text wholesale */\n | \"delta.modified-conflict\"\n /** a `Based-On:` line whose value is not a digest, or declared twice — the pin cannot be compared, so it protects nothing */\n | \"delta.baseline-invalid\"\n /** a MODIFIED/REMOVED requirement with no `Based-On:` — nothing can tell whether the living text moved under it */\n | \"delta.baseline-missing\"\n /** the living requirement changed since this delta was written — merging it would silently discard whoever landed in between */\n | \"delta.baseline-stale\"\n /** the archive creates `services/<id>/` but nothing writes its `model.likec4` — the fleet gate will report the service incomplete */\n | \"service.no-model\"\n /* --- capability axis: the Capability: join against architecture/capabilities.yaml --- */\n /** a delta requirement's `Capability:` entry that the fleet vocabulary does not declare — the merge would land a join that resolves to nothing, so it blocks archive like other errors, and `--approve` overrides it */\n | \"capability.unknown\"\n /** a delta requirement's `Realizes:` entry that names no capability requirement — same argument as `capability.unknown` one join over: the merge would land a pointer at a promise that does not exist, looking exactly like a working one */\n | \"capability.realizes-unknown\"\n /** a capability requirement this feature ADDS that no `Realizes:` line in the same feature's service deltas names — the merge would land a business promise nothing in this change keeps. A warning that GATES, `--approve` overrides: the document is legal (writing ahead of the fleet is the intended use, which is why `capability.requirement-unrealized` warns and never gates), the MERGE is what is unsafe. Joined by `Realizes:`, NOT by `Covers:` — the name is the roadmap's, the join is the other one */\n | \"capability.uncovered\"\n /** the same join in the removal direction — this feature RETIRES a capability requirement that something the merge leaves behind still realizes, so the archive would leave `capability.realizes-unknown` standing against a document nobody in the feature touched. An error, exactly as `openapi.remove-op-consumed` is for the identical shape one axis over */\n | \"capability.remove-requirement-realized\"\n | CapabilityDocCode\n /* --- the domain glossary: a term a feature introduces --- */\n /** a `features/<FEAT>/glossary/<term>.md` whose term the living glossary already defines — the merge is a whole-file copy, so it would replace an authored definition wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local glossary document INTRODUCES a term, and rewriting one belongs in a pull request where git produces the conflict */\n | \"glossary.term-exists\"\n /* --- the use-case axis: a flow a feature introduces --- */\n /** a `features/<FEAT>/usecases/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The flows were not graded, and archiving would copy them into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it */\n | \"usecase.flow-invalid\"\n /** a `features/<FEAT>/usecases/<name>.likec4` whose flow the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored hop sequence wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local flow INTRODUCES a use case, and rewriting one belongs in a pull request where git produces the conflict */\n | \"usecase.flow-exists\"\n /* --- the deployment axis: the topology a feature introduces --- */\n /** a `features/<FEAT>/deployment/<name>.likec4` whose file the living `architecture/` already holds — the merge is a whole-file copy, so it would replace an authored topology wholesale. An error with no legal reading, which is why `--approve` changes nothing about it: a feature-local deployment document INTRODUCES topology, and rewriting one belongs in a pull request where git produces the conflict. The same act, and the same severity, as `usecase.flow-exists` one axis over */\n | \"deployment.doc-exists\"\n /** a `features/<FEAT>/deployment/*.likec4` that could not be read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The topology was not graded, and archiving would copy it into `architecture/` for the next reader's `loam validate --all` to fail on. Mechanical rather than a judgement, which is why `--approve` does not move it; the twin of `usecase.flow-invalid` one axis over */\n | \"deployment.doc-invalid\"\n /** a `features/<FEAT>/deployment/<name>.likec4` whose name is one loam owns inside `architecture/` — `subsystems.likec4` and `services.likec4` (both generated, overwritten wholesale by `loam subsystem sync`, and parsed by nothing, so anything landing in either is invisible from the moment it archives), `landscape.likec4` (spliced into, never replaced), or a path under `usecases/` (the flow tree). Refused whether or not the target exists, which is the point: three of the four fail silently when it does not. An error with no legal reading, so `--approve` changes nothing about it */\n | \"deployment.doc-reserved\"\n /* --- authoring: did a person actually write this? --- */\n /** a document `loam new` scaffolded still carries its exact placeholder text — the merge would publish a requirement, scenario or description nobody authored */\n | \"scaffold.placeholder\"\n /** intent.md is missing, or says nothing outside the scaffold's own comments — nothing states why the feature exists */\n | \"intent.empty\"\n /* --- docs-repo contract: --all only, never a merge question --- */\n /** AGENTS.md carries no version stamp, or one older than the running binary — the agent contract may have drifted */\n | \"agents.stale\";\n"]}
|
|
@@ -40,6 +40,13 @@ export function serviceMaturity(v) {
|
|
|
40
40
|
* What stands between this service and `vouched`, named as the thing to go and
|
|
41
41
|
* do. One rung's worth at a time: telling somebody at `empty` that they also
|
|
42
42
|
* need a vouch is noise, and the vouch is not even possible yet.
|
|
43
|
+
*
|
|
44
|
+
* At the top rung, the one thing a vouch can still owe: a re-vouch, when a
|
|
45
|
+
* stamped document's words moved after the person read them
|
|
46
|
+
* (`ServiceEntry.contentStale`). The RUNG does not move — the stamp is real —
|
|
47
|
+
* but "nothing missing" over words nobody has read since is the false green
|
|
48
|
+
* this list exists to prevent, and `list --needs-work`, `explore` and the
|
|
49
|
+
* context pack all read their worklist from here.
|
|
43
50
|
*/
|
|
44
51
|
export function maturityGaps(v) {
|
|
45
52
|
const s = v.entry;
|
|
@@ -54,6 +61,8 @@ export function maturityGaps(v) {
|
|
|
54
61
|
return ["sources: in the spec.md frontmatter"];
|
|
55
62
|
if (!(s.status === "verified" && s.sources.stamped))
|
|
56
63
|
return [`\`loam vouch --service ${s.id}\``];
|
|
64
|
+
if (s.contentStale.length > 0)
|
|
65
|
+
return [`re-vouch (${s.contentStale.join(", ")} changed since the vouch)`];
|
|
57
66
|
return [];
|
|
58
67
|
}
|
|
59
68
|
/** Counts per rung, every rung present — a stable shape a fleet dashboard can diff. */
|
|
@@ -73,8 +82,9 @@ export function maturityRollup(graded) {
|
|
|
73
82
|
* asks both questions, and `INFRA_KINDS` below is the third they now ask
|
|
74
83
|
* beside them.
|
|
75
84
|
*
|
|
76
|
-
* Extracted on the third strike: `commands/validate/checks/vocabulary.ts
|
|
77
|
-
* `core/gate/partners.ts` and `core/verify/checklist.ts`
|
|
85
|
+
* Extracted on the third strike: `commands/validate/checks/vocabulary.ts` (a
|
|
86
|
+
* module since deleted), `core/gate/partners.ts` and `core/verify/checklist.ts`
|
|
87
|
+
* each carried a copy.
|
|
78
88
|
* Their real home is `core/c4/likec4.ts` beside the model they describe, but
|
|
79
89
|
* that file sits against the 300-line limit and its package holds five files
|
|
80
90
|
* — so the trio lives here, beside `landscapeEvidence`, which reads the same
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maturity.js","sourceRoot":"","sources":["../../../src/core/vocabulary/maturity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The adoption-maturity ladder: one monotone word for how far a service's\n * documentation has got, and what stands between it and the next rung.\n *\n * This lived inside `commands/list.ts` while `list` was the only caller. It\n * moved here when `explore` needed the same answer, because the alternative is\n * the failure `core/kernel/ids/service.ts` already records for the service-id grammar: a\n * second, subtly different copy, so two commands grade the same directory\n * differently and nobody can tell which one is lying. The rung is a fleet dial\n * — `list` prints it per service and in a rollup, `explore` prints it for a\n * service somebody is about to build against — and a dial with two readings is\n * not a dial.\n */\nimport type { ServiceEntry } from \"../repo/entries.js\";\n\n/** The ladder, in order. Each rung stands on every rung below it. */\nexport const MATURITY_LADDER = [\"empty\", \"partial\", \"documented\", \"sourced\", \"vouched\"] as const;\nexport type Maturity = (typeof MATURITY_LADDER)[number];\n\n/**\n * A service entry plus the two facts the directory enumeration cannot know on\n * its own. Both change what a rung MEANS, and both are fleet-level: they need\n * the landscape, not `services/<id>/`.\n */\nexport interface MaturityInput {\n entry: ServiceEntry;\n /** `arch.spec.md` — the architecture-obligations axis, absent from ServiceEntry.has. */\n archSpec: boolean;\n /**\n * Does anything in the fleet call an operation on this service? True unless\n * the landscape PROVES otherwise, exactly as `validate`'s no-openapi grace\n * reads it: a missing or unparseable landscape proves nothing.\n */\n apiExpected: boolean;\n}\n\n/**\n * How far a service's documentation has got. Derived from artifact PRESENCE and\n * provenance state only — the data an enumeration already holds — never from\n * what the artifacts say. COMPLETENESS of adopted docs is explicitly on the\n * unchecked list (brief/unchecked.ts): a service with one endpoint documented out of\n * thirty climbs this ladder exactly as fast as a thorough one, which is why no\n * rung is called \"adopted\".\n *\n * empty services/<id>/ exists, no artifact is in it\n * partial some artifacts, but not the required set\n * documented the artifacts the adopt brief marks required are present\n * sourced the living spec declares `sources` — something ties it to code\n * vouched status: verified with a sources_digest behind it — a person\n * stamped it. `verified` with no digest is a claim with nothing\n * behind it and stays below this rung.\n *\n * `openapi.yaml` is required ONLY where an API is expected. The rung used to\n * demand it of everything, which permanently pinned every worker, cron and\n * consumer in the fleet at `partial` — services that are fully documented and\n * vouched for, reported as unfinished forever, leaving the rollup permanently\n * below its true documentation state. The evidence is the same one\n * `validate` uses to keep `service.no-openapi` quiet, so a service cannot be\n * green in one command and unfinished in the other.\n */\nexport function serviceMaturity(v: MaturityInput): Maturity {\n const s = v.entry;\n if (!Object.values(s.has).some(Boolean) && !v.archSpec && s.adrs === 0) return \"empty\";\n if (!(s.has.model && s.has.spec && (!v.apiExpected || s.has.openapi))) return \"partial\";\n if (!s.sources.declared) return \"documented\";\n if (!(s.status === \"verified\" && s.sources.stamped)) return \"sourced\";\n return \"vouched\";\n}\n\n/**\n * What stands between this service and `vouched`, named as the thing to go and\n * do. One rung's worth at a time: telling somebody at `empty` that they also\n * need a vouch is noise, and the vouch is not even possible yet.\n */\nexport function maturityGaps(v: MaturityInput): string[] {\n const s = v.entry;\n const files = [\n ...(s.has.model ? [] : [\"model.likec4\"]),\n ...(s.has.spec ? [] : [\"spec.md\"]),\n ...(v.apiExpected && !s.has.openapi ? [\"openapi.yaml\"] : []),\n ];\n if (files.length > 0) return files;\n if (!s.sources.declared) return [\"sources: in the spec.md frontmatter\"];\n if (!(s.status === \"verified\" && s.sources.stamped)) return [`\\`loam vouch --service ${s.id}\\``];\n return [];\n}\n\n/** Counts per rung, every rung present — a stable shape a fleet dashboard can diff. */\nexport function maturityRollup(graded: readonly { maturity: Maturity }[]): Record<Maturity, number> {\n const out = Object.fromEntries(MATURITY_LADDER.map((m) => [m, 0])) as Record<Maturity, number>;\n for (const v of graded) out[v.maturity] += 1;\n return out;\n}\n\n/* ------------------------------------------------------------------ */\n/* What the fleet map proves about one service */\n/* ------------------------------------------------------------------ */\n\n/**\n * C4 kinds that model people, and the tag marking an element as somebody\n * else's system. A person is never a service directory, and `#external` is\n * undocumented on purpose — every census and exemption over the fleet map\n * asks both questions, and `INFRA_KINDS` below is the third they now ask\n * beside them.\n *\n * Extracted on the third strike: `commands/validate/checks/vocabulary.ts`,\n * `core/gate/partners.ts` and `core/verify/checklist.ts` each carried a copy.\n * Their real home is `core/c4/likec4.ts` beside the model they describe, but\n * that file sits against the 300-line limit and its package holds five files\n * — so the trio lives here, beside `landscapeEvidence`, which reads the same\n * map for the same per-service questions.\n *\n * `PLATFORM_TAG` joined them from `commands/validate/checks/fleet-shape.ts`,\n * where it was declared beside its one reader. It moved for the rule above the\n * others came for: `fleet/kind-tags.ts` needs to know which tag names loam\n * grades on, and reaching into fleet-shape.ts for a five-character constant\n * would import a module that reads permissions, capabilities and every\n * service's requirements to get it. The two tags loam gives meaning to belong\n * in one place anyway — that list IS what the kind-tag check is about.\n */\nexport const ACTOR_KINDS = new Set([\"person\", \"actor\", \"user\"]);\n\n/**\n * C4 kinds that model INFRASTRUCTURE the fleet runs rather than a system a team\n * adopts. A person is never a `services/<id>/`, and neither is a datastore: a\n * Postgres, a Kafka topic or an S3 bucket has no spec.md to write, no OpenAPI to\n * publish, no maturity rung to climb and nobody to adopt it.\n *\n * The third census category, beside `ACTOR_KINDS` and `EXTERNAL_TAG`, and it\n * exists because the fleet had only two. A store the fleet OWNS and draws at\n * fleet level was neither a person nor somebody else's system, so it earned\n * `landscape.service-undocumented` (\"bind it with `metadata { service }`, or tag\n * it `#external`\") — two remedies, neither of them true of it — and\n * `gate.partner-undocumented` on every service that reads it. `examples/docs`\n * and this repo's own suite both tagged an owned Redis `#external` purely to buy\n * that silence, which is a map that lies about ownership to satisfy a check.\n *\n * The LIMITATION is `ACTOR_KINDS`' exactly, and is stated here so the next\n * reader does not have to measure it: the hook is a kind NAME, compared\n * case-insensitively, so a fleet whose specification spells its kind `postgres`,\n * `store` or `datastore` is not covered. That is deliberate rather than\n * unfinished — a tag (`#infrastructure`) can be added later as a SECOND spelling\n * without moving anything, exactly as `#external` sits beside `ACTOR_KINDS`\n * today. An element of one of these kinds that stands for a real\n * `services/<id>/` — a bound `database`, a title naming a directory — is a\n * service and every reader here must say so first; the kind is the fallback,\n * never the override.\n */\nexport const INFRA_KINDS = new Set([\"database\", \"queue\", \"topic\", \"cache\", \"bucket\"]);\n\n/**\n * The one element kind read as a datastore, compared case-insensitively — the\n * subject of `landscape.datastore-shared`.\n *\n * It was a bare string declared twice, in `commands/validate/fleet/map/consumers.ts`\n * and `commands/validate/fleet/map/attest.ts`, each with a comment pointing at\n * the other. Two literals free to drift about what a store IS are exactly the\n * failure the trio above was extracted to stop: the peer census and the nested\n * census would have graded different fleets the day one of them learned a second\n * spelling.\n */\nexport const DATASTORE_KIND = \"database\";\nexport const EXTERNAL_TAG = \"external\";\n/** Tag marking ubiquitous infrastructure; the scaffolded fleet view excludes it. */\nexport const PLATFORM_TAG = \"platform\";\n\n/**\n * The tags loam GRADES on, as opposed to the ones it merely carries.\n *\n * A tag in this list changes what loam concludes about a fleet: `#external`\n * exempts an element from the landscape↔services reconciliation entirely, and\n * `#platform` silences the hub warning. Every other tag on an element is the\n * author's own vocabulary and loam does not read it.\n *\n * The list exists because since LikeC4 1.59.0 a tag can be declared on a KIND\n * — `specification { element softwareSystem { #external } }` — and LikeC4\n * applies it to every element of that kind. A kind-wide declaration of a tag on\n * this list switches loam's grading off for the whole fleet at once, which is\n * what `kindTagFindings` refuses. A tag NOT on this list is safe to declare\n * kind-wide, so the check must ask exactly this question and no broader one.\n */\nexport const GRADED_TAGS: readonly string[] = [EXTERNAL_TAG, PLATFORM_TAG];\n\n/** One end of a call the fleet map draws, as every per-service view spells it. */\nexport interface LandscapeEdge {\n service: string;\n op: string | null;\n title: string | null;\n}\n\n/**\n * The slice of a parsed relationship the partition reads. Structural on\n * purpose — the smallest parameter type the derivation needs — so this module\n * stays a leaf: `core/c4/likec4.ts`'s `Rel` satisfies it without an import\n * edge from the vocabulary package into the parser.\n */\nexport interface EdgeSource {\n source: string;\n target: string;\n op?: string;\n title?: string;\n}\n\nexport interface LandscapeEvidenceRequest {\n /** The service being asked about. */\n id: string;\n /** Whether the landscape parsed — what makes its silence evidence rather than absence. */\n parses: boolean;\n /** The parsed relationships; empty when the landscape is absent or unparseable. */\n relationships: readonly EdgeSource[];\n /** Every element id in the map, for the `modelled` probe. */\n elementIds: readonly string[];\n /** The shared resolver (`core/c4/resolve/service.ts` `serviceResolver`), widened to plain strings. */\n svcOf: (id: string) => string;\n}\n\nexport interface LandscapeEvidence {\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n /** Whether anything in the fleet map resolves to this service. */\n modelled: boolean;\n /** The `apiExpected` input the ladder above consumes. */\n apiExpected: boolean;\n}\n\n/**\n * The fleet map's evidence about one service: the edges into and out of it,\n * whether the map draws it at all, and whether an API is expected of it.\n *\n * One spelling, because it had grown three. `explore`'s describe and the\n * context pack's living slice partitioned the same relationships with the\n * same positive-evidence rule line for line — an inbound edge carrying an\n * operation proves somebody calls the service; a landscape that is absent or\n * does not parse proves NOTHING about who calls it, so the contract is still\n * owed — and a rule spelled twice is how the same service grades `partial` in\n * one command and `documented` in another. `list`'s serviceViews\n * (`commands/list/views.ts`) derives the same rule fleet-wide in ONE pass —\n * a single `called` set for every service, not a partition per service —\n * so that spelling deliberately stays there, cross-referenced from both\n * sides: calling this per service inside a fleet loop would re-walk the\n * whole relationship list once per service.\n */\nexport function landscapeEvidence(req: LandscapeEvidenceRequest): LandscapeEvidence {\n const { id, parses, relationships, svcOf } = req;\n const inbound: LandscapeEdge[] = [];\n const outbound: LandscapeEdge[] = [];\n for (const r of relationships) {\n // An edge whose BOTH endpoints resolve to this service is its own internal\n // wiring (`svc.web -> svc.db`): filed as inbound it made the service its own\n // caller — `← <id> \"reads\"` in `loam context` (verification 2026-09-04) —\n // and, through `apiExpected` below, told a service nobody calls that it owed\n // an OpenAPI contract. `core/brief/landscape.ts` drops it for the same\n // reason. It still TOUCHES the service, which is why the brief's `touched`\n // and `landscape.service-isolated` read the relationships rather than these\n // two lists.\n if (svcOf(r.source) === id && svcOf(r.target) === id) continue;\n const edge = { op: r.op ?? null, title: r.title ?? null };\n if (svcOf(r.target) === id) inbound.push({ service: svcOf(r.source), ...edge });\n else if (svcOf(r.source) === id) outbound.push({ service: svcOf(r.target), ...edge });\n }\n return {\n inbound,\n outbound,\n modelled: req.elementIds.some((e) => svcOf(e) === id),\n apiExpected: !parses || inbound.some((e) => e.op !== null),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"maturity.js","sourceRoot":"","sources":["../../../src/core/vocabulary/maturity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The adoption-maturity ladder: one monotone word for how far a service's\n * documentation has got, and what stands between it and the next rung.\n *\n * This lived inside `commands/list.ts` while `list` was the only caller. It\n * moved here when `explore` needed the same answer, because the alternative is\n * the failure `core/kernel/ids/service.ts` already records for the service-id grammar: a\n * second, subtly different copy, so two commands grade the same directory\n * differently and nobody can tell which one is lying. The rung is a fleet dial\n * — `list` prints it per service and in a rollup, `explore` prints it for a\n * service somebody is about to build against — and a dial with two readings is\n * not a dial.\n */\nimport type { ServiceEntry } from \"../repo/entries.js\";\n\n/** The ladder, in order. Each rung stands on every rung below it. */\nexport const MATURITY_LADDER = [\"empty\", \"partial\", \"documented\", \"sourced\", \"vouched\"] as const;\nexport type Maturity = (typeof MATURITY_LADDER)[number];\n\n/**\n * A service entry plus the two facts the directory enumeration cannot know on\n * its own. Both change what a rung MEANS, and both are fleet-level: they need\n * the landscape, not `services/<id>/`.\n */\nexport interface MaturityInput {\n entry: ServiceEntry;\n /** `arch.spec.md` — the architecture-obligations axis, absent from ServiceEntry.has. */\n archSpec: boolean;\n /**\n * Does anything in the fleet call an operation on this service? True unless\n * the landscape PROVES otherwise, exactly as `validate`'s no-openapi grace\n * reads it: a missing or unparseable landscape proves nothing.\n */\n apiExpected: boolean;\n}\n\n/**\n * How far a service's documentation has got. Derived from artifact PRESENCE and\n * provenance state only — the data an enumeration already holds — never from\n * what the artifacts say. COMPLETENESS of adopted docs is explicitly on the\n * unchecked list (brief/unchecked.ts): a service with one endpoint documented out of\n * thirty climbs this ladder exactly as fast as a thorough one, which is why no\n * rung is called \"adopted\".\n *\n * empty services/<id>/ exists, no artifact is in it\n * partial some artifacts, but not the required set\n * documented the artifacts the adopt brief marks required are present\n * sourced the living spec declares `sources` — something ties it to code\n * vouched status: verified with a sources_digest behind it — a person\n * stamped it. `verified` with no digest is a claim with nothing\n * behind it and stays below this rung.\n *\n * `openapi.yaml` is required ONLY where an API is expected. The rung used to\n * demand it of everything, which permanently pinned every worker, cron and\n * consumer in the fleet at `partial` — services that are fully documented and\n * vouched for, reported as unfinished forever, leaving the rollup permanently\n * below its true documentation state. The evidence is the same one\n * `validate` uses to keep `service.no-openapi` quiet, so a service cannot be\n * green in one command and unfinished in the other.\n */\nexport function serviceMaturity(v: MaturityInput): Maturity {\n const s = v.entry;\n if (!Object.values(s.has).some(Boolean) && !v.archSpec && s.adrs === 0) return \"empty\";\n if (!(s.has.model && s.has.spec && (!v.apiExpected || s.has.openapi))) return \"partial\";\n if (!s.sources.declared) return \"documented\";\n if (!(s.status === \"verified\" && s.sources.stamped)) return \"sourced\";\n return \"vouched\";\n}\n\n/**\n * What stands between this service and `vouched`, named as the thing to go and\n * do. One rung's worth at a time: telling somebody at `empty` that they also\n * need a vouch is noise, and the vouch is not even possible yet.\n *\n * At the top rung, the one thing a vouch can still owe: a re-vouch, when a\n * stamped document's words moved after the person read them\n * (`ServiceEntry.contentStale`). The RUNG does not move — the stamp is real —\n * but \"nothing missing\" over words nobody has read since is the false green\n * this list exists to prevent, and `list --needs-work`, `explore` and the\n * context pack all read their worklist from here.\n */\nexport function maturityGaps(v: MaturityInput): string[] {\n const s = v.entry;\n const files = [\n ...(s.has.model ? [] : [\"model.likec4\"]),\n ...(s.has.spec ? [] : [\"spec.md\"]),\n ...(v.apiExpected && !s.has.openapi ? [\"openapi.yaml\"] : []),\n ];\n if (files.length > 0) return files;\n if (!s.sources.declared) return [\"sources: in the spec.md frontmatter\"];\n if (!(s.status === \"verified\" && s.sources.stamped)) return [`\\`loam vouch --service ${s.id}\\``];\n if (s.contentStale.length > 0) return [`re-vouch (${s.contentStale.join(\", \")} changed since the vouch)`];\n return [];\n}\n\n/** Counts per rung, every rung present — a stable shape a fleet dashboard can diff. */\nexport function maturityRollup(graded: readonly { maturity: Maturity }[]): Record<Maturity, number> {\n const out = Object.fromEntries(MATURITY_LADDER.map((m) => [m, 0])) as Record<Maturity, number>;\n for (const v of graded) out[v.maturity] += 1;\n return out;\n}\n\n/* ------------------------------------------------------------------ */\n/* What the fleet map proves about one service */\n/* ------------------------------------------------------------------ */\n\n/**\n * C4 kinds that model people, and the tag marking an element as somebody\n * else's system. A person is never a service directory, and `#external` is\n * undocumented on purpose — every census and exemption over the fleet map\n * asks both questions, and `INFRA_KINDS` below is the third they now ask\n * beside them.\n *\n * Extracted on the third strike: `commands/validate/checks/vocabulary.ts` (a\n * module since deleted), `core/gate/partners.ts` and `core/verify/checklist.ts`\n * each carried a copy.\n * Their real home is `core/c4/likec4.ts` beside the model they describe, but\n * that file sits against the 300-line limit and its package holds five files\n * — so the trio lives here, beside `landscapeEvidence`, which reads the same\n * map for the same per-service questions.\n *\n * `PLATFORM_TAG` joined them from `commands/validate/checks/fleet-shape.ts`,\n * where it was declared beside its one reader. It moved for the rule above the\n * others came for: `fleet/kind-tags.ts` needs to know which tag names loam\n * grades on, and reaching into fleet-shape.ts for a five-character constant\n * would import a module that reads permissions, capabilities and every\n * service's requirements to get it. The two tags loam gives meaning to belong\n * in one place anyway — that list IS what the kind-tag check is about.\n */\nexport const ACTOR_KINDS = new Set([\"person\", \"actor\", \"user\"]);\n\n/**\n * C4 kinds that model INFRASTRUCTURE the fleet runs rather than a system a team\n * adopts. A person is never a `services/<id>/`, and neither is a datastore: a\n * Postgres, a Kafka topic or an S3 bucket has no spec.md to write, no OpenAPI to\n * publish, no maturity rung to climb and nobody to adopt it.\n *\n * The third census category, beside `ACTOR_KINDS` and `EXTERNAL_TAG`, and it\n * exists because the fleet had only two. A store the fleet OWNS and draws at\n * fleet level was neither a person nor somebody else's system, so it earned\n * `landscape.service-undocumented` (\"bind it with `metadata { service }`, or tag\n * it `#external`\") — two remedies, neither of them true of it — and\n * `gate.partner-undocumented` on every service that reads it. `examples/docs`\n * and this repo's own suite both tagged an owned Redis `#external` purely to buy\n * that silence, which is a map that lies about ownership to satisfy a check.\n *\n * The LIMITATION is `ACTOR_KINDS`' exactly, and is stated here so the next\n * reader does not have to measure it: the hook is a kind NAME, compared\n * case-insensitively, so a fleet whose specification spells its kind `postgres`,\n * `store` or `datastore` is not covered. That is deliberate rather than\n * unfinished — a tag (`#infrastructure`) can be added later as a SECOND spelling\n * without moving anything, exactly as `#external` sits beside `ACTOR_KINDS`\n * today. An element of one of these kinds that stands for a real\n * `services/<id>/` — a bound `database`, a title naming a directory — is a\n * service and every reader here must say so first; the kind is the fallback,\n * never the override.\n */\nexport const INFRA_KINDS = new Set([\"database\", \"queue\", \"topic\", \"cache\", \"bucket\"]);\n\n/**\n * The one element kind read as a datastore, compared case-insensitively — the\n * subject of `landscape.datastore-shared`.\n *\n * It was a bare string declared twice, in `commands/validate/fleet/map/consumers.ts`\n * and `commands/validate/fleet/map/attest.ts`, each with a comment pointing at\n * the other. Two literals free to drift about what a store IS are exactly the\n * failure the trio above was extracted to stop: the peer census and the nested\n * census would have graded different fleets the day one of them learned a second\n * spelling.\n */\nexport const DATASTORE_KIND = \"database\";\nexport const EXTERNAL_TAG = \"external\";\n/** Tag marking ubiquitous infrastructure; the scaffolded fleet view excludes it. */\nexport const PLATFORM_TAG = \"platform\";\n\n/**\n * The tags loam GRADES on, as opposed to the ones it merely carries.\n *\n * A tag in this list changes what loam concludes about a fleet: `#external`\n * exempts an element from the landscape↔services reconciliation entirely, and\n * `#platform` silences the hub warning. Every other tag on an element is the\n * author's own vocabulary and loam does not read it.\n *\n * The list exists because since LikeC4 1.59.0 a tag can be declared on a KIND\n * — `specification { element softwareSystem { #external } }` — and LikeC4\n * applies it to every element of that kind. A kind-wide declaration of a tag on\n * this list switches loam's grading off for the whole fleet at once, which is\n * what `kindTagFindings` refuses. A tag NOT on this list is safe to declare\n * kind-wide, so the check must ask exactly this question and no broader one.\n */\nexport const GRADED_TAGS: readonly string[] = [EXTERNAL_TAG, PLATFORM_TAG];\n\n/** One end of a call the fleet map draws, as every per-service view spells it. */\nexport interface LandscapeEdge {\n service: string;\n op: string | null;\n title: string | null;\n}\n\n/**\n * The slice of a parsed relationship the partition reads. Structural on\n * purpose — the smallest parameter type the derivation needs — so this module\n * stays a leaf: `core/c4/likec4.ts`'s `Rel` satisfies it without an import\n * edge from the vocabulary package into the parser.\n */\nexport interface EdgeSource {\n source: string;\n target: string;\n op?: string;\n title?: string;\n}\n\nexport interface LandscapeEvidenceRequest {\n /** The service being asked about. */\n id: string;\n /** Whether the landscape parsed — what makes its silence evidence rather than absence. */\n parses: boolean;\n /** The parsed relationships; empty when the landscape is absent or unparseable. */\n relationships: readonly EdgeSource[];\n /** Every element id in the map, for the `modelled` probe. */\n elementIds: readonly string[];\n /** The shared resolver (`core/c4/resolve/service.ts` `serviceResolver`), widened to plain strings. */\n svcOf: (id: string) => string;\n}\n\nexport interface LandscapeEvidence {\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n /** Whether anything in the fleet map resolves to this service. */\n modelled: boolean;\n /** The `apiExpected` input the ladder above consumes. */\n apiExpected: boolean;\n}\n\n/**\n * The fleet map's evidence about one service: the edges into and out of it,\n * whether the map draws it at all, and whether an API is expected of it.\n *\n * One spelling, because it had grown three. `explore`'s describe and the\n * context pack's living slice partitioned the same relationships with the\n * same positive-evidence rule line for line — an inbound edge carrying an\n * operation proves somebody calls the service; a landscape that is absent or\n * does not parse proves NOTHING about who calls it, so the contract is still\n * owed — and a rule spelled twice is how the same service grades `partial` in\n * one command and `documented` in another. `list`'s serviceViews\n * (`commands/list/views.ts`) derives the same rule fleet-wide in ONE pass —\n * a single `called` set for every service, not a partition per service —\n * so that spelling deliberately stays there, cross-referenced from both\n * sides: calling this per service inside a fleet loop would re-walk the\n * whole relationship list once per service.\n */\nexport function landscapeEvidence(req: LandscapeEvidenceRequest): LandscapeEvidence {\n const { id, parses, relationships, svcOf } = req;\n const inbound: LandscapeEdge[] = [];\n const outbound: LandscapeEdge[] = [];\n for (const r of relationships) {\n // An edge whose BOTH endpoints resolve to this service is its own internal\n // wiring (`svc.web -> svc.db`): filed as inbound it made the service its own\n // caller — `← <id> \"reads\"` in `loam context` (verification 2026-09-04) —\n // and, through `apiExpected` below, told a service nobody calls that it owed\n // an OpenAPI contract. `core/brief/landscape.ts` drops it for the same\n // reason. It still TOUCHES the service, which is why the brief's `touched`\n // and `landscape.service-isolated` read the relationships rather than these\n // two lists.\n if (svcOf(r.source) === id && svcOf(r.target) === id) continue;\n const edge = { op: r.op ?? null, title: r.title ?? null };\n if (svcOf(r.target) === id) inbound.push({ service: svcOf(r.source), ...edge });\n else if (svcOf(r.source) === id) outbound.push({ service: svcOf(r.target), ...edge });\n }\n return {\n inbound,\n outbound,\n modelled: req.elementIds.some((e) => svcOf(e) === id),\n apiExpected: !parses || inbound.some((e) => e.op !== null),\n };\n}\n"]}
|
package/examples/README.md
CHANGED
|
@@ -18,6 +18,7 @@ loam archive FEAT-112 --dry-run # an operation being retired, writing nothing
|
|
|
18
18
|
loam verify FEAT-088 # a shipped feature's done-check: attested
|
|
19
19
|
loam verify FEAT-120 # …and its pair, the same check: verified
|
|
20
20
|
loam dependencies # the active-feature graph
|
|
21
|
+
loam list suites # which services each test suite needs up
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
Working on loam itself and running from source, use `npx tsx ../../src/cli.ts <args>` in place of
|
|
@@ -58,6 +59,18 @@ GROUP and earns no finding. Each requirement in both files is a promise a custom
|
|
|
58
59
|
names no service, and carries a `Requirement-ID:` — required here, unlike in a service spec, because
|
|
59
60
|
these documents outlive every service that realizes them.
|
|
60
61
|
|
|
62
|
+
**One journey in two suites.** The same use case also carries `#suite-smoke` and
|
|
63
|
+
`#suite-notifications`, both declared in the landscape's `specification` block, so `loam list
|
|
64
|
+
suites` has a real answer: each suite needs `checkout-web`, `order-service` and
|
|
65
|
+
`notification-service` up — the union of the journey's participants, resolved through the same
|
|
66
|
+
element→service join the step grades use (notification-service is found through its filing under
|
|
67
|
+
`platform/`, because the answer is ids, never paths). Two participants resolve to no service and are
|
|
68
|
+
NAMED rather than dropped: `customer`, who drives the flow, and `kafka.orderEvents`, which is
|
|
69
|
+
`#external` through its `topic` kind and wants a double on the test stand. One journey in both
|
|
70
|
+
suites is the point of the tag: a directory could file it in only one. The suite tags change
|
|
71
|
+
nothing `validate` says here — the view was already graded through its `#cap-` tag — but on a view
|
|
72
|
+
carrying ONLY `#suite-` tags they are the opt-in, and its hops are graded like any other use case's.
|
|
73
|
+
|
|
61
74
|
**Two glossary terms**, `glossary/order.md` and the nested `glossary/payments/authorization.md`,
|
|
62
75
|
each cited from the capability documents that use the word. This is the axis's whole mechanism in
|
|
63
76
|
two files: the citation is an ordinary markdown link, so `loam validate` resolves it like any other
|
|
@@ -94,8 +107,10 @@ for.
|
|
|
94
107
|
- **`features/archive/FEAT-088-refunds/`** — shipped, and **`attested`**. It was merged by the real
|
|
95
108
|
`loam archive`, so its `.loam-before/` snapshot holds the exact bytes the merge overwrote and
|
|
96
109
|
`loam unarchive FEAT-088` would put them back. Its `verification.yaml` is the done-check written
|
|
97
|
-
down
|
|
98
|
-
|
|
110
|
+
down — recorded all at once, so it is the one file (a feature recorded per service carries one
|
|
111
|
+
`verification/<svc>.yaml` per attesting service instead): every claim is confirmed, but the
|
|
112
|
+
scenario claims rest on an agent's word instead of a digest-matched test run. loam does not
|
|
113
|
+
pretend those are the same thing.
|
|
99
114
|
- **`features/archive/FEAT-120-refund-notification/`** — shipped, and **`verified`**. The sequel
|
|
100
115
|
FEAT-088 left open: the money went back and nobody told the customer. It publishes
|
|
101
116
|
`payment.PaymentRefunded` from payment-service and has notification-service turn it into the
|
|
@@ -111,10 +126,10 @@ for.
|
|
|
111
126
|
with its own requirements, architecture requirements and contract, a C4 delta that splices a
|
|
112
127
|
nested element into the living landscape, a `MODIFIED` requirement that **renames its heading**
|
|
113
128
|
while keeping its `Requirement-ID` — loam's rename mechanism — and an **event-contract delta**
|
|
114
|
-
(`specs/payment-service/asyncapi.yaml`)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
(`specs/payment-service/asyncapi.yaml`) that restates payment-service's living contract under
|
|
130
|
+
`loam rebase`-written `x-loam-based-on` pins and adds one new producer side, so the archive merges
|
|
131
|
+
the new slots and leaves every pinned quote to the living contract's own copy. The restatement is
|
|
132
|
+
there to demonstrate the pin; a real delta holds only the slots it adds, changes or removes.
|
|
118
133
|
- **`features/FEAT-112-retire-order-v1/`** — in flight, and the smallest legal shape: no
|
|
119
134
|
`delta.likec4` at all, because retiring an operation moves no boxes. It carries the two halves
|
|
120
135
|
loam requires together for a removal — a `REMOVED` requirement and an `x-loam-remove: true` marker
|
|
@@ -85,6 +85,14 @@ specification {
|
|
|
85
85
|
// cannot carry flattened to `-`. Declared here for the same reason — one
|
|
86
86
|
// specification block serves the whole `architecture/` project.
|
|
87
87
|
tag req-NOTIFY-ONCE
|
|
88
|
+
// The SUITES a flow runs with — `#suite-<name>` on the view, many-to-many, so
|
|
89
|
+
// the one journey below sits in both. Declared here, like every tag, so a
|
|
90
|
+
// mistyped suite is a parse error rather than a second, empty suite.
|
|
91
|
+
// `loam list suites` answers which services each one needs up: the union of
|
|
92
|
+
// its journeys' participants, plus the ones no services/ directory stands for
|
|
93
|
+
// (the customer who drives it, the #external topic in the middle).
|
|
94
|
+
tag suite-smoke
|
|
95
|
+
tag suite-notifications
|
|
88
96
|
// An ARCHITECTURAL OBLIGATION, declared in architecture/obligations.yaml and
|
|
89
97
|
// applied HERE, on the objects it holds for. The decision itself is
|
|
90
98
|
// architecture/adrs/0001-transactional-outbox.md; this tag is the architect
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// A fleet use case: the ordered hops that fulfil one business capability.
|
|
2
2
|
//
|
|
3
3
|
// This file is the whole shape of the axis. A `dynamic view` tagged
|
|
4
|
-
// `#cap-<slug>` opts in to grading
|
|
5
|
-
//
|
|
4
|
+
// `#cap-<slug>` opts in to grading — so does one tagged only `#req-` or only
|
|
5
|
+
// `#suite-<name>`, the suite a journey runs with (`loam list suites`); an
|
|
6
|
+
// untagged one is somebody's hand-drawn diagram and loam never says a word
|
|
7
|
+
// about it. The slug is the capability id
|
|
6
8
|
// from architecture/capabilities.yaml with every `/` flattened to `-`, because
|
|
7
9
|
// a LikeC4 tag name cannot carry a slash — `identity/tokens` would be
|
|
8
10
|
// `#cap-identity-tokens`.
|
|
@@ -49,6 +51,8 @@ views {
|
|
|
49
51
|
dynamic view uc_order_notification {
|
|
50
52
|
#cap-order-notifications
|
|
51
53
|
#req-NOTIFY-ONCE
|
|
54
|
+
#suite-smoke
|
|
55
|
+
#suite-notifications
|
|
52
56
|
title 'Order notification'
|
|
53
57
|
description 'A placed order reaches the customer as exactly one confirmation'
|
|
54
58
|
|
package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# The event-contract delta: what FEAT-101 changes about the messages
|
|
2
2
|
# payment-service puts on the bus.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# the
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
4
|
+
# A delta needs only the slots it adds, changes or removes — the merge leaves
|
|
5
|
+
# every slot it does not declare exactly as the living contract has it. This
|
|
6
|
+
# one restates the living contract around the change ON PURPOSE, to show what
|
|
7
|
+
# a pin does with a restatement: identity is the slot — `channels.<key>`,
|
|
8
|
+
# `operations.<key>`, `components.messages.<key>` — and `x-loam-based-on`
|
|
9
|
+
# (written by `loam rebase`, never by hand) is what lets the merge tell a
|
|
10
|
+
# restated slot from an edited one: a pinned QUOTE is never a merge input, so
|
|
11
|
+
# a change that lands on the living contract after this delta was written
|
|
12
|
+
# survives the archive. Outside a demonstration, leave quotes out: each one
|
|
13
|
+
# is a pin, and a parallel feature's removal turns it into an error. The new
|
|
14
|
+
# slots below carry no pin — there is no living version for them to be based on.
|
|
12
15
|
asyncapi: 3.0.0
|
|
13
16
|
info:
|
|
14
17
|
title: payment-service events
|
package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# API delta for FEAT-101 — payment-split-service (new) gains the split API.
|
|
2
2
|
# On `loam archive` this folds into services/payment-split-service/openapi.yaml (living).
|
|
3
3
|
#
|
|
4
|
-
# A feature contract
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# the shape that silently reverts whoever landed in between.
|
|
4
|
+
# A feature contract holds only what the feature adds, changes or removes, and the merge upserts
|
|
5
|
+
# every operation it spells. Both operations here are new, like the service, so neither carries
|
|
6
|
+
# an `x-loam-based-on` pin — there is no living operation for a pin to be the digest of. On a
|
|
7
|
+
# service that already exists, an operation the delta edits carries one, written by `loam
|
|
8
|
+
# rebase`; one it would only quote belongs out of the delta, because a restatement is exactly
|
|
9
|
+
# the shape that silently reverts whoever landed in between unless something pins it.
|
|
10
10
|
openapi: 3.1.0
|
|
11
11
|
info:
|
|
12
12
|
title: payment-split-service
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# API delta for FEAT-088 — payment-service gains the refund operation.
|
|
2
2
|
#
|
|
3
|
-
# A feature contract
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
3
|
+
# A feature contract holds only what the feature adds, changes or removes. The merge walks only
|
|
4
|
+
# the paths and methods this document spells, so authorizePayment and capturePayment, which it
|
|
5
|
+
# leaves out, stay exactly as the living contract has them. refundPayment is new, so it carries
|
|
6
|
+
# no `x-loam-based-on` pin: a pin is the digest of the LIVING operation, and there is none. Only
|
|
7
|
+
# an operation the living contract already holds needs one (`loam rebase` writes it), and every
|
|
8
|
+
# such operation quoted without being changed is one more pin and a collision waiting for a
|
|
9
|
+
# parallel feature to edit or remove it.
|
|
9
10
|
openapi: 3.1.0
|
|
10
11
|
info:
|
|
11
12
|
title: payment-service
|
|
@@ -51,10 +52,12 @@ components:
|
|
|
51
52
|
refundedAt:
|
|
52
53
|
type: string
|
|
53
54
|
format: date-time
|
|
54
|
-
# Quoted from the living contract unchanged
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
55
|
+
# Quoted from the living contract unchanged, and it did not need to be: the `$ref` to it
|
|
56
|
+
# above resolves into the living document, which already holds Problem. The merge leaves a
|
|
57
|
+
# component the living document holds IDENTICALLY alone; one it holds DIFFERENTLY is
|
|
58
|
+
# overwritten wholesale and reported as `openapi.component-modified`. Components carried no
|
|
59
|
+
# baseline pin when this feature archived; a quote like this one now needs one in the root
|
|
60
|
+
# `x-loam-baselines` record `loam rebase` writes — one more reason to leave it out.
|
|
58
61
|
Problem:
|
|
59
62
|
type: object
|
|
60
63
|
required: [type, title, status]
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
# `action: send|receive`, which is the exact analog of an `operationId` and the thing
|
|
6
6
|
# that makes direction legible; 2.x spells the same thing as publish/subscribe under
|
|
7
7
|
# a channel, where the two words are ambiguous about whose perspective they take. A
|
|
8
|
-
# 2.x document declares no `operations` at all, so loam
|
|
9
|
-
#
|
|
8
|
+
# 2.x document declares no `operations` at all, so loam refuses it as one
|
|
9
|
+
# `asyncapi.invalid` naming the version rather than reading it as a contract with no
|
|
10
|
+
# messages (`asyncapi convert --target-version=3.0.0` upgrades it).
|
|
10
11
|
#
|
|
11
12
|
# The join is the message NAME, spelled three times and required to be identical: the
|
|
12
13
|
# landscape edge's `metadata { publishes }` / `metadata { consumes }`, a requirement's
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
# `action: send|receive`, which is what makes a message's direction legible;
|
|
7
7
|
# 2.x spells the same thing as publish/subscribe under a channel, where the two
|
|
8
8
|
# words are ambiguous about whose perspective they take — and a 2.x document
|
|
9
|
-
#
|
|
9
|
+
# is refused as one `asyncapi.invalid` naming its version, with nothing graded
|
|
10
|
+
# against it (`asyncapi convert --target-version=3.0.0` upgrades it).
|
|
10
11
|
#
|
|
11
12
|
# The name is the spine: the landscape edge's `metadata { consumes }`, the
|
|
12
13
|
# requirement's `Consumes:` line and `name` below are one name spelled three
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ybotok/loam",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.9",
|
|
4
4
|
"description": "Architecture-first semantic integrity and change governance for evolving software systems: connect C4, requirements, contracts, and verification evidence.",
|
|
5
5
|
"author": "spentsov",
|
|
6
6
|
"repository": {
|