@ybotok/loam 0.2.0-alpha.6 → 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 +1477 -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 +108 -11
- package/SCHEMA.md +721 -130
- 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 +82 -33
- 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 +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +64 -13
- package/dist/commands/seed/plan.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 +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +60 -25
- 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 +141 -71
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/report.js +55 -5
- package/dist/commands/subsystem/txn/report.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +121 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +82 -12
- 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/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.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/census.js +26 -3
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +35 -32
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +7 -12
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +16 -5
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/consumers.js +43 -122
- package/dist/commands/validate/fleet/map/consumers.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 +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/ids.js +33 -7
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +17 -35
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/fleet/views/stale.js +31 -14
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/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 +57 -102
- 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 +31 -17
- 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 +37 -21
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +25 -10
- 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 +25 -22
- 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 +62 -24
- 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 +30 -25
- 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 +79 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +1 -1
- package/dist/core/brief/unchecked.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/architecture.js +31 -9
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/documents.js +36 -10
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +14 -21
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +7 -3
- package/dist/core/c4/project/staged.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 +15 -16
- 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 +10 -3
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +64 -19
- 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/paths.js +34 -29
- package/dist/core/repo/paths.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/render/body.js +45 -6
- package/dist/core/repo/tree/render/body.js.map +1 -1
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +19 -2
- package/dist/core/repo/tree/render/stale.js.map +1 -1
- package/dist/core/repo/tree/render/views.js +102 -33
- package/dist/core/repo/tree/render/views.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 +45 -10
- 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 +53 -3
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +28 -9
- package/examples/docs/architecture/landscape.likec4 +21 -7
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +3 -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/order-service/model.likec4 +10 -8
- package/examples/docs/services/payment-service/model.likec4 +6 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
- 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":"consumers.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/consumers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Who consumes a drawn element — and the datastore grades that read the answer.\n *\n * The census used to walk the MAP's relationships alone, and under the extending\n * shape that is a partial view of the fleet: a service draws the container-level\n * call in its own `model.likec4` and the map carries the service-level twin, so a\n * store whose consuming edges were all drawn in models had ZERO consumers and\n * earned nothing at all, while a store reached by three models and one map edge\n * was reported `landscape.datastore-private` naming the one service the map\n * happened to draw (R1). A model is evidence about the fleet exactly as the map\n * is, so both are counted here, once, by one derivation two checks share.\n *\n * WHERE A PRIVATE STORE LIVES, stated once because three surfaces used to answer\n * it differently: a store one service owns is NESTED UNDER THAT SERVICE'S\n * ELEMENT — in the service's extending model (`extend <fqn> { store = database\n * … }`), or inside the service's element on the map when the map draws that\n * service's containers. A second service reaching it later is drawn where that\n * service draws every other call: a container-level edge in ITS model naming\n * `<owner-fqn>.<store>`, carried up to the map as a service-level edge to the\n * owning service. Nothing moves back, and nothing goes ungraded — a nested store\n * two services reach is `landscape.datastore-shared` here (E3).\n *\n * WHICH OF THE TWO PLACEMENTS, and this is measured rather than assumed: a\n * per-service project is `architecture/` plus that ONE model, so an edge naming\n * `<owner-fqn>.<store>` resolves for the second service only when the MAP\n * declares the store. Written in the owner's `extend` block instead, the same\n * edge is two errors on the consumer's own model (\"Could not resolve reference\n * to Referenceable named 'store'\"), so that store is private for good and its\n * second consumer can only ever be drawn at service level. The message below\n * says so; SCHEMA carries the same sentence.\n *\n * The choice is cheap, and both messages now say exactly HOW cheap, because\n * \"reversible\" was false in the one state that used to assert it. The two\n * placements do give the store the same id, `<owner-fqn>.<store>` — but the\n * move is only free in ONE direction once a second service names it. Measured\n * on `examples/docs`: moving `db` out of order-service's `extend` block into\n * `orderService` on the map left the model at 12 elements · 8 relationships,\n * left `Covers: marketplace.orderService.db` in its arch.spec.md resolving, and\n * left checkout-web's container-level edge to it resolving too. Moving it BACK\n * with that second edge drawn is two errors on checkout-web's own model\n * (\"Could not resolve reference to Referenceable named 'db'\"), or\n * `landscape.invalid` plus one `spine.landscape-invalid` per service when the\n * second edge is drawn on the map — because an `extend` block is visible to its\n * own service's project alone. `datastore-shared` printed the unconditional\n * clause in 100% of the states it fires in, every one of which already has the\n * second consumer that makes map→extend break (verification 2026-09-04). So the\n * rule both messages now state is directional: extend→map is always safe;\n * map→extend only while no other service's model or map edge names the store.\n *\n * And the remedy is named for the consumer's own model SHAPE rather than\n * assumed: `extend <fqn>` in a STANDALONE model (one that declares its own\n * `specification`) resolves nothing, because such a model is parsed alone — so\n * prescribing it there turns a clean run into `c4.invalid`. The shape comes off\n * the same `./attest.ts` record the consumer join reads.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason and with its rule: it\n * imports NOTHING from `fleet/`, so the map facts it reads arrive on the input\n * records the caller fills.\n */\nimport { type Elem, type Rel } from \"../../../../core/c4/likec4.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\nimport { type AttestedModel, type NestedStore } from \"./attest.js\";\n\n/** The element kind read as a datastore, compared case-insensitively. */\nconst DATASTORE_KIND = \"database\";\n\nexport interface CensusInput {\n /** The map's own edges — a service-level twin is what the map carries of a container-level call. */\n relationships: Rel[];\n /** The services/<id>/ directories that exist: a consumer is a source filed under a real one. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver every edge join uses. */\n resolve: (id: string) => string;\n /** What each service's own model attests (`./attest.ts`). */\n attested: readonly AttestedModel[];\n}\n\n/**\n * Distinct consumer SERVICES of an element, its nested children included: an\n * edge into `kafka.paymentEvents` is consumption of `kafka`.\n *\n * Two kinds of evidence, unioned. From the MAP: a relationship whose target is\n * the element or a descendant and whose source the shared resolver files under a\n * real `services/<id>/` — persons, other external systems and unresolved sources\n * never count, and two edges from one service are one consumer. From the\n * MODELS: a service whose own model attests an OUTBOUND call at the element or a\n * descendant.\n *\n * The model half reads EXTENDING models only, and the reason is the id space. An\n * extending model's ids are the map's fully-qualified ids — one project — so the\n * join is exact. A standalone model's ids are its own file's, and\n * `core/c4/resolve/attested.ts` says outright that a counterpart is never\n * matched to a landscape element: joining one would file a coincidence of\n * spelling as a coupling between two services.\n */\nexport function consumerCensus(input: CensusInput): (elementId: string) => string[] {\n const reaches = (elementId: string, id: string): boolean => id === elementId || id.startsWith(`${elementId}.`);\n return (elementId: string): string[] => {\n const out = new Set<string>();\n for (const r of input.relationships) {\n if (!reaches(elementId, r.target)) continue;\n const svc = input.resolve(r.source);\n if (input.services.has(svc)) out.add(svc);\n }\n for (const model of input.attested) {\n if (!model.extending) continue;\n if (model.calls.some((c) => c.direction === \"out\" && reaches(elementId, c.counterpartId))) {\n out.add(model.service);\n }\n }\n return [...out].sort();\n };\n}\n\n/** What loam actually read at a consumer's own `model.likec4`, which decides where a store may be nested. */\nexport type ConsumerShape = \"extending\" | \"standalone\" | null;\n\nexport interface DatastoreInput {\n /** Service-LEVEL elements — the fleet map's own boxes, where a store drawn as a peer is found. */\n drawn: Elem[];\n /** Every element the MAP declares, at any depth — the other place a nested store is written. */\n elements: Elem[];\n /** Stores an extending model declares inside its own element (`./attest.ts`). */\n nestedStores: readonly NestedStore[];\n /** The services/<id>/ directories that exist. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver, for naming the consumer's own element on the map. */\n resolve: (id: string) => string;\n /**\n * `census.ts`'s one predicate for \"this element stands for a `services/<id>/`\",\n * injected because a `fleet/map/` module may not import `fleet/`. Both halves\n * of the grade below skip such an element: it is the fleet itself, not its\n * shape, and a store bound to a directory is the binding checks' subject.\n * `./attest.ts` applies the same predicate to the stores a MODEL declares, so\n * all three places answer it once.\n */\n standsForService: (e: Elem) => boolean;\n /** What each service's own model attests (`./attest.ts`) — the consumer join AND the shape the remedy names. */\n attested: readonly AttestedModel[];\n /** The census above, built once and shared with the platform-candidate advisory. */\n consumers: (elementId: string) => string[];\n /** Where a service's directory sits, repo-relative — the enumeration's answer, never `services/<id>`. */\n pathOf: (id: string) => string;\n}\n\n/**\n * A datastore drawn as a fleet-level peer makes a claim its consumer count\n * either supports or refutes: one consumer means the drawing is false — the\n * store is that service's internals, not a system in its own right — while two\n * or more mean the strongest coupling two services can have, which deserves to\n * be stated rather than inferred. A NESTED store is private by construction, so\n * it is silent at one consumer and earns the same `datastore-shared` at two.\n */\nexport function datastoreFindings(input: DatastoreInput): Finding[] {\n const findings: Finding[] = [];\n for (const e of input.drawn) {\n // An element that stands for a real service is the fleet itself, not its\n // shape; a datastore bound to a directory is graded by the binding checks.\n if (input.standsForService(e)) continue;\n if (e.kind.toLowerCase() !== DATASTORE_KIND) continue;\n const consumers = input.consumers(e.id);\n if (consumers.length === 0) continue;\n findings.push(\n consumers.length === 1 ? privateFinding(e, consumers[0]!, input) : sharedFinding(e.title, consumers, \"\", \"\"),\n );\n }\n for (const store of nestedStoresOf(input)) {\n // The owner is a consumer by construction — the store sits inside the element\n // that resolves to it — so silence at one consumer IS the fixed state, and\n // the only question left is whether a second service reaches the same data.\n const consumers = [...new Set([store.owner, ...input.consumers(store.fqn)])].sort();\n if (consumers.length < 2) continue;\n findings.push(\n sharedFinding(\n store.title,\n consumers,\n // The store's own id, and said as such. The clause read \"nested under\n // '<fqn>'\", which sends a reader looking for an element that HOLDS the\n // store — when that id IS the store, and its container is its prefix.\n ` — ${store.owner}'s own store, written as '${store.fqn}'`,\n reversibility(store, consumers.filter((c) => c !== store.owner)),\n ),\n );\n }\n\n return findings;\n}\n\n/**\n * Every store nested under the element that resolves to its owner, from both\n * places the stance allows one to be written: inside the service's element ON\n * THE MAP, and in the service's own extending model. Keyed by fqn, so a\n * map-declared store — which the owner's model slice carries too, since a slice\n * is everything resolving to the service — is one store and not two.\n *\n * Both sources are needed and neither is redundant. The map's covers a service\n * whose model is standalone or absent (no slice to read); the model's covers a\n * store the map never draws, which is the placement the adopt brief prescribes.\n */\nfunction nestedStoresOf(input: DatastoreInput): NestedStore[] {\n const drawnIds = new Set(input.drawn.map((e) => e.id));\n const byFqn = new Map<string, NestedStore>();\n for (const e of input.elements) {\n if (e.kind.toLowerCase() !== DATASTORE_KIND || drawnIds.has(e.id)) continue;\n // The peer branch's guard, which this half was missing: a `database` bound\n // with `metadata { service '<id>' }` and drawn one level down became a store\n // \"owned\" by its own binding, so a second reader made it a shared datastore\n // and the fix offered was to give each service its own copy of a service.\n if (input.standsForService(e)) continue;\n const owner = input.resolve(e.id);\n if (!input.services.has(owner)) continue;\n byFqn.set(e.id, { owner, fqn: e.id, title: e.title });\n }\n for (const store of input.nestedStores) if (!byFqn.has(store.fqn)) byFqn.set(store.fqn, store);\n return [...byFqn.values()];\n}\n\n/**\n * Which way the store may still be moved, now that a second service reaches it.\n *\n * The clause this replaces said the two placements were interchangeable — \"the\n * two placements give it the same id … so moving the declaration between them\n * rewrites no edge\" — and that is false in every state `datastore-shared` fires\n * in, because the finding needs a second consumer and a second consumer's edge\n * resolves only against a store the MAP declares (a per-service project is\n * `architecture/` plus one model). Measured: performing the move the sentence\n * called free turned a clean `examples/docs` into `c4.invalid` on the consumer's\n * model, or into `landscape.invalid` + one `spine.landscape-invalid` per service\n * when the second edge is on the map (verification 2026-09-04).\n *\n * The direction is stated rather than the current placement, and it holds\n * either way round: out of the owner's `extend` block onto the map is always\n * safe, back in is not while another service names the store. (In practice a\n * store this finding reaches is one the MAP declares — a second consumer's edge\n * cannot resolve against an `extend` block, so it could never have been drawn\n * — which is the same fact said from the other side.)\n */\nfunction reversibility(store: NestedStore, others: string[]): string {\n const named = others.length === 1 ? `${others[0]!}'s edge` : `${others.join(\", \")}'s edges`;\n return (\n `. Both placements give it the same id, ${store.fqn}, but with a second consumer the move is ONE-WAY: ` +\n `${named} resolves only against a declaration the MAP holds — a per-service project is \\`architecture/\\` ` +\n `plus one model, so an \\`extend\\` block is visible to its own service's project alone. Out of ` +\n `${store.owner}'s \\`extend\\` block onto ${store.owner}'s element on the map is always safe; moving it ` +\n `back INTO the \\`extend\\` block breaks that edge — \\`c4.invalid\\` on the other service's model, or ` +\n `\\`landscape.invalid\\` when the edge is drawn on the map`\n );\n}\n\n/**\n * Where the store goes, named for the consumer's own model SHAPE — which this\n * sentence used to assert instead of reading. It always prescribed the\n * extending-shape remedy, and following it on a standalone consumer turns a\n * clean run into `c4.invalid`: a model that declares its own `specification` is\n * parsed alone, so `extend <fqn>` there resolves nothing at all (measured — one\n * error, \"Could not resolve reference to Element named '<fqn>'\"). The two\n * placements otherwise give the store the SAME id, which is the fact that makes\n * the choice cheap: measured on examples/docs, moving `db` out of\n * order-service's extend block into `orderService` on the map left the model at\n * 12 elements · 8 relationships, left `Covers: marketplace.orderService.db` in\n * its arch.spec.md resolving, and left checkout-web's edge to it resolving too.\n */\nfunction placement(at: { fqn: string; local: string; path: string; shape: ConsumerShape }): string {\n // Direction, not symmetry. The clause used to read \"moving the declaration\n // between them later rewrites no edge\", which is true only while this store\n // has ONE consumer — the state this finding is in — and false the moment the\n // second consumer it goes on to promise arrives: that consumer's edge resolves\n // against the map's declaration alone. Saying it unconditionally here is what\n // made `datastore-shared` inherit a sentence that was false in every state it\n // printed in (verification 2026-09-04).\n const same =\n `the two placements give it the same id, ${at.fqn}.${at.local}, and with one consumer either move ` +\n `is free — out of the \\`extend\\` block onto the map always is, and back into it while no other ` +\n `service's model or map edge names the store, an \\`extend\\` block being visible to its own service's ` +\n `project alone`;\n if (at.shape === \"extending\") {\n return (\n `A store one service owns is nested UNDER that service's element: write it in the ` +\n `\\`extend ${at.fqn} { … }\\` block of ${at.path}, or inside '${at.fqn}' here when the map draws ` +\n `that service's containers, and delete this peer — ${same}.`\n );\n }\n // The two absent-extending-model arms differ in what there is to do: a\n // STANDALONE model exists and can be migrated; with no model at all there is\n // nothing to migrate, and \"migrate the model\" sent a reader looking for a file\n // loam had just said it could not find (verification 2026-09-04).\n const otherwise =\n at.shape === \"standalone\"\n ? `${at.path} declares its own \\`specification\\`, so it is parsed alone and \\`extend ${at.fqn}\\` ` +\n `resolves nothing there`\n : `loam read no extending model at ${at.path}`;\n const migrate =\n at.shape === \"standalone\"\n ? `migrate the model to the extending shape (SCHEMA.md, \"Two shapes of a service model\")`\n : `write an extending model at ${at.path} (SCHEMA.md, \"Two shapes of a service model\")`;\n return (\n `A store one service owns is nested UNDER that service's element, and this consumer has no ` +\n `extending model to nest it in (${otherwise}): write it inside '${at.fqn}' here, where the map draws ` +\n `that service's containers, and delete this peer — or ${migrate} and write it in an ` +\n `\\`extend ${at.fqn} { … }\\` block there, where ${same}.`\n );\n}\n\n/**\n * The one-consumer message, and it names the PLACEMENT rather than a directory:\n * the remedy used to read \"move it into services/<id>/model.likec4 as a nested\n * container, or add the second consumer's edge\", which offered two remedies as\n * equals and left the second one impossible — an edge naming the moved store is\n * `landscape.invalid` for loam, so an author who took the first remedy believed\n * they had shut the second door (#01). They had not, and the sentence now says\n * how: a later consumer is drawn in ITS OWN model, and loam counts that — as\n * long as that model EXTENDS the map, which the caveat below is careful to say.\n */\nfunction privateFinding(store: Elem, consumer: string, input: DatastoreInput): Finding {\n const fqn = input.drawn.find((e) => input.resolve(e.id) === consumer)?.id ?? consumer;\n const local = store.id.split(\".\").at(-1) ?? store.id;\n const model = input.attested.find((m) => m.service === consumer);\n const shape: ConsumerShape = model === undefined ? null : model.extending ? \"extending\" : \"standalone\";\n return {\n severity: \"warn\",\n code: \"landscape.datastore-private\",\n subject: store.title,\n message:\n `landscape: '${store.title}' is a datastore with a single consumer at fleet level ` +\n `('${consumer}') — drawn as a peer it reads as a system in its own right, available to be ` +\n `depended on. ${placement({ fqn, local, path: `${input.pathOf(consumer)}/model.likec4`, shape })} ` +\n `Nesting closes no door: a second service reaching the same data later draws a container-level ` +\n `edge to ${fqn}.${local} in ITS OWN model plus a service-level edge to '${fqn}' here, and loam ` +\n `counts a consumer an EXTENDING model attests exactly as it counts one drawn here — so that ` +\n `state is \\`landscape.datastore-shared\\`, never silence. A consumer whose model stands alone is ` +\n `not counted (its ids are its own file's, never the map's), so its edge belongs on the map. ` +\n `Which placement: that edge RESOLVES only against a store the map declares (a per-service ` +\n `project is architecture/ plus one model), so ` +\n (shape === \"extending\"\n ? `nest it here when a second consumer is foreseeable and in the model when it is not. `\n : `the map is the placement that keeps that door open. `) +\n `If another service reaches the same data today, draw its edge and this becomes that warning now`,\n };\n}\n\n/** The two-or-more message, shared by the peer and the nested store — one word about coupling, once. */\nfunction sharedFinding(title: string, consumers: string[], where: string, tail: string): Finding {\n return {\n severity: \"warn\",\n code: \"landscape.datastore-shared\",\n subject: title,\n message:\n `landscape: '${title}' is a datastore shared by ${consumers.length} services ` +\n `(${consumers.join(\", \")})${where} — the strongest coupling two services can have, and the ` +\n `hardest to undo. Shared means the same DATA: if they read the same tables or keys, keep it ` +\n `where it is and let this warning state the coupling; if they only share a host or cluster ` +\n `(two schemas, two lock paths), that is operational blast radius — a runbook fact — and the ` +\n `honest model is one private store per service${tail}`,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"consumers.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/consumers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Who consumes a drawn element — and the datastore grade that reads the answer.\n *\n * The census used to walk the MAP's relationships alone, and under the extending\n * shape that is a partial view of the fleet: a service draws the container-level\n * call in its own `model.likec4` and the map carries the service-level twin, so a\n * store whose consuming edges were all drawn in models had ZERO consumers and\n * was graded on the one service the map happened to draw (R1). A model is\n * evidence about the fleet exactly as the map is, so both are counted here,\n * once, by one derivation two checks share.\n *\n * WHERE A STORE LIVES IS THE TEAM'S CHOICE, and this module used to say the\n * opposite at length. Nested under the element that resolves to its owner — in\n * that service's `extend <fqn> { store = database … }` block, or inside its\n * element on the map — and drawn as a fleet-level PEER are both first-class\n * placements, and loam grades neither as wrong. `landscape.datastore-private`\n * existed to push every one-consumer store into the first, its whole message a\n * prescription to delete the peer; it is retired (2026-09-07), the arm below is\n * gone, and no message here prescribes a move.\n *\n * What survives the retirement is the one CONSEQUENCE, which is a fact about\n * LikeC4 rather than a preference: a per-service project is `architecture/` plus\n * that ONE model, so an edge naming `<owner-fqn>.<store>` resolves for a SECOND\n * service only when the MAP declares the store. Written in the owner's `extend`\n * block instead, the same edge is two errors on the consumer's own model\n * (\"Could not resolve reference to Referenceable named 'store'\"). That is the\n * load-bearing half, it is stated by `reversibility` below where it actually\n * bites, and SCHEMA carries the same sentence.\n *\n * The choice is cheap, and the surviving message says exactly HOW cheap, because\n * \"reversible\" was false in the one state that used to assert it. The two\n * placements do give the store the same id, `<owner-fqn>.<store>` — but the\n * move is only free in ONE direction once a second service names it. Measured\n * on `examples/docs`: moving `db` out of order-service's `extend` block into\n * `orderService` on the map left the model at 12 elements · 8 relationships,\n * left `Covers: marketplace.orderService.db` in its arch.spec.md resolving, and\n * left checkout-web's container-level edge to it resolving too. Moving it BACK\n * with that second edge drawn is two errors on checkout-web's own model\n * (\"Could not resolve reference to Referenceable named 'db'\"), or\n * `landscape.invalid` plus one `spine.landscape-invalid` per service when the\n * second edge is drawn on the map — because an `extend` block is visible to its\n * own service's project alone. `datastore-shared` printed the unconditional\n * clause in 100% of the states it fires in, every one of which already has the\n * second consumer that makes map→extend break (verification 2026-09-04). So the\n * rule the message states is directional: extend→map is always safe; map→extend\n * only while no other service's model or map edge names the store.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason and with its rule: it\n * imports NOTHING from `fleet/`, so the map facts it reads arrive on the input\n * records the caller fills.\n */\nimport { type Elem, type Rel } from \"../../../../core/c4/likec4.js\";\nimport { DATASTORE_KIND } from \"../../../../core/vocabulary/maturity.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\nimport { type AttestedModel, type NestedStore } from \"./attest.js\";\n\nexport interface CensusInput {\n /** The map's own edges — a service-level twin is what the map carries of a container-level call. */\n relationships: Rel[];\n /** The services/<id>/ directories that exist: a consumer is a source filed under a real one. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver every edge join uses. */\n resolve: (id: string) => string;\n /** What each service's own model attests (`./attest.ts`). */\n attested: readonly AttestedModel[];\n}\n\n/**\n * Distinct consumer SERVICES of an element, its nested children included: an\n * edge into `kafka.paymentEvents` is consumption of `kafka`.\n *\n * Two kinds of evidence, unioned. From the MAP: a relationship whose target is\n * the element or a descendant and whose source the shared resolver files under a\n * real `services/<id>/` — persons, other external systems and unresolved sources\n * never count, and two edges from one service are one consumer. From the\n * MODELS: a service whose own model attests an OUTBOUND call at the element or a\n * descendant.\n *\n * The model half reads EXTENDING models only, and the reason is the id space. An\n * extending model's ids are the map's fully-qualified ids — one project — so the\n * join is exact. A standalone model's ids are its own file's, and\n * `core/c4/resolve/attested.ts` says outright that a counterpart is never\n * matched to a landscape element: joining one would file a coincidence of\n * spelling as a coupling between two services.\n */\nexport function consumerCensus(input: CensusInput): (elementId: string) => string[] {\n const reaches = (elementId: string, id: string): boolean => id === elementId || id.startsWith(`${elementId}.`);\n return (elementId: string): string[] => {\n const out = new Set<string>();\n for (const r of input.relationships) {\n if (!reaches(elementId, r.target)) continue;\n const svc = input.resolve(r.source);\n if (input.services.has(svc)) out.add(svc);\n }\n for (const model of input.attested) {\n if (!model.extending) continue;\n if (model.calls.some((c) => c.direction === \"out\" && reaches(elementId, c.counterpartId))) {\n out.add(model.service);\n }\n }\n return [...out].sort();\n };\n}\n\nexport interface DatastoreInput {\n /** Service-LEVEL elements — the fleet map's own boxes, where a store drawn as a peer is found. */\n drawn: Elem[];\n /** Every element the MAP declares, at any depth — the other place a nested store is written. */\n elements: Elem[];\n /** Stores an extending model declares inside its own element (`./attest.ts`). */\n nestedStores: readonly NestedStore[];\n /** The services/<id>/ directories that exist. */\n services: ReadonlySet<string>;\n /** The shared element→service resolver, for naming the consumer's own element on the map. */\n resolve: (id: string) => string;\n /**\n * `census.ts`'s one predicate for \"this element stands for a `services/<id>/`\",\n * injected because a `fleet/map/` module may not import `fleet/`. Both halves\n * of the grade below skip such an element: it is the fleet itself, not its\n * shape, and a store bound to a directory is the binding checks' subject.\n * `./attest.ts` applies the same predicate to the stores a MODEL declares, so\n * all three places answer it once.\n */\n standsForService: (e: Elem) => boolean;\n /** The census above, built once and shared with the platform-candidate advisory. */\n consumers: (elementId: string) => string[];\n}\n\n/**\n * A datastore two or more services reach is the strongest coupling two services\n * can have and the hardest to undo, which deserves to be STATED rather than\n * inferred. That is the whole of this grade now, and it applies to both\n * placements alike: a fleet-level peer and a store nested under its owner earn\n * the same `landscape.datastore-shared` at two consumers, and neither is graded\n * at one.\n *\n * The one-consumer arm is gone (2026-09-07). A fleet-level peer with a single\n * consumer used to be `landscape.datastore-private` — \"drawn as a peer it reads\n * as a system in its own right … delete this peer\" — which made WHERE a store is\n * drawn a restriction rather than the team's choice. It was never a fact about\n * the fleet: the same store, nested, was silent, so the finding graded the\n * drawing and not the architecture. The nested half was ALREADY silent at one\n * consumer, and it is that half's rule that now holds for both.\n */\nexport function datastoreFindings(input: DatastoreInput): Finding[] {\n const findings: Finding[] = [];\n for (const e of input.drawn) {\n // An element that stands for a real service is the fleet itself, not its\n // shape; a datastore bound to a directory is graded by the binding checks.\n if (input.standsForService(e)) continue;\n if (e.kind.toLowerCase() !== DATASTORE_KIND) continue;\n const consumers = input.consumers(e.id);\n // One consumer is a store that service owns, drawn where the team chose to\n // draw it — a fact about the map's layout, not about the fleet. Zero is a\n // store nothing reaches, which was never graded here either.\n if (consumers.length < 2) continue;\n findings.push(sharedFinding(e.title, consumers, \"\", \"\"));\n }\n for (const store of nestedStoresOf(input)) {\n // The owner is a consumer by construction — the store sits inside the element\n // that resolves to it — so silence at one consumer IS the fixed state, and\n // the only question left is whether a second service reaches the same data.\n const consumers = [...new Set([store.owner, ...input.consumers(store.fqn)])].sort();\n if (consumers.length < 2) continue;\n findings.push(\n sharedFinding(\n store.title,\n consumers,\n // The store's own id, and said as such. The clause read \"nested under\n // '<fqn>'\", which sends a reader looking for an element that HOLDS the\n // store — when that id IS the store, and its container is its prefix.\n ` — ${store.owner}'s own store, written as '${store.fqn}'`,\n reversibility(store, consumers.filter((c) => c !== store.owner)),\n ),\n );\n }\n\n return findings;\n}\n\n/**\n * Every store nested under the element that resolves to its owner, from both\n * places the stance allows one to be written: inside the service's element ON\n * THE MAP, and in the service's own extending model. Keyed by fqn, so a\n * map-declared store — which the owner's model slice carries too, since a slice\n * is everything resolving to the service — is one store and not two.\n *\n * Both sources are needed and neither is redundant. The map's covers a service\n * whose model is standalone or absent (no slice to read); the model's covers a\n * store the map never draws, which is the placement the adopt brief prescribes.\n */\nfunction nestedStoresOf(input: DatastoreInput): NestedStore[] {\n const drawnIds = new Set(input.drawn.map((e) => e.id));\n const byFqn = new Map<string, NestedStore>();\n for (const e of input.elements) {\n if (e.kind.toLowerCase() !== DATASTORE_KIND || drawnIds.has(e.id)) continue;\n // The peer branch's guard, which this half was missing: a `database` bound\n // with `metadata { service '<id>' }` and drawn one level down became a store\n // \"owned\" by its own binding, so a second reader made it a shared datastore\n // and the fix offered was to give each service its own copy of a service.\n if (input.standsForService(e)) continue;\n const owner = input.resolve(e.id);\n if (!input.services.has(owner)) continue;\n byFqn.set(e.id, { owner, fqn: e.id, title: e.title });\n }\n for (const store of input.nestedStores) if (!byFqn.has(store.fqn)) byFqn.set(store.fqn, store);\n return [...byFqn.values()];\n}\n\n/**\n * Which way the store may still be moved, now that a second service reaches it.\n *\n * The clause this replaces said the two placements were interchangeable — \"the\n * two placements give it the same id … so moving the declaration between them\n * rewrites no edge\" — and that is false in every state `datastore-shared` fires\n * in, because the finding needs a second consumer and a second consumer's edge\n * resolves only against a store the MAP declares (a per-service project is\n * `architecture/` plus one model). Measured: performing the move the sentence\n * called free turned a clean `examples/docs` into `c4.invalid` on the consumer's\n * model, or into `landscape.invalid` + one `spine.landscape-invalid` per service\n * when the second edge is on the map (verification 2026-09-04).\n *\n * The direction is stated rather than the current placement, and it holds\n * either way round: out of the owner's `extend` block onto the map is always\n * safe, back in is not while another service names the store. (In practice a\n * store this finding reaches is one the MAP declares — a second consumer's edge\n * cannot resolve against an `extend` block, so it could never have been drawn\n * — which is the same fact said from the other side.)\n */\nfunction reversibility(store: NestedStore, others: string[]): string {\n const named = others.length === 1 ? `${others[0]!}'s edge` : `${others.join(\", \")}'s edges`;\n return (\n `. Both placements give it the same id, ${store.fqn}, but with a second consumer the move is ONE-WAY: ` +\n `${named} resolves only against a declaration the MAP holds — a per-service project is \\`architecture/\\` ` +\n `plus one model, so an \\`extend\\` block is visible to its own service's project alone. Out of ` +\n `${store.owner}'s \\`extend\\` block onto ${store.owner}'s element on the map is always safe; moving it ` +\n `back INTO the \\`extend\\` block breaks that edge — \\`c4.invalid\\` on the other service's model, or ` +\n `\\`landscape.invalid\\` when the edge is drawn on the map`\n );\n}\n\n/** The two-or-more message, shared by the peer and the nested store — one word about coupling, once. */\nfunction sharedFinding(title: string, consumers: string[], where: string, tail: string): Finding {\n return {\n severity: \"warn\",\n code: \"landscape.datastore-shared\",\n subject: title,\n message:\n `landscape: '${title}' is a datastore shared by ${consumers.length} services ` +\n `(${consumers.join(\", \")})${where} — the strongest coupling two services can have, and the ` +\n `hardest to undo. Shared means the same DATA: if they read the same tables or keys, keep it ` +\n `where it is and let this warning state the coupling; if they only share a host or cluster ` +\n `(two schemas, two lock paths), that is operational blast radius — a runbook fact — and the ` +\n `honest model is one private store per service${tail}`,\n };\n}\n"]}
|
|
@@ -10,15 +10,11 @@
|
|
|
10
10
|
* the grades; this owns which inputs they are handed, which is the part that has
|
|
11
11
|
* twice been got wrong and is worth stating in one place.
|
|
12
12
|
*
|
|
13
|
-
* A sub-package of `fleet/` for
|
|
14
|
-
* it imports NOTHING from `fleet/` itself
|
|
15
|
-
*
|
|
16
|
-
* `scripts/package-graph.mjs` refuses — so everything it needs arrives in one
|
|
17
|
-
* record the caller fills.
|
|
13
|
+
* A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule:
|
|
14
|
+
* it imports NOTHING from `fleet/` itself, so everything it needs arrives in
|
|
15
|
+
* one record the caller fills.
|
|
18
16
|
*/
|
|
19
|
-
import { readCapabilityVocabulary } from "../../../../core/capabilities/capabilities.js";
|
|
20
17
|
import { capabilityRequirementIndex, gradableCapabilityIds } from "../../../../core/capabilities/findings.js";
|
|
21
|
-
import { parseRequirements, readRequirementsDocument } from "../../../../core/document/parse.js";
|
|
22
18
|
import { FleetContext } from "../../../../core/fleet-context.js";
|
|
23
19
|
import { useCaseFindings } from "../usecases/usecases.js";
|
|
24
20
|
/**
|
|
@@ -39,10 +35,8 @@ import { useCaseFindings } from "../usecases/usecases.js";
|
|
|
39
35
|
*/
|
|
40
36
|
export async function fleetUseCaseFindings(input) {
|
|
41
37
|
const { docsDir, fleet } = input;
|
|
42
|
-
const vocabulary =
|
|
43
|
-
const capabilityReqs = await capabilityRequirementIndex(vocabulary,
|
|
44
|
-
? async (p) => parseRequirements(await readRequirementsDocument(p))
|
|
45
|
-
: (p) => fleet.readRequirements(p));
|
|
38
|
+
const vocabulary = await fleet.capabilities(docsDir);
|
|
39
|
+
const capabilityReqs = await capabilityRequirementIndex(vocabulary, (p) => fleet.readRequirements(p));
|
|
46
40
|
return useCaseFindings({
|
|
47
41
|
views: input.views,
|
|
48
42
|
elements: input.elements,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usecases.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/usecases.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"usecases.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/map/usecases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAKjE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAsB1D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAAoB;IAC7D,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACjC,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,cAAc,GAAG,MAAM,0BAA0B,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IACtG,OAAO,eAAe,CAAC;QACrB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,YAAY,EAAE,qBAAqB,CAAC,UAAU,CAAC;QAC/C,cAAc,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC;KAC5E,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The fleet's use cases, graded — the CALL, with the two vocabulary reads it\n * needs, lifted out of `../landscape.ts` when that module reached its line\n * limit.\n *\n * The seam is a phase rather than a subject: everything here happens after the\n * map has parsed and grades documents the map does not own — every\n * `dynamic view` in `architecture/landscape.likec4` and in every\n * `architecture/usecases/*.likec4`. `../usecases/usecases.ts` owns the opt-in and\n * the grades; this owns which inputs they are handed, which is the part that has\n * twice been got wrong and is worth stating in one place.\n *\n * A sub-package of `fleet/` for `./isolation.ts`'s reason, and with its rule:\n * it imports NOTHING from `fleet/` itself, so everything it needs arrives in\n * one record the caller fills.\n */\nimport { capabilityRequirementIndex, gradableCapabilityIds } from \"../../../../core/capabilities/findings.js\";\nimport { FleetContext } from \"../../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport type { Elem, Rel } from \"../../../../core/c4/likec4.js\";\nimport type { ParsedView } from \"../../../../core/c4/parsed/dynamic-views.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\nimport { useCaseFindings } from \"../usecases/usecases.js\";\n\nexport interface FleetUseCases {\n docsDir: DocsDir;\n /**\n * The views come from the PROJECT load, never from a single-file fallback,\n * and that is the file-naming rule made mechanical instead of remembered:\n * only the project load gives a view the `sourcePath` a finding has to name,\n * while a single-file load calls every document `source.c4` — so a message\n * built from that load would send its reader to `architecture/source.c4`, a\n * file that has never existed. No project, no views, and nothing to get wrong.\n */\n views: readonly ParsedView[];\n /** The map's model — what a hop is resolved against. */\n elements: Elem[];\n relationships: Rel[];\n /** The enumerated fleet, and the resolver every other edge join in the target uses. */\n services: ReadonlySet<string>;\n resolve: (id: string) => string;\n fleet: FleetContext;\n}\n\n/**\n * The capability ladder is APPLIED BY ITS OWN FUNCTION rather than re-spelled\n * here: an absent or unreadable capabilities.yaml means silence for the whole\n * family, not a fleet full of unresolved tags, so `null` travels rather than an\n * empty list — which the join would read as \"the fleet declares no capabilities\"\n * and grade every tag against. `gradableCapabilityIds`\n * (`core/capabilities/findings.ts`) is the one statement of that rule, and this\n * is a caller of it precisely so a fourth un-gradable vocabulary state cannot be\n * fixed in core while the command layer keeps handing the join a whole key set.\n *\n * Both reads are already paid for under `--all`: `capabilityFleetFindings` has\n * asked for the vocabulary, and every requirement document behind the `#req-`\n * tag was parsed by it, so the fleet memo answers from cache. The requirement\n * index is built here rather than inside the use-case package so that package\n * never learns what a capability document is.\n */\nexport async function fleetUseCaseFindings(input: FleetUseCases): Promise<Finding[]> {\n const { docsDir, fleet } = input;\n const vocabulary = await fleet.capabilities(docsDir);\n const capabilityReqs = await capabilityRequirementIndex(vocabulary, (p) => fleet.readRequirements(p));\n return useCaseFindings({\n views: input.views,\n elements: input.elements,\n relationships: input.relationships,\n services: input.services,\n resolve: input.resolve,\n capabilities: gradableCapabilityIds(vocabulary),\n requirementsOf: (capability) => capabilityReqs.byCapability.get(capability),\n });\n}\n"]}
|
|
@@ -37,7 +37,6 @@ import { servicePathsAt } from "../../../core/repo/paths.js";
|
|
|
37
37
|
import {} from "../../../core/repo/entries.js";
|
|
38
38
|
import {} from "../../../core/vocabulary/report.js";
|
|
39
39
|
import { FleetContext } from "../../../core/fleet-context.js";
|
|
40
|
-
import { readRequirementsDocument, parseRequirements } from "../../../core/document/parse.js";
|
|
41
40
|
import { coversEntries } from "../checks/requirements.js";
|
|
42
41
|
/**
|
|
43
42
|
* The half that does not depend on the map: the file reads, and its `adr:`
|
|
@@ -234,10 +233,7 @@ async function livingCovers(check) {
|
|
|
234
233
|
for (const path of [paths.spec, paths.archSpec]) {
|
|
235
234
|
if (!existsSync(path))
|
|
236
235
|
continue;
|
|
237
|
-
|
|
238
|
-
? parseRequirements(await readRequirementsDocument(path))
|
|
239
|
-
: await check.fleet.readRequirements(path);
|
|
240
|
-
entries.push(...coversEntries(reqs));
|
|
236
|
+
entries.push(...coversEntries(await check.fleet.readRequirements(path)));
|
|
241
237
|
}
|
|
242
238
|
}
|
|
243
239
|
return entries;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"obligations.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/obligations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architectural obligation axis, graded against the fleet it is about.\n *\n * `core/obligations/obligations.ts` states what the axis IS and why it has this\n * shape. This module is the four questions asked of a fleet that has one, and\n * the order they are asked in — the same division `checks/fleet-shape.ts` keeps\n * for the authorization and capability vocabularies, which this sits beside on\n * the `landscape` target for their reason: an obligation crosses services by\n * construction, so no service target can answer for it.\n *\n * THE FOUR ARE TWO PAIRS, and each pair is one join read in both directions.\n * A tag nothing declares is an ERROR (`obligation.unknown`) and a declaration\n * nothing tags is a WARNING (`obligation.unapplied`) — exactly the asymmetry\n * `permissions.unknown`/`permissions.unenforced` already carry, and for the\n * same reason: a typo in a tag reads like a rule, while a word nobody has\n * adopted yet is an honest state. Then a tagged object no requirement covers is\n * a WARNING (`obligation.uncovered`) and an `adr:` naming no file is an ERROR\n * (`obligation.adr-missing`): the team's work being outstanding is normal, and\n * a pointer at a decision nobody can read is not.\n *\n * `obligation.uncovered` IS THE PREREQUISITE THE ROADMAP NAMED. `c4.uncovered`\n * has always been able to say \"this architecture object owes a requirement\",\n * but only about a NEW tagged element in a feature's `delta.likec4` — never\n * about the map the fleet actually runs on. This asks the same question of the\n * LIVING landscape, against the union of every service's living `arch.spec.md`\n * `Covers:` lines, which is the index that did not exist before.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport {\n closeIds,\n coversDeployEdge,\n coversDeployNode,\n coversEdge,\n coversElement,\n type CoversEntry,\n} from \"../../../core/c4/arch.js\";\nimport { NO_DEPLOYMENT } from \"../../../core/c4/parsed/deployment.js\";\nimport { taggedDeployment, type TaggedDeployment } from \"../../../core/deployment/objects.js\";\nimport { elementService } from \"../../../core/c4/resolve/service.js\";\nimport { OBLIGATION_TAG_PREFIX, type ObligationVocabulary } from \"../../../core/obligations/obligations.js\";\nimport { servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { type ServiceEntry } from \"../../../core/repo/entries.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { readRequirementsDocument, parseRequirements } from \"../../../core/document/parse.js\";\nimport { coversEntries } from \"../checks/requirements.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport interface ObligationCheck {\n docsDir: DocsDir;\n vocabulary: ObligationVocabulary;\n /**\n * The living landscape, PARSED. Not nullable, and the absence is the point:\n * the landscape target returns before it reaches this call when the map is\n * missing or did not parse, so a run that could not read the map asks none of\n * the three questions below rather than answering them empty. \"Loam did not\n * look\" is never the same answer as \"there is nothing there\" — an unreadable\n * map graded as empty would report every declared obligation as applied\n * nowhere — and the suspension lives at the call site because that is where\n * the fact is known. It is also why the vocabulary half is a separate\n * function: those two verdicts must survive exactly those early returns.\n */\n land: LoadedDoc;\n services: ServiceEntry[];\n fleet?: FleetContext;\n}\n\n/**\n * The half that does not depend on the map: the file reads, and its `adr:`\n * pointers resolve.\n *\n * Separate from the map questions because the landscape target returns early\n * twice — an absent map, and one that did not parse — and a broken vocabulary\n * must still be named in exactly the runs where somebody is fixing something\n * else. The same placement `permissionFindings` and `capabilityFleetFindings`\n * already have, one level of precision finer.\n */\nexport function obligationVocabularyFindings(docsDir: DocsDir, vocabulary: ObligationVocabulary): Finding[] {\n if (!vocabulary.present) return [];\n if (vocabulary.invalid !== undefined) {\n // Reported ALONE, exactly as `permissions.invalid` and `capability.invalid`\n // are: every `#obl-` tag in the fleet resolves against this file, so grading\n // them on top of a broken vocabulary is a cascade, not a diagnosis.\n return [\n {\n severity: \"error\",\n code: \"obligation.invalid\",\n message:\n `landscape: architecture/obligations.yaml does not read as a vocabulary — ${vocabulary.invalid}. ` +\n \"Every `#obl-` tag on the fleet map resolves against this file, so none of them can be graded until it parses. \" +\n \"The shape is `obligations: {<id>: {description, adr}}`, ids spelled with letters, digits, `_` and `-` only.\",\n },\n ];\n }\n\n return adrFindings(docsDir, [...vocabulary.byId.values()]);\n}\n\n/** The three questions that need the map, suspended entirely when it could not be read. */\nexport async function obligationFindings(check: ObligationCheck): Promise<Finding[]> {\n const { vocabulary } = check;\n if (!vocabulary.present || vocabulary.invalid !== undefined) return [];\n const findings: Finding[] = [];\n\n // Which objects carry which obligation. Elements and edges are collected\n // together because every question below treats them alike: an obligation\n // applies to a thing on the map, and whether that thing is a box or an arrow\n // changes only how the message spells it and how `Covers:` matches it.\n // The enumerated fleet rides in for the deployment half of the walk: an\n // instance deploys a CONTAINER more often than a whole system, and resolving\n // which service owns one needs the ancestor walk `serviceResolver` performs.\n // It is the same set `uncoveredFindings` resolves edge endpoints with, or the\n // message would name one service and the coverage check count another.\n const known = new Set(check.services.map((s) => s.id));\n const tagged = taggedObjects(check.land, known);\n const applied = new Set(tagged.map((t) => t.obligation));\n\n for (const id of [...applied].sort()) {\n if (vocabulary.byId.has(id)) continue;\n const close = closeIds(id, [...vocabulary.byId.keys()]);\n findings.push({\n severity: \"error\",\n code: \"obligation.unknown\",\n subject: id,\n message:\n `landscape: #${OBLIGATION_TAG_PREFIX}${id} tags ${count(tagged.filter((t) => t.obligation === id).length, \"object\")} on the fleet map, ` +\n `and architecture/obligations.yaml declares no obligation '${id}'` +\n (close.length > 0 ? ` — did you mean: ${close.join(\", \")}?` : \"\") +\n \". A tag that resolves to nothing reads exactly like a rule the fleet keeps: declare it, or fix the tag.\",\n details: tagged.filter((t) => t.obligation === id).map((t) => t.where),\n });\n }\n\n const unapplied = [...vocabulary.byId.keys()].filter((id) => !applied.has(id)).sort();\n if (unapplied.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"obligation.unapplied\",\n message:\n `landscape: ${unapplied.length} declared obligation(s) that no \\`#${OBLIGATION_TAG_PREFIX}\\` tag applies anywhere on the fleet map — ` +\n \"either a decision that was reversed and left its word behind, or one nobody has placed yet. \" +\n \"Tag the elements and edges it governs, or drop the declaration; a vocabulary is worth what cites it.\",\n details: unapplied,\n });\n }\n\n // ONLY A RESOLVED APPLICATION IS ASKED ABOUT COVERAGE. A tag naming no\n // declared obligation has already earned its error above, and \"no requirement\n // covers #obl-typo\" is not a second breach — it is the same one restated in\n // words that ask the reader to write a requirement for a rule that does not\n // exist. The use-case axis draws this line in the same place: only a resolved\n // `#cap-` claim keeps a promise.\n findings.push(...(await uncoveredFindings(check, tagged.filter((t) => vocabulary.byId.has(t.obligation)))));\n return findings;\n}\n\n/** One place an obligation is applied: which obligation, on what, and how a message names it. */\ninterface TaggedObject {\n obligation: string;\n /** The element, the edge, or the deployment object — exactly one is set. */\n element?: Elem;\n edge?: Rel;\n deployed?: TaggedDeployment;\n /**\n * How the finding spells this object, and how a `Covers:` line has to: an\n * element id, `source -> target`, or the `node:` forms.\n */\n where: string;\n /**\n * What the finding is filed under — the service that owns it, where one\n * does. ABSENT for a region, a datacenter or a cluster: nobody-s service\n * runs one, and naming a team that did not ask for the work is worse than\n * leaving the sentence general.\n */\n subject?: string;\n}\n\n/**\n * Every `#obl-` tag on the living map, one entry per (obligation, object) pair.\n *\n * A bare `#obl-` slugs to the empty string and is kept as an application on\n * purpose, exactly as a bare `#cap-` is kept as a claim: the prefix is the\n * author's opt-in however little follows it, and dropping it as unreserved\n * would leave an object that asked to be graded silently ungraded. It then\n * resolves to `obligation.unknown` in every fleet that did not declare an empty\n * id, which is the honest answer.\n */\nfunction taggedObjects(land: LoadedDoc, known: ReadonlySet<string>): TaggedObject[] {\n const out: TaggedObject[] = [];\n for (const element of land.elements) {\n for (const tag of element.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n element,\n where: element.id,\n subject: elementService(element),\n });\n }\n }\n for (const edge of land.relationships) {\n for (const tag of edge.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n edge,\n where: `${edge.source} -> ${edge.target}`,\n subject: edge.target,\n });\n }\n }\n // The topology, walked by the same rule and for the reason the rule exists: a\n // tag is a claim about the object it sits on, and which MODEL that object\n // lives in is not something a reader of the tag can see. Until this walk the\n // same undeclared tag was an error on a container and silence on a\n // datacenter — fail-open, and the case FEAT-1's ARCH-LOAM-DEPLOY-TAGGED was\n // written about. A fleet that draws no topology contributes nothing here.\n for (const d of taggedDeployment(land.deployment ?? NO_DEPLOYMENT, land.elements, known, OBLIGATION_TAG_PREFIX)) {\n out.push({\n obligation: d.obligation,\n deployed: d,\n where: d.where,\n ...(d.service === undefined ? {} : { subject: d.service }),\n });\n }\n return out;\n}\n\n/**\n * `obligation.uncovered` — a tagged object no living arch requirement covers.\n *\n * The index is the union of EVERY service's living `arch.spec.md` `Covers:`\n * lines, because an obligation is placed by the architect on the map and met by\n * whichever team owns the thing it sits on — and a per-service index would\n * report an edge as uncovered because the requirement that covers it lives in\n * the service at the other end. `coversEntries` already drops REMOVED\n * requirements: a retired requirement covers nothing.\n *\n * ONE FINDING PER TAGGED OBJECT, subject = the service that owns it, so a\n * fleet-wide report reads as a worklist per team rather than as one line naming\n * forty edges.\n */\nasync function uncoveredFindings(check: ObligationCheck, tagged: TaggedObject[]): Promise<Finding[]> {\n if (tagged.length === 0) return [];\n const covers = await livingCovers(check);\n const elements = check.land.elements;\n const known = new Set(check.services.map((s) => s.id));\n const covered = (t: TaggedObject): boolean => {\n if (t.element !== undefined) return covers.some((c) => coversElement(c, t.element!));\n if (t.edge !== undefined) return covers.some((c) => coversEdge(c, t.edge!, elements, known));\n const d = t.deployed!;\n return d.edge === undefined\n ? covers.some((c) => coversDeployNode(c, d.id!))\n : covers.some((c) => coversDeployEdge(c, d.edge!.source, d.edge!.target));\n };\n return tagged\n .filter((t) => !covered(t))\n .map((t) => ({\n severity: \"warn\" as const,\n code: \"obligation.uncovered\",\n ...(t.subject === undefined ? {} : { subject: t.subject }),\n message:\n `${t.where} carries #${OBLIGATION_TAG_PREFIX}${t.obligation} and no living arch requirement covers it — ` +\n `the obligation is placed and nothing says it is met. Write the requirement in ` +\n // A region, a datacenter or a cluster is owned by no service, so the\n // sentence stays general rather than naming a team at random. Every\n // other object here has a real owner and keeps the actionable form.\n `${t.subject === undefined ? \"the arch.spec.md of whichever service answers for this topology\" : `${t.subject}'s arch.spec.md`}` +\n ` with \\`Covers: ${t.where}\\` and a scenario that proves it, or drop the tag if the decision no longer applies here.`,\n }));\n}\n\n/**\n * Every `Covers:` entry the LIVING fleet writes, across both requirement\n * documents of every service.\n *\n * Existence is checked for both files and for the same reason\n * `permissionFindings` checks it: `arch.spec.md` is optional and most of a\n * legacy fleet has none, while `spec.md` is missing on a half-adopted service —\n * and `FleetContext.readRequirements` throws ENOENT, which surfaces as\n * `repository-unavailable` and takes the whole `--all` run down. Both axes are\n * read, not only the architecture one: `Covers:` is parsed everywhere for one\n * grammar's sake, and a fleet that wrote its outbox requirement in `spec.md`\n * has said the thing loam is asking about.\n */\nasync function livingCovers(check: ObligationCheck): Promise<CoversEntry[]> {\n const entries: CoversEntry[] = [];\n for (const service of check.services) {\n const paths = servicePathsAt(service.dir);\n for (const path of [paths.spec, paths.archSpec]) {\n if (!existsSync(path)) continue;\n const reqs =\n check.fleet === undefined\n ? parseRequirements(await readRequirementsDocument(path))\n : await check.fleet.readRequirements(path);\n entries.push(...coversEntries(reqs));\n }\n }\n return entries;\n}\n\n/**\n * `obligation.adr-missing` — an `adr:` path that names no file.\n *\n * The path is relative to the docs repo root rather than to\n * `architecture/obligations.yaml`, because that is how a reader of a fleet\n * document thinks about a fleet path, and because the value is a YAML field\n * rather than a markdown link — `link.unresolved` grades links written in prose,\n * and this one is written in a slot loam defined.\n */\nfunction adrFindings(docsDir: DocsDir, obligations: readonly { id: string; adr?: string }[]): Finding[] {\n return obligations\n .filter((o) => o.adr !== undefined && !existsSync(join(docsDir, o.adr)))\n .map((o) => ({\n severity: \"error\" as const,\n code: \"obligation.adr-missing\",\n subject: o.id,\n message:\n `landscape: obligation '${o.id}' names \\`adr: ${o.adr!}\\` and no such file exists — ` +\n \"the ADR is where the decision itself is written, so a pointer at nothing leaves every tag citing a rule nobody can read. \" +\n \"Fix the path (it is relative to the docs repo root), write the record, or drop the field.\",\n }));\n}\n\n/** `1 object` / `3 objects` — the plural spelled once, because three messages count things. */\nfunction count(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? \"\" : \"s\"}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"obligations.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/obligations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architectural obligation axis, graded against the fleet it is about.\n *\n * `core/obligations/obligations.ts` states what the axis IS and why it has this\n * shape. This module is the four questions asked of a fleet that has one, and\n * the order they are asked in — the same division `checks/fleet-shape.ts` keeps\n * for the authorization and capability vocabularies, which this sits beside on\n * the `landscape` target for their reason: an obligation crosses services by\n * construction, so no service target can answer for it.\n *\n * THE FOUR ARE TWO PAIRS, and each pair is one join read in both directions.\n * A tag nothing declares is an ERROR (`obligation.unknown`) and a declaration\n * nothing tags is a WARNING (`obligation.unapplied`) — exactly the asymmetry\n * `permissions.unknown`/`permissions.unenforced` already carry, and for the\n * same reason: a typo in a tag reads like a rule, while a word nobody has\n * adopted yet is an honest state. Then a tagged object no requirement covers is\n * a WARNING (`obligation.uncovered`) and an `adr:` naming no file is an ERROR\n * (`obligation.adr-missing`): the team's work being outstanding is normal, and\n * a pointer at a decision nobody can read is not.\n *\n * `obligation.uncovered` IS THE PREREQUISITE THE ROADMAP NAMED. `c4.uncovered`\n * has always been able to say \"this architecture object owes a requirement\",\n * but only about a NEW tagged element in a feature's `delta.likec4` — never\n * about the map the fleet actually runs on. This asks the same question of the\n * LIVING landscape, against the union of every service's living `arch.spec.md`\n * `Covers:` lines, which is the index that did not exist before.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { type Elem, type LoadedDoc, type Rel } from \"../../../core/c4/likec4.js\";\nimport {\n closeIds,\n coversDeployEdge,\n coversDeployNode,\n coversEdge,\n coversElement,\n type CoversEntry,\n} from \"../../../core/c4/arch.js\";\nimport { NO_DEPLOYMENT } from \"../../../core/c4/parsed/deployment.js\";\nimport { taggedDeployment, type TaggedDeployment } from \"../../../core/deployment/objects.js\";\nimport { elementService } from \"../../../core/c4/resolve/service.js\";\nimport { OBLIGATION_TAG_PREFIX, type ObligationVocabulary } from \"../../../core/obligations/obligations.js\";\nimport { servicePathsAt } from \"../../../core/repo/paths.js\";\nimport { type ServiceEntry } from \"../../../core/repo/entries.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { coversEntries } from \"../checks/requirements.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\nexport interface ObligationCheck {\n docsDir: DocsDir;\n vocabulary: ObligationVocabulary;\n /**\n * The living landscape, PARSED. Not nullable, and the absence is the point:\n * the landscape target returns before it reaches this call when the map is\n * missing or did not parse, so a run that could not read the map asks none of\n * the three questions below rather than answering them empty. \"Loam did not\n * look\" is never the same answer as \"there is nothing there\" — an unreadable\n * map graded as empty would report every declared obligation as applied\n * nowhere — and the suspension lives at the call site because that is where\n * the fact is known. It is also why the vocabulary half is a separate\n * function: those two verdicts must survive exactly those early returns.\n */\n land: LoadedDoc;\n services: ServiceEntry[];\n fleet: FleetContext;\n}\n\n/**\n * The half that does not depend on the map: the file reads, and its `adr:`\n * pointers resolve.\n *\n * Separate from the map questions because the landscape target returns early\n * twice — an absent map, and one that did not parse — and a broken vocabulary\n * must still be named in exactly the runs where somebody is fixing something\n * else. The same placement `permissionFindings` and `capabilityFleetFindings`\n * already have, one level of precision finer.\n */\nexport function obligationVocabularyFindings(docsDir: DocsDir, vocabulary: ObligationVocabulary): Finding[] {\n if (!vocabulary.present) return [];\n if (vocabulary.invalid !== undefined) {\n // Reported ALONE, exactly as `permissions.invalid` and `capability.invalid`\n // are: every `#obl-` tag in the fleet resolves against this file, so grading\n // them on top of a broken vocabulary is a cascade, not a diagnosis.\n return [\n {\n severity: \"error\",\n code: \"obligation.invalid\",\n message:\n `landscape: architecture/obligations.yaml does not read as a vocabulary — ${vocabulary.invalid}. ` +\n \"Every `#obl-` tag on the fleet map resolves against this file, so none of them can be graded until it parses. \" +\n \"The shape is `obligations: {<id>: {description, adr}}`, ids spelled with letters, digits, `_` and `-` only.\",\n },\n ];\n }\n\n return adrFindings(docsDir, [...vocabulary.byId.values()]);\n}\n\n/** The three questions that need the map, suspended entirely when it could not be read. */\nexport async function obligationFindings(check: ObligationCheck): Promise<Finding[]> {\n const { vocabulary } = check;\n if (!vocabulary.present || vocabulary.invalid !== undefined) return [];\n const findings: Finding[] = [];\n\n // Which objects carry which obligation. Elements and edges are collected\n // together because every question below treats them alike: an obligation\n // applies to a thing on the map, and whether that thing is a box or an arrow\n // changes only how the message spells it and how `Covers:` matches it.\n // The enumerated fleet rides in for the deployment half of the walk: an\n // instance deploys a CONTAINER more often than a whole system, and resolving\n // which service owns one needs the ancestor walk `serviceResolver` performs.\n // It is the same set `uncoveredFindings` resolves edge endpoints with, or the\n // message would name one service and the coverage check count another.\n const known = new Set(check.services.map((s) => s.id));\n const tagged = taggedObjects(check.land, known);\n const applied = new Set(tagged.map((t) => t.obligation));\n\n for (const id of [...applied].sort()) {\n if (vocabulary.byId.has(id)) continue;\n const close = closeIds(id, [...vocabulary.byId.keys()]);\n findings.push({\n severity: \"error\",\n code: \"obligation.unknown\",\n subject: id,\n message:\n `landscape: #${OBLIGATION_TAG_PREFIX}${id} tags ${count(tagged.filter((t) => t.obligation === id).length, \"object\")} on the fleet map, ` +\n `and architecture/obligations.yaml declares no obligation '${id}'` +\n (close.length > 0 ? ` — did you mean: ${close.join(\", \")}?` : \"\") +\n \". A tag that resolves to nothing reads exactly like a rule the fleet keeps: declare it, or fix the tag.\",\n details: tagged.filter((t) => t.obligation === id).map((t) => t.where),\n });\n }\n\n const unapplied = [...vocabulary.byId.keys()].filter((id) => !applied.has(id)).sort();\n if (unapplied.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"obligation.unapplied\",\n message:\n `landscape: ${unapplied.length} declared obligation(s) that no \\`#${OBLIGATION_TAG_PREFIX}\\` tag applies anywhere on the fleet map — ` +\n \"either a decision that was reversed and left its word behind, or one nobody has placed yet. \" +\n \"Tag the elements and edges it governs, or drop the declaration; a vocabulary is worth what cites it.\",\n details: unapplied,\n });\n }\n\n // ONLY A RESOLVED APPLICATION IS ASKED ABOUT COVERAGE. A tag naming no\n // declared obligation has already earned its error above, and \"no requirement\n // covers #obl-typo\" is not a second breach — it is the same one restated in\n // words that ask the reader to write a requirement for a rule that does not\n // exist. The use-case axis draws this line in the same place: only a resolved\n // `#cap-` claim keeps a promise.\n findings.push(...(await uncoveredFindings(check, tagged.filter((t) => vocabulary.byId.has(t.obligation)))));\n return findings;\n}\n\n/** One place an obligation is applied: which obligation, on what, and how a message names it. */\ninterface TaggedObject {\n obligation: string;\n /** The element, the edge, or the deployment object — exactly one is set. */\n element?: Elem;\n edge?: Rel;\n deployed?: TaggedDeployment;\n /**\n * How the finding spells this object, and how a `Covers:` line has to: an\n * element id, `source -> target`, or the `node:` forms.\n */\n where: string;\n /**\n * What the finding is filed under — the service that owns it, where one\n * does. ABSENT for a region, a datacenter or a cluster: nobody-s service\n * runs one, and naming a team that did not ask for the work is worse than\n * leaving the sentence general.\n */\n subject?: string;\n}\n\n/**\n * Every `#obl-` tag on the living map, one entry per (obligation, object) pair.\n *\n * A bare `#obl-` slugs to the empty string and is kept as an application on\n * purpose, exactly as a bare `#cap-` is kept as a claim: the prefix is the\n * author's opt-in however little follows it, and dropping it as unreserved\n * would leave an object that asked to be graded silently ungraded. It then\n * resolves to `obligation.unknown` in every fleet that did not declare an empty\n * id, which is the honest answer.\n */\nfunction taggedObjects(land: LoadedDoc, known: ReadonlySet<string>): TaggedObject[] {\n const out: TaggedObject[] = [];\n for (const element of land.elements) {\n for (const tag of element.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n element,\n where: element.id,\n subject: elementService(element),\n });\n }\n }\n for (const edge of land.relationships) {\n for (const tag of edge.tags.filter((t) => t.startsWith(OBLIGATION_TAG_PREFIX))) {\n out.push({\n obligation: tag.slice(OBLIGATION_TAG_PREFIX.length),\n edge,\n where: `${edge.source} -> ${edge.target}`,\n subject: edge.target,\n });\n }\n }\n // The topology, walked by the same rule and for the reason the rule exists: a\n // tag is a claim about the object it sits on, and which MODEL that object\n // lives in is not something a reader of the tag can see. Until this walk the\n // same undeclared tag was an error on a container and silence on a\n // datacenter — fail-open, and the case FEAT-1's ARCH-LOAM-DEPLOY-TAGGED was\n // written about. A fleet that draws no topology contributes nothing here.\n for (const d of taggedDeployment(land.deployment ?? NO_DEPLOYMENT, land.elements, known, OBLIGATION_TAG_PREFIX)) {\n out.push({\n obligation: d.obligation,\n deployed: d,\n where: d.where,\n ...(d.service === undefined ? {} : { subject: d.service }),\n });\n }\n return out;\n}\n\n/**\n * `obligation.uncovered` — a tagged object no living arch requirement covers.\n *\n * The index is the union of EVERY service's living `arch.spec.md` `Covers:`\n * lines, because an obligation is placed by the architect on the map and met by\n * whichever team owns the thing it sits on — and a per-service index would\n * report an edge as uncovered because the requirement that covers it lives in\n * the service at the other end. `coversEntries` already drops REMOVED\n * requirements: a retired requirement covers nothing.\n *\n * ONE FINDING PER TAGGED OBJECT, subject = the service that owns it, so a\n * fleet-wide report reads as a worklist per team rather than as one line naming\n * forty edges.\n */\nasync function uncoveredFindings(check: ObligationCheck, tagged: TaggedObject[]): Promise<Finding[]> {\n if (tagged.length === 0) return [];\n const covers = await livingCovers(check);\n const elements = check.land.elements;\n const known = new Set(check.services.map((s) => s.id));\n const covered = (t: TaggedObject): boolean => {\n if (t.element !== undefined) return covers.some((c) => coversElement(c, t.element!));\n if (t.edge !== undefined) return covers.some((c) => coversEdge(c, t.edge!, elements, known));\n const d = t.deployed!;\n return d.edge === undefined\n ? covers.some((c) => coversDeployNode(c, d.id!))\n : covers.some((c) => coversDeployEdge(c, d.edge!.source, d.edge!.target));\n };\n return tagged\n .filter((t) => !covered(t))\n .map((t) => ({\n severity: \"warn\" as const,\n code: \"obligation.uncovered\",\n ...(t.subject === undefined ? {} : { subject: t.subject }),\n message:\n `${t.where} carries #${OBLIGATION_TAG_PREFIX}${t.obligation} and no living arch requirement covers it — ` +\n `the obligation is placed and nothing says it is met. Write the requirement in ` +\n // A region, a datacenter or a cluster is owned by no service, so the\n // sentence stays general rather than naming a team at random. Every\n // other object here has a real owner and keeps the actionable form.\n `${t.subject === undefined ? \"the arch.spec.md of whichever service answers for this topology\" : `${t.subject}'s arch.spec.md`}` +\n ` with \\`Covers: ${t.where}\\` and a scenario that proves it, or drop the tag if the decision no longer applies here.`,\n }));\n}\n\n/**\n * Every `Covers:` entry the LIVING fleet writes, across both requirement\n * documents of every service.\n *\n * Existence is checked for both files and for the same reason\n * `permissionFindings` checks it: `arch.spec.md` is optional and most of a\n * legacy fleet has none, while `spec.md` is missing on a half-adopted service —\n * and `FleetContext.readRequirements` throws ENOENT, which surfaces as\n * `repository-unavailable` and takes the whole `--all` run down. Both axes are\n * read, not only the architecture one: `Covers:` is parsed everywhere for one\n * grammar's sake, and a fleet that wrote its outbox requirement in `spec.md`\n * has said the thing loam is asking about.\n */\nasync function livingCovers(check: ObligationCheck): Promise<CoversEntry[]> {\n const entries: CoversEntry[] = [];\n for (const service of check.services) {\n const paths = servicePathsAt(service.dir);\n for (const path of [paths.spec, paths.archSpec]) {\n if (!existsSync(path)) continue;\n entries.push(...coversEntries(await check.fleet.readRequirements(path)));\n }\n }\n return entries;\n}\n\n/**\n * `obligation.adr-missing` — an `adr:` path that names no file.\n *\n * The path is relative to the docs repo root rather than to\n * `architecture/obligations.yaml`, because that is how a reader of a fleet\n * document thinks about a fleet path, and because the value is a YAML field\n * rather than a markdown link — `link.unresolved` grades links written in prose,\n * and this one is written in a slot loam defined.\n */\nfunction adrFindings(docsDir: DocsDir, obligations: readonly { id: string; adr?: string }[]): Finding[] {\n return obligations\n .filter((o) => o.adr !== undefined && !existsSync(join(docsDir, o.adr)))\n .map((o) => ({\n severity: \"error\" as const,\n code: \"obligation.adr-missing\",\n subject: o.id,\n message:\n `landscape: obligation '${o.id}' names \\`adr: ${o.adr!}\\` and no such file exists — ` +\n \"the ADR is where the decision itself is written, so a pointer at nothing leaves every tag citing a rule nobody can read. \" +\n \"Fix the path (it is relative to the docs repo root), write the record, or drop the field.\",\n }));\n}\n\n/** `1 object` / `3 objects` — the plural spelled once, because three messages count things. */\nfunction count(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? \"\" : \"s\"}`;\n}\n"]}
|
|
@@ -29,8 +29,12 @@ export function printScorecard(card) {
|
|
|
29
29
|
// because that is where it changes the reading: "120 vouched" and "120
|
|
30
30
|
// vouched (100 sampled)" are different fleets, and a reader who stops at
|
|
31
31
|
// the first number must not have been told the stronger of the two.
|
|
32
|
+
// The content count rides beside the digest count rather than inside the
|
|
33
|
+
// vouched one: both are staleness, of the words and of the code, and
|
|
34
|
+
// "0 stale digest(s)" alone read as "nothing is stale" in a run that had
|
|
35
|
+
// just warned `content.stale`.
|
|
32
36
|
` provenance ${p.vouched} vouched${p.sampledVouched > 0 ? ` (${p.sampledVouched} sampled)` : ""} — ` +
|
|
33
|
-
`${p.staleDigests} stale digest(s) · ${p.unverifiableFromHere} unverifiable from here`);
|
|
37
|
+
`${p.contentStale} stale content · ${p.staleDigests} stale digest(s) · ${p.unverifiableFromHere} unverifiable from here`);
|
|
34
38
|
// The ceiling is the features axis's `active` — the payload deliberately
|
|
35
39
|
// carries that denominator once, so this line reads it from there.
|
|
36
40
|
console.log(` verification ${f.active} feature(s) → ${v.recorded} recorded — ` +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"print.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/scorecard/print.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAkB,MAAM,gBAAgB,CAAC;AAEhD,MAAM,UAAU,cAAc,CAAC,IAAe;IAC5C,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GAC7F,IAAI,CAAC;IACP,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CACT,mBAAmB,IAAI,CAAC,QAAQ,KAAK;QACnC,eAAe,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CACzE,CAAC;IACF,OAAO,CAAC,GAAG;IACT,0EAA0E;IAC1E,uEAAuE;IACvE,yEAAyE;IACzE,oEAAoE;IACpE,mBAAmB,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,cAAc,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK;QACtG,GAAG,CAAC,CAAC,YAAY,sBAAsB,CAAC,CAAC,oBAAoB,yBAAyB,
|
|
1
|
+
{"version":3,"file":"print.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/scorecard/print.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAkB,MAAM,gBAAgB,CAAC;AAEhD,MAAM,UAAU,cAAc,CAAC,IAAe;IAC5C,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GAC7F,IAAI,CAAC;IACP,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CACT,mBAAmB,IAAI,CAAC,QAAQ,KAAK;QACnC,eAAe,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CACzE,CAAC;IACF,OAAO,CAAC,GAAG;IACT,0EAA0E;IAC1E,uEAAuE;IACvE,yEAAyE;IACzE,oEAAoE;IACpE,yEAAyE;IACzE,qEAAqE;IACrE,yEAAyE;IACzE,+BAA+B;IAC/B,mBAAmB,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,cAAc,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK;QACtG,GAAG,CAAC,CAAC,YAAY,oBAAoB,CAAC,CAAC,YAAY,sBAAsB,CAAC,CAAC,oBAAoB,yBAAyB,CAC3H,CAAC;IACF,yEAAyE;IACzE,mEAAmE;IACnE,OAAO,CAAC,GAAG,CACT,mBAAmB,CAAC,CAAC,MAAM,iBAAiB,CAAC,CAAC,QAAQ,cAAc;QAClE,qBAAqB,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;QACvF,aAAa,CAAC,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,aAAa;QAC9D,wEAAwE;QACxE,yEAAyE;QACzE,qEAAqE;QACrE,sEAAsE;QACtE,+DAA+D;QAC/D,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAClF,CAAC;IACF,OAAO,CAAC,GAAG,CACT,mBAAmB,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,QAAQ,cAAc;QAChE,GAAG,CAAC,CAAC,UAAU,gBAAgB,CAAC,CAAC,uBAAuB,kBAAkB,CAC7E,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,SAAS,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC,QAAQ,eAAe,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC;IAC/E,0EAA0E;IAC1E,wEAAwE;IACxE,qEAAqE;IACrE,kDAAkD;IAClD,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;IACxB,OAAO,CAAC,GAAG,CACT,kBAAkB;QAChB,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CACjF,CAAC;IACF,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CACT,4BAA4B,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC9E,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,mBAAmB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAC9E,CAAC;IACF,OAAO,CAAC,GAAG,CACT,mBAAmB,CAAC,CAAC,MAAM,YAAY;QACrC,iBAAiB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAC9E,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The fleet scorecard's one table — how the `--all` text report renders what\n * `./scorecard.js` derived. A module of its own for validate's own discipline\n * (validate.ts's header: the checks produce findings and none of them print;\n * printing happens after the last target is graded), and so the derivation\n * and the formatting do not share a file whose one seam is exactly that.\n *\n * One line per axis, ceiling before actual, read left to right the way the\n * numbers are read in a CI log — except the adoption row, which states\n * participation over the denominator (`requirements 3/40`): adoption is an\n * N-of-M spread, not a ceiling chased by an actual, and forcing it into the\n * arrow idiom would read as one. It prints under `--errors-only` too, on\n * purpose: like the summary footer it is a rollup, not a finding, and\n * `--errors-only` is a findings-rendering lever (report.ts).\n */\nimport { ARTIFACT_STATUSES } from \"../../../../core/status/report.js\";\nimport { ANSWERED_BY } from \"../../../../core/verify/answers.js\";\nimport { VERIFICATION_VERDICTS } from \"../../../../core/verify/record.js\";\nimport { MATURITY_LADDER } from \"../../../../core/vocabulary/maturity.js\";\nimport { ADOPTION_AXES } from \"./adoption.js\";\nimport { type Scorecard } from \"./scorecard.js\";\n\nexport function printScorecard(card: Scorecard): void {\n const { maturity, provenance: p, verification: v, operations: o, messages: m, c4, features: f } =\n card;\n console.log(\"\\nfleet scorecard (ceiling → actual)\");\n console.log(\n ` services ${card.services} — ` +\n MATURITY_LADDER.map((rung) => `${maturity[rung]} ${rung}`).join(\" · \"),\n );\n console.log(\n // The sampled count rides INSIDE the vouched number rather than after it,\n // because that is where it changes the reading: \"120 vouched\" and \"120\n // vouched (100 sampled)\" are different fleets, and a reader who stops at\n // the first number must not have been told the stronger of the two.\n // The content count rides beside the digest count rather than inside the\n // vouched one: both are staleness, of the words and of the code, and\n // \"0 stale digest(s)\" alone read as \"nothing is stale\" in a run that had\n // just warned `content.stale`.\n ` provenance ${p.vouched} vouched${p.sampledVouched > 0 ? ` (${p.sampledVouched} sampled)` : \"\"} — ` +\n `${p.contentStale} stale content · ${p.staleDigests} stale digest(s) · ${p.unverifiableFromHere} unverifiable from here`,\n );\n // The ceiling is the features axis's `active` — the payload deliberately\n // carries that denominator once, so this line reads it from there.\n console.log(\n ` verification ${f.active} feature(s) → ${v.recorded} recorded — ` +\n VERIFICATION_VERDICTS.map((verdict) => `${v.verdicts[verdict]} ${verdict}`).join(\" · \") +\n ` — claims ${v.claims.confirmed}/${v.claims.total} confirmed ` +\n // Every provenance, under its own `answered_by` spelling and always all\n // three — the same stable-shape rendering as the verdicts before it. Two\n // buckets could not name what they held: the line said \"runner\" over\n // claims no runner answered, and no label short of the real one fixes\n // that, because `external-runner` is neither of the other two.\n `(${ANSWERED_BY.map((who) => `${v.claims.answered[who]} ${who}`).join(\" · \")})`,\n );\n console.log(\n ` operations ${o.defined} defined → ${o.governed} governed · ` +\n `${o.deprecated} deprecated (${o.deprecatedStillConsumed} still consumed)`,\n );\n console.log(` messages ${m.defined} defined → ${m.linked} linked`);\n console.log(` c4 ${c4.elements} elements → ${c4.covered} covered`);\n // Participation per axis over the one denominator, then the same facts as\n // the sentence a standup reads out: which axes the fleet has started at\n // all. \"adopted\" here means at least one participating service — the\n // per-axis counts on the line above say how many.\n const a = card.adoption;\n console.log(\n ` adoption ` +\n ADOPTION_AXES.map((axis) => `${axis} ${a[axis]}/${card.services}`).join(\" · \"),\n );\n const adopted = ADOPTION_AXES.filter((axis) => a[axis] > 0);\n const notStarted = ADOPTION_AXES.filter((axis) => a[axis] === 0);\n console.log(\n ` axes adopted: ${adopted.length === 0 ? \"none\" : adopted.join(\", \")}` +\n (notStarted.length === 0 ? \"\" : ` · not started: ${notStarted.join(\", \")}`),\n );\n console.log(\n ` features ${f.active} active — ` +\n ARTIFACT_STATUSES.map((stage) => `${f.stages[stage]} ${stage}`).join(\" · \"),\n );\n}\n"]}
|
|
@@ -101,12 +101,15 @@ async function derive(input) {
|
|
|
101
101
|
const covered = eligible.filter((e) => covers.some((c) => coversElement(c, e))).length;
|
|
102
102
|
// Verification: `list`'s own cells, so the two commands cannot disagree
|
|
103
103
|
// about a record's verdict. Active features only — an archived record is
|
|
104
|
-
// frozen history, not fleet debt. This fan-out is NOT memo hits:
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
104
|
+
// frozen history, not fleet debt. This fan-out is NOT memo hits: reading a
|
|
105
|
+
// feature's record is a directory listing plus a small read per record file
|
|
106
|
+
// (the one-file `verification.yaml` and one `verification/<svc>.yaml` per
|
|
107
|
+
// attesting service), and a recorded cell additionally derives its checklist
|
|
108
|
+
// (the same cost `list` pays for the same column; only its enumerations and
|
|
109
|
+
// delta parse are memo hits through `fleet`) — real filesystem work, so it
|
|
110
|
+
// runs through the bounded pool.
|
|
108
111
|
const features = await fleet.listFeatures(docsDir);
|
|
109
|
-
const cells = await inOrder(features, (f) => featureVerification(docsDir, f));
|
|
112
|
+
const cells = await inOrder(features, (f) => featureVerification(docsDir, f, fleet));
|
|
110
113
|
const recorded = cells.filter((c) => c !== null);
|
|
111
114
|
const verdicts = Object.fromEntries(VERIFICATION_VERDICTS.map((v) => [v, 0]));
|
|
112
115
|
for (const cell of recorded)
|
|
@@ -124,18 +127,21 @@ async function derive(input) {
|
|
|
124
127
|
maturity,
|
|
125
128
|
provenance: {
|
|
126
129
|
vouched: maturity.vouched,
|
|
127
|
-
//
|
|
130
|
+
// Every count joins through `subjectsWith` on the run's OWN findings, so
|
|
128
131
|
// each is structurally the same number as the report beside it — the
|
|
129
132
|
// unverifiable count is the footer's (unverifiableSubjects is the same
|
|
130
133
|
// call on the same code; this package cannot import the parent back).
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
+
// Every code is a LITERAL — the emitter's own spelling — because the
|
|
135
|
+
// stable-code collector reads counting sites and refuses a slot it
|
|
136
|
+
// cannot read; the collector is also what convicts a typo here.
|
|
134
137
|
staleDigests: subjectsWith(targets, "sources.stale"),
|
|
135
138
|
unverifiableFromHere: subjectsWith(targets, "sources.unverifiable-from-here"),
|
|
136
|
-
// Doc-side, so unlike `staleDigests`
|
|
137
|
-
// docs repo: `sources.sampled-vouch`
|
|
139
|
+
// Doc-side, so unlike `staleDigests` these two are complete even from
|
|
140
|
+
// the docs repo: neither `sources.sampled-vouch` nor `content.stale`
|
|
141
|
+
// needs a service repo to fire. `content.stale` names no subject, and
|
|
142
|
+
// `subjectsWith` falls back to the target id — the service, here.
|
|
138
143
|
sampledVouched: subjectsWith(targets, "sources.sampled-vouch"),
|
|
144
|
+
contentStale: subjectsWith(targets, "content.stale"),
|
|
139
145
|
},
|
|
140
146
|
verification: {
|
|
141
147
|
recorded: recorded.length,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scorecard.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/scorecard/scorecard.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet scorecard: ceiling-vs-actual aggregates for `validate --all`,\n * recomputed per invocation from the same memoized FleetContext reads the run\n * already paid for. loam stores no history — week-over-week tracking is the\n * caller's pipeline's job: capture the `scorecard` payload key per run into a\n * metrics store. Each axis is a pair of domain-named counts (`defined` vs\n * `governed`) rather than abstract ceiling/actual keys, because payload keys\n * freeze the moment they ship and the domain nouns survive axis nuance.\n *\n * This module derives and never prints; `./print.js` renders the one table\n * the `--all` text report appends, and `./contracts.js` owns the per-service\n * contract reads it sums.\n */\nimport { existsSync } from \"node:fs\";\nimport { inOrder } from \"../../../../core/kernel/concurrency.js\";\nimport { coversElement } from \"../../../../core/c4/arch.js\";\nimport { serviceResolver } from \"../../../../core/c4/resolve/service.js\";\nimport { type FleetContext } from \"../../../../core/fleet-context.js\";\nimport { type DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { landscapePath } from \"../../../../core/repo/paths.js\";\nimport { fleetStatus } from \"../../../../core/status/fleet/fleet.js\";\nimport { ARTIFACT_STATUSES, type ArtifactStatus } from \"../../../../core/status/report.js\";\nimport { ANSWERED_BY, type AnsweredBy } from \"../../../../core/verify/answers.js\";\nimport { VERIFICATION_VERDICTS, type VerificationVerdict } from \"../../../../core/verify/record.js\";\nimport { maturityRollup, type Maturity } from \"../../../../core/vocabulary/maturity.js\";\nimport { subjectsWith, type TargetReport } from \"../../../../core/vocabulary/report.js\";\nimport { featureVerification, serviceViews, type VerificationCell } from \"../../../list/views.js\";\nimport { drawnSystems } from \"../census.js\";\nimport { readLandscape } from \"../load.js\";\nimport { adoptionOf, type Adoption } from \"./adoption.js\";\nimport { contractCounts, type ContractCounts } from \"./contracts.js\";\n\n/** What `buildScorecard` needs of the run that is already in hand. */\nexport interface ScorecardInput {\n docsDir: DocsDir;\n /** The run's graded targets — the source for the one count only a repo-bound check can make (`sources.stale` walks the service's own repository, and must not run twice). */\n targets: TargetReport[];\n /** The run's memoized snapshot: every read below is a hit on it. */\n fleet: FleetContext;\n /** loam.json's `service` — the one repo whose `sources` are checkable from here (serviceViews' rule). */\n boundService: string | undefined;\n}\n\n/**\n * The additive `scorecard` payload key of `validate --all --json`, and the\n * `--all` text table. Shape frozen on ship, like every payload key: fields\n * may be added, never changed or removed.\n */\nexport interface Scorecard {\n /** Every `services/` directory the enumeration lists — the maturity rollup's denominator. */\n services: number;\n /** Counts per adoption-maturity rung, every rung present (core/vocabulary/maturity.ts). */\n maturity: Record<Maturity, number>;\n provenance: {\n /** Services at the `vouched` rung — a person stamped them. */\n vouched: number;\n /**\n * Of those, the ones where the person read a recorded SAMPLE of the\n * document rather than all of it, by subject off this run's\n * `sources.sampled-vouch` findings.\n *\n * Additive beside `vouched` rather than subtracted from it, because the\n * rung is real — a person did stamp it — but a dial reading\n * `vouched: 120` where 100 of them are sampled is exactly the\n * partial-trust-as-full-trust misreading the scope exists to prevent. A\n * dashboard that wants the stricter number subtracts this one knowingly.\n */\n sampledVouched: number;\n /**\n * Services whose stamped sources digest no longer matches the code, by\n * subject off this run's `sources.stale` findings — only what THIS run\n * could verify, so read it beside the count below, never alone.\n */\n staleDigests: number;\n /** Services whose declared `sources` only their own repos can check. */\n unverifiableFromHere: number;\n };\n verification: {\n /**\n * Active features with a verification record beside them. Its ceiling is\n * `features.active` — deliberately not repeated here: one denominator,\n * derived once, so the two axes cannot disagree about how many features\n * are in flight.\n */\n recorded: number;\n /** The recorded features' three-valued verdicts (core/verify/record.ts), every key present. */\n verdicts: Record<VerificationVerdict, number>;\n claims: {\n /** Questions asked across every recorded feature, `tallyRecord`'s counting. */\n total: number;\n confirmed: number;\n /**\n * Of the confirmed, one count per `answered_by` value (`ANSWERED_BY`),\n * every key present — the stable-shape rule `verdicts` and `stages`\n * follow, so a fourth provenance would appear rather than be absorbed.\n *\n * Read off the answers' own provenance, never by subtracting the\n * attested share: `attestedClaims` counts `scenario.tested` alone, so\n * `confirmed - attested` filed every OTHER kind an agent confirmed — a\n * permission, a capability, an exposed operation — under a test runner\n * that had not run. A record with four agent answers printed as three\n * runner and one agent, which is the reading this axis exists to give.\n */\n answered: Record<AnsweredBy, number>;\n };\n };\n /**\n * The HTTP axis, with a deliberate UNIT split: `defined`/`governed` count\n * contract SLOTS, exactly as the API axis's own findings count operations,\n * while `deprecated`/`deprecatedStillConsumed` count DISTINCT operationIds —\n * a duplicate-id slot pair (already its own warning) retires as ONE\n * operation, one migration owed.\n */\n operations: {\n /** Slots the living contracts define (removal markers are never operations). */\n defined: number;\n /** Slots (like `defined`) whose id a living requirement's `Operations:` line governs. */\n governed: number;\n /** Distinct deprecated operationIds. */\n deprecated: number;\n /** Distinct deprecated ids something still joins to — a living `Operations:` line, or an op-linked inbound landscape edge. */\n deprecatedStillConsumed: number;\n };\n messages: {\n /** Distinct message names the living async contracts declare. */\n defined: number;\n /** Of them, the ones a requirement's `Publishes:`/`Consumes:` line governs. */\n linked: number;\n };\n c4: {\n /** The drawn SYSTEMS — `drawnSystems`, the fleet map's own census, so the map's exemptions (actors, `#external`, groupings, containers) and this count cannot drift. */\n elements: number;\n /** Of them, the ones some living arch requirement's `Covers:` entry names. */\n covered: number;\n };\n /**\n * Services PARTICIPATING in each contract axis — how far the staged\n * adoption has spread, axis by axis. The denominator is `services`, carried\n * once above like every other denominator here. One mechanical rule per\n * axis, per service: `requirements` — spec.md holds at least one\n * `### Requirement:` block (content, not file presence, so a fleet of empty\n * adopt scaffolds honestly reads \"not started\"); `arch` — the same for\n * arch.spec.md; `openapi` / `asyncapi` — the contract file exists;\n * `permissions` / `capabilities` — at least one non-REMOVED requirement in\n * either spec document carries a `Requires:` / `Capability:` entry (the\n * used-set rule the fleet vocabulary checks apply). An axis at 0 is \"not\n * started\" — distinct from partially adopted — and is what licenses the\n * text report's per-axis warning banner (./adoption.js). An unreadable\n * service counts as participating in nothing, the same zeros doctrine as\n * every count here — read these beside the run's `service.unreadable`\n * findings, and note the text renderer refuses to group warnings on a run\n * that has any (report.ts).\n */\n adoption: Adoption;\n features: {\n active: number;\n /**\n * The fleet form's stage per active feature — all five ARTIFACT_STATUSES\n * keys even though that form never grades `draft` (the maturityRollup\n * rule: a stable shape a dashboard can diff).\n */\n stages: Record<ArtifactStatus, number>;\n };\n}\n\n/**\n * Derive the scorecard, or decline. The failure story, per axis: a memoized\n * read that FAILED rejects again on every await, so the per-service fan-out\n * contains those per subject — an unreadable service contributes zeros while\n * its own `service.unreadable` finding names the cause — and every landscape\n * read goes through `../load.ts`'s containment, so an unreadable fleet map\n * zeroes the map-derived axes on every platform instead of deleting the card.\n * The catch is the last line of defence for the fleet-wide reads this module\n * does not own (the verification cells, the fleet status): a rollup that can\n * die must not take the graded report with it. No number rather than a wrong\n * number.\n */\nexport async function buildScorecard(input: ScorecardInput): Promise<Scorecard | null> {\n try {\n return await derive(input);\n } catch {\n // Unreadable is not zero: the axes cannot be counted, so no claim is made.\n return null;\n }\n}\n\nasync function derive(input: ScorecardInput): Promise<Scorecard> {\n const { docsDir, targets, fleet, boundService } = input;\n const services = await fleet.listServices(docsDir);\n\n // The landscape, through the run's memo and the fleet target's containment:\n // an unreadable file grades as one that did not parse, and a landscape that\n // proves nothing contributes no inbound edges and no element census —\n // fail-closed, exactly as the no-openapi grace reads the same absence.\n // Loaded FIRST and handed to serviceViews below, so the maturity rollup\n // costs no second parse of a document this run already holds.\n const lp = landscapePath(docsDir);\n const land = existsSync(lp) ? await readLandscape(() => fleet.loadLikeC4(lp)) : null;\n const proven = land !== null && land.errors.length === 0;\n\n const views = await serviceViews(docsDir, services, boundService, land);\n const maturity = maturityRollup(views);\n\n // Inbound op-linked edges per service, resolved exactly as the API axis\n // resolves them (service/api.ts): the container-aware resolver, so an edge\n // drawn into `paymentService.api` counts for the service that owns it.\n const known: ReadonlySet<string> = new Set(services.map((s) => s.id));\n const inbound = new Map<string, Set<string>>();\n if (proven) {\n const svcOf = serviceResolver(land.elements, known);\n for (const r of land.relationships) {\n if (r.op === undefined) continue;\n const owner: string = svcOf(r.target);\n const ops = inbound.get(owner) ?? new Set<string>();\n ops.add(r.op);\n inbound.set(owner, ops);\n }\n }\n\n // Every read in THIS fan-out is a memo hit — the service targets already\n // parsed both requirement documents and both contracts — so it spawns no new\n // filesystem work and needs no pool (unlike the verification fan-out below).\n const perService = await Promise.all(\n services.map((svc) => contractCounts(svc, fleet, inbound.get(svc.id) ?? new Set<string>())),\n );\n const sum = (pick: (c: ContractCounts) => number): number =>\n perService.reduce((n, c) => n + pick(c), 0);\n const operations = {\n defined: sum((c) => c.operations.defined),\n governed: sum((c) => c.operations.governed),\n deprecated: sum((c) => c.operations.deprecated),\n deprecatedStillConsumed: sum((c) => c.operations.deprecatedStillConsumed),\n };\n const messages = { defined: sum((c) => c.messages.defined), linked: sum((c) => c.messages.linked) };\n const adoption = adoptionOf(perService);\n\n // C4 coverage: the fleet map's own system census (`drawnSystems` — the set\n // the undocumented pass walks) against every living arch.spec.md's `Covers:`\n // entries. Element entries only — an edge entry names no element.\n const covers = perService.flatMap((c) => c.covers);\n const eligible = proven ? drawnSystems(land.elements, known) : [];\n const covered = eligible.filter((e) => covers.some((c) => coversElement(c, e))).length;\n\n // Verification: `list`'s own cells, so the two commands cannot disagree\n // about a record's verdict. Active features only — an archived record is\n // frozen history, not fleet debt. This fan-out is NOT memo hits:\n // readVerification is one small file read per feature, and a recorded cell\n // additionally derives its checklist (the same cost `list` pays for the same\n // column) — real filesystem work, so it runs through the bounded pool.\n const features = await fleet.listFeatures(docsDir);\n const cells = await inOrder(features, (f) => featureVerification(docsDir, f));\n const recorded = cells.filter((c): c is VerificationCell => c !== null);\n const verdicts = Object.fromEntries(VERIFICATION_VERDICTS.map((v) => [v, 0])) as Record<VerificationVerdict, number>;\n for (const cell of recorded) verdicts[cell.verdict] += 1;\n const confirmed = recorded.reduce((n, c) => n + c.confirmed, 0);\n const answered = Object.fromEntries(\n ANSWERED_BY.map((who) => [who, recorded.reduce((n, c) => n + c.answered[who], 0)]),\n ) as Record<AnsweredBy, number>;\n\n // The fleet form of `status`, on this run's own snapshot: presence and\n // dependency order, nothing graded — the stages an agent already branches on.\n const status = await fleetStatus(docsDir, { context: fleet });\n const stages = Object.fromEntries(ARTIFACT_STATUSES.map((s) => [s, 0])) as Record<ArtifactStatus, number>;\n for (const f of status.features) stages[f.stage] += 1;\n\n return {\n services: services.length,\n maturity,\n provenance: {\n vouched: maturity.vouched,\n // Both counts join through `subjectsWith` on the run's OWN findings, so\n // each is structurally the same number as the report beside it — the\n // unverifiable count is the footer's (unverifiableSubjects is the same\n // call on the same code; this package cannot import the parent back).\n // Both codes are LITERALS — provenance/sources.ts's own spellings —\n // because the stable-code collector reads counting sites and refuses a\n // slot it cannot read; the collector is also what convicts a typo here.\n staleDigests: subjectsWith(targets, \"sources.stale\"),\n unverifiableFromHere: subjectsWith(targets, \"sources.unverifiable-from-here\"),\n // Doc-side, so unlike `staleDigests` this one is complete even from the\n // docs repo: `sources.sampled-vouch` needs no service repo to fire.\n sampledVouched: subjectsWith(targets, \"sources.sampled-vouch\"),\n },\n verification: {\n recorded: recorded.length,\n verdicts,\n claims: {\n total: recorded.reduce((n, c) => n + c.claims, 0),\n confirmed,\n answered,\n },\n },\n operations,\n messages,\n c4: { elements: eligible.length, covered },\n adoption,\n features: { active: status.features.length, stages },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scorecard.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/scorecard/scorecard.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet scorecard: ceiling-vs-actual aggregates for `validate --all`,\n * recomputed per invocation from the same memoized FleetContext reads the run\n * already paid for. loam stores no history — week-over-week tracking is the\n * caller's pipeline's job: capture the `scorecard` payload key per run into a\n * metrics store. Each axis is a pair of domain-named counts (`defined` vs\n * `governed`) rather than abstract ceiling/actual keys, because payload keys\n * freeze the moment they ship and the domain nouns survive axis nuance.\n *\n * This module derives and never prints; `./print.js` renders the one table\n * the `--all` text report appends, and `./contracts.js` owns the per-service\n * contract reads it sums.\n */\nimport { existsSync } from \"node:fs\";\nimport { inOrder } from \"../../../../core/kernel/concurrency.js\";\nimport { coversElement } from \"../../../../core/c4/arch.js\";\nimport { serviceResolver } from \"../../../../core/c4/resolve/service.js\";\nimport { type FleetContext } from \"../../../../core/fleet-context.js\";\nimport { type DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport { landscapePath } from \"../../../../core/repo/paths.js\";\nimport { fleetStatus } from \"../../../../core/status/fleet/fleet.js\";\nimport { ARTIFACT_STATUSES, type ArtifactStatus } from \"../../../../core/status/report.js\";\nimport { ANSWERED_BY, type AnsweredBy } from \"../../../../core/verify/answers.js\";\nimport { VERIFICATION_VERDICTS, type VerificationVerdict } from \"../../../../core/verify/record.js\";\nimport { maturityRollup, type Maturity } from \"../../../../core/vocabulary/maturity.js\";\nimport { subjectsWith, type TargetReport } from \"../../../../core/vocabulary/report.js\";\nimport { featureVerification, serviceViews, type VerificationCell } from \"../../../list/views.js\";\nimport { drawnSystems } from \"../census.js\";\nimport { readLandscape } from \"../load.js\";\nimport { adoptionOf, type Adoption } from \"./adoption.js\";\nimport { contractCounts, type ContractCounts } from \"./contracts.js\";\n\n/** What `buildScorecard` needs of the run that is already in hand. */\nexport interface ScorecardInput {\n docsDir: DocsDir;\n /** The run's graded targets — the source for the one count only a repo-bound check can make (`sources.stale` walks the service's own repository, and must not run twice). */\n targets: TargetReport[];\n /** The run's memoized snapshot: every read below is a hit on it. */\n fleet: FleetContext;\n /** loam.json's `service` — the one repo whose `sources` are checkable from here (serviceViews' rule). */\n boundService: string | undefined;\n}\n\n/**\n * The additive `scorecard` payload key of `validate --all --json`, and the\n * `--all` text table. Shape frozen on ship, like every payload key: fields\n * may be added, never changed or removed.\n */\nexport interface Scorecard {\n /** Every `services/` directory the enumeration lists — the maturity rollup's denominator. */\n services: number;\n /** Counts per adoption-maturity rung, every rung present (core/vocabulary/maturity.ts). */\n maturity: Record<Maturity, number>;\n provenance: {\n /** Services at the `vouched` rung — a person stamped them. */\n vouched: number;\n /**\n * Of those, the ones where the person read a recorded SAMPLE of the\n * document rather than all of it, by subject off this run's\n * `sources.sampled-vouch` findings.\n *\n * Additive beside `vouched` rather than subtracted from it, because the\n * rung is real — a person did stamp it — but a dial reading\n * `vouched: 120` where 100 of them are sampled is exactly the\n * partial-trust-as-full-trust misreading the scope exists to prevent. A\n * dashboard that wants the stricter number subtracts this one knowingly.\n */\n sampledVouched: number;\n /**\n * Services whose stamped sources digest no longer matches the code, by\n * subject off this run's `sources.stale` findings — only what THIS run\n * could verify, so read it beside the count below, never alone.\n */\n staleDigests: number;\n /** Services whose declared `sources` only their own repos can check. */\n unverifiableFromHere: number;\n /**\n * Services with a stamped document whose WORDS moved after the vouch, by\n * subject off this run's `content.stale` findings. The doc-side half of\n * staleness, so unlike `staleDigests` it is complete from the docs repo —\n * and its absence was the scorecard saying \"1 vouched — 0 stale\" in the\n * same run that warned `content.stale` one line above it. Additive beside\n * `vouched` for `sampledVouched`'s reason.\n */\n contentStale: number;\n };\n verification: {\n /**\n * Active features with a verification record beside them. Its ceiling is\n * `features.active` — deliberately not repeated here: one denominator,\n * derived once, so the two axes cannot disagree about how many features\n * are in flight.\n */\n recorded: number;\n /** The recorded features' three-valued verdicts (core/verify/record.ts), every key present. */\n verdicts: Record<VerificationVerdict, number>;\n claims: {\n /** Questions asked across every recorded feature, `tallyRecord`'s counting. */\n total: number;\n confirmed: number;\n /**\n * Of the confirmed, one count per `answered_by` value (`ANSWERED_BY`),\n * every key present — the stable-shape rule `verdicts` and `stages`\n * follow, so a fourth provenance would appear rather than be absorbed.\n *\n * Read off the answers' own provenance, never by subtracting the\n * attested share: `attestedClaims` counts `scenario.tested` alone, so\n * `confirmed - attested` filed every OTHER kind an agent confirmed — a\n * permission, a capability, an exposed operation — under a test runner\n * that had not run. A record with four agent answers printed as three\n * runner and one agent, which is the reading this axis exists to give.\n */\n answered: Record<AnsweredBy, number>;\n };\n };\n /**\n * The HTTP axis, with a deliberate UNIT split: `defined`/`governed` count\n * contract SLOTS, exactly as the API axis's own findings count operations,\n * while `deprecated`/`deprecatedStillConsumed` count DISTINCT operationIds —\n * a duplicate-id slot pair (already its own warning) retires as ONE\n * operation, one migration owed.\n */\n operations: {\n /** Slots the living contracts define (removal markers are never operations). */\n defined: number;\n /** Slots (like `defined`) whose id a living requirement's `Operations:` line governs. */\n governed: number;\n /** Distinct deprecated operationIds. */\n deprecated: number;\n /** Distinct deprecated ids something still joins to — a living `Operations:` line, or an op-linked inbound landscape edge. */\n deprecatedStillConsumed: number;\n };\n messages: {\n /** Distinct message names the living async contracts declare. */\n defined: number;\n /** Of them, the ones a requirement's `Publishes:`/`Consumes:` line governs. */\n linked: number;\n };\n c4: {\n /** The drawn SYSTEMS — `drawnSystems`, the fleet map's own census, so the map's exemptions (actors, INFRA kinds the fleet runs rather than adopts, `#external`, groupings, containers) and this count cannot drift. An INFRA-kind element that DOES stand for a `services/<id>/` stays in, so adopting a store never shrinks the denominator. */\n elements: number;\n /** Of them, the ones some living arch requirement's `Covers:` entry names. */\n covered: number;\n };\n /**\n * Services PARTICIPATING in each contract axis — how far the staged\n * adoption has spread, axis by axis. The denominator is `services`, carried\n * once above like every other denominator here. One mechanical rule per\n * axis, per service: `requirements` — spec.md holds at least one\n * `### Requirement:` block (content, not file presence, so a fleet of empty\n * adopt scaffolds honestly reads \"not started\"); `arch` — the same for\n * arch.spec.md; `openapi` / `asyncapi` — the contract file exists;\n * `permissions` / `capabilities` — at least one non-REMOVED requirement in\n * either spec document carries a `Requires:` / `Capability:` entry (the\n * used-set rule the fleet vocabulary checks apply). An axis at 0 is \"not\n * started\" — distinct from partially adopted — and is what licenses the\n * text report's per-axis warning banner (./adoption.js). An unreadable\n * service counts as participating in nothing, the same zeros doctrine as\n * every count here — read these beside the run's `service.unreadable`\n * findings, and note the text renderer refuses to group warnings on a run\n * that has any (report.ts).\n */\n adoption: Adoption;\n features: {\n active: number;\n /**\n * The fleet form's stage per active feature — all five ARTIFACT_STATUSES\n * keys even though that form never grades `draft` (the maturityRollup\n * rule: a stable shape a dashboard can diff).\n */\n stages: Record<ArtifactStatus, number>;\n };\n}\n\n/**\n * Derive the scorecard, or decline. The failure story, per axis: a memoized\n * read that FAILED rejects again on every await, so the per-service fan-out\n * contains those per subject — an unreadable service contributes zeros while\n * its own `service.unreadable` finding names the cause — and every landscape\n * read goes through `../load.ts`'s containment, so an unreadable fleet map\n * zeroes the map-derived axes on every platform instead of deleting the card.\n * The catch is the last line of defence for the fleet-wide reads this module\n * does not own (the verification cells, the fleet status): a rollup that can\n * die must not take the graded report with it. No number rather than a wrong\n * number.\n */\nexport async function buildScorecard(input: ScorecardInput): Promise<Scorecard | null> {\n try {\n return await derive(input);\n } catch {\n // Unreadable is not zero: the axes cannot be counted, so no claim is made.\n return null;\n }\n}\n\nasync function derive(input: ScorecardInput): Promise<Scorecard> {\n const { docsDir, targets, fleet, boundService } = input;\n const services = await fleet.listServices(docsDir);\n\n // The landscape, through the run's memo and the fleet target's containment:\n // an unreadable file grades as one that did not parse, and a landscape that\n // proves nothing contributes no inbound edges and no element census —\n // fail-closed, exactly as the no-openapi grace reads the same absence.\n // Loaded FIRST and handed to serviceViews below, so the maturity rollup\n // costs no second parse of a document this run already holds.\n const lp = landscapePath(docsDir);\n const land = existsSync(lp) ? await readLandscape(() => fleet.loadLikeC4(lp)) : null;\n const proven = land !== null && land.errors.length === 0;\n\n const views = await serviceViews(docsDir, services, boundService, land);\n const maturity = maturityRollup(views);\n\n // Inbound op-linked edges per service, resolved exactly as the API axis\n // resolves them (service/api.ts): the container-aware resolver, so an edge\n // drawn into `paymentService.api` counts for the service that owns it.\n const known: ReadonlySet<string> = new Set(services.map((s) => s.id));\n const inbound = new Map<string, Set<string>>();\n if (proven) {\n const svcOf = serviceResolver(land.elements, known);\n for (const r of land.relationships) {\n if (r.op === undefined) continue;\n const owner: string = svcOf(r.target);\n const ops = inbound.get(owner) ?? new Set<string>();\n ops.add(r.op);\n inbound.set(owner, ops);\n }\n }\n\n // Every read in THIS fan-out is a memo hit — the service targets already\n // parsed both requirement documents and both contracts — so it spawns no new\n // filesystem work and needs no pool (unlike the verification fan-out below).\n const perService = await Promise.all(\n services.map((svc) => contractCounts(svc, fleet, inbound.get(svc.id) ?? new Set<string>())),\n );\n const sum = (pick: (c: ContractCounts) => number): number =>\n perService.reduce((n, c) => n + pick(c), 0);\n const operations = {\n defined: sum((c) => c.operations.defined),\n governed: sum((c) => c.operations.governed),\n deprecated: sum((c) => c.operations.deprecated),\n deprecatedStillConsumed: sum((c) => c.operations.deprecatedStillConsumed),\n };\n const messages = { defined: sum((c) => c.messages.defined), linked: sum((c) => c.messages.linked) };\n const adoption = adoptionOf(perService);\n\n // C4 coverage: the fleet map's own system census (`drawnSystems` — the set\n // the undocumented pass walks) against every living arch.spec.md's `Covers:`\n // entries. Element entries only — an edge entry names no element.\n const covers = perService.flatMap((c) => c.covers);\n const eligible = proven ? drawnSystems(land.elements, known) : [];\n const covered = eligible.filter((e) => covers.some((c) => coversElement(c, e))).length;\n\n // Verification: `list`'s own cells, so the two commands cannot disagree\n // about a record's verdict. Active features only — an archived record is\n // frozen history, not fleet debt. This fan-out is NOT memo hits: reading a\n // feature's record is a directory listing plus a small read per record file\n // (the one-file `verification.yaml` and one `verification/<svc>.yaml` per\n // attesting service), and a recorded cell additionally derives its checklist\n // (the same cost `list` pays for the same column; only its enumerations and\n // delta parse are memo hits through `fleet`) — real filesystem work, so it\n // runs through the bounded pool.\n const features = await fleet.listFeatures(docsDir);\n const cells = await inOrder(features, (f) => featureVerification(docsDir, f, fleet));\n const recorded = cells.filter((c): c is VerificationCell => c !== null);\n const verdicts = Object.fromEntries(VERIFICATION_VERDICTS.map((v) => [v, 0])) as Record<VerificationVerdict, number>;\n for (const cell of recorded) verdicts[cell.verdict] += 1;\n const confirmed = recorded.reduce((n, c) => n + c.confirmed, 0);\n const answered = Object.fromEntries(\n ANSWERED_BY.map((who) => [who, recorded.reduce((n, c) => n + c.answered[who], 0)]),\n ) as Record<AnsweredBy, number>;\n\n // The fleet form of `status`, on this run's own snapshot: presence and\n // dependency order, nothing graded — the stages an agent already branches on.\n const status = await fleetStatus(docsDir, { context: fleet });\n const stages = Object.fromEntries(ARTIFACT_STATUSES.map((s) => [s, 0])) as Record<ArtifactStatus, number>;\n for (const f of status.features) stages[f.stage] += 1;\n\n return {\n services: services.length,\n maturity,\n provenance: {\n vouched: maturity.vouched,\n // Every count joins through `subjectsWith` on the run's OWN findings, so\n // each is structurally the same number as the report beside it — the\n // unverifiable count is the footer's (unverifiableSubjects is the same\n // call on the same code; this package cannot import the parent back).\n // Every code is a LITERAL — the emitter's own spelling — because the\n // stable-code collector reads counting sites and refuses a slot it\n // cannot read; the collector is also what convicts a typo here.\n staleDigests: subjectsWith(targets, \"sources.stale\"),\n unverifiableFromHere: subjectsWith(targets, \"sources.unverifiable-from-here\"),\n // Doc-side, so unlike `staleDigests` these two are complete even from\n // the docs repo: neither `sources.sampled-vouch` nor `content.stale`\n // needs a service repo to fire. `content.stale` names no subject, and\n // `subjectsWith` falls back to the target id — the service, here.\n sampledVouched: subjectsWith(targets, \"sources.sampled-vouch\"),\n contentStale: subjectsWith(targets, \"content.stale\"),\n },\n verification: {\n recorded: recorded.length,\n verdicts,\n claims: {\n total: recorded.reduce((n, c) => n + c.claims, 0),\n confirmed,\n answered,\n },\n },\n operations,\n messages,\n c4: { elements: eligible.length, covered },\n adoption,\n features: { active: status.features.length, stages },\n };\n}\n"]}
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* this ship into a fleet that already has diagrams. An untagged `dynamic view`
|
|
7
7
|
* is somebody's hand-drawn sequence — drawn to explain something to a person,
|
|
8
8
|
* not to be joined to a model — and grading it would turn every existing docs
|
|
9
|
-
* repo red on upgrade over documents nobody promised anything about. A `#cap
|
|
10
|
-
* or `#
|
|
11
|
-
* model", and nothing else says it: a view with no tags and a view whose
|
|
12
|
-
* are all somebody's own vocabulary are the same thing here.
|
|
9
|
+
* repo red on upgrade over documents nobody promised anything about. A `#cap-`,
|
|
10
|
+
* `#req-` or `#suite-` tag is the author saying "this is a use case; hold it to
|
|
11
|
+
* the model", and nothing else says it: a view with no tags and a view whose
|
|
12
|
+
* tags are all somebody's own vocabulary are the same thing here. A
|
|
13
|
+
* `#suite-`-only view resolves nothing below — it carries no claim — and its
|
|
14
|
+
* hops are graded like any other's.
|
|
13
15
|
*
|
|
14
16
|
* THE TAG OPTS A VIEW IN WHETHER OR NOT IT RESOLVES. A `#cap-` slug that names
|
|
15
17
|
* no declared capability is a broken claim about the vocabulary, not a withdrawn
|