@ybotok/loam 0.2.0-alpha.7 → 0.2.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1315 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +83 -7
- package/SCHEMA.md +569 -98
- package/WORKFLOW.md +272 -62
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +33 -11
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/explore.js +4 -8
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +28 -33
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +21 -25
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +3 -5
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +5 -8
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +14 -41
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +17 -38
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +32 -5
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +23 -23
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +6 -10
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +1 -3
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +8 -28
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/report.js +5 -4
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +16 -104
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +1 -1
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +54 -51
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +56 -101
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +4 -3
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +32 -18
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +9 -3
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +20 -16
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +50 -18
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +6 -18
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +15 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/documents.js +31 -6
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +6 -17
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/findings.js +15 -20
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/coherence.js +30 -16
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +119 -8
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +3 -6
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +28 -16
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +5 -2
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +4 -2
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +5 -36
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +22 -8
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +56 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +2 -16
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/repo.js +24 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/walk.js +8 -30
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/staging/commit.js +10 -12
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +174 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +3 -0
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +12 -2
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +21 -6
- package/examples/docs/architecture/landscape.likec4 +8 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -11,37 +11,15 @@
|
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
12
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
|
-
import { repoPath } from "
|
|
15
|
-
import { resolvePortableFileInsideLexically, UnsafePathError } from "
|
|
16
|
-
// Every `isRecord` below guards
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
import { isDigest, sha256 } from "./writes.js";
|
|
24
|
-
import { message, quietRm } from "./commit.js";
|
|
25
|
-
/** Where archive parks the pre-images, inside the feature directory so they travel with it — the module doc above says why. */
|
|
26
|
-
export const SNAPSHOT_DIR = ".loam-before";
|
|
27
|
-
export const SNAPSHOT_MANIFEST = "manifest.json";
|
|
28
|
-
/**
|
|
29
|
-
* Bumped only when the layout changes in a way `unarchive` must refuse to guess
|
|
30
|
-
* at. 2 added `before`: a version-1 snapshot says nothing about the bytes it
|
|
31
|
-
* would RESTORE, so an edited pre-image read back as an intact one. 3 re-keys
|
|
32
|
-
* `services/` entries by `(service, artifact)`: a literal path pins the service
|
|
33
|
-
* directory where it stood on the day of the archive, and once the subsystem
|
|
34
|
-
* tree lets it move — re-cutting is expected to be routine — an undo addressed
|
|
35
|
-
* by path restores into a location that is gone. The service id never moves.
|
|
36
|
-
*/
|
|
37
|
-
export const SNAPSHOT_VERSION = 3;
|
|
38
|
-
/**
|
|
39
|
-
* Version 2 is still READ, forever: version-2 snapshots already sit inside
|
|
40
|
-
* archived features, which carry them across loam upgrades, and each must keep
|
|
41
|
-
* restoring — and keep being graded for staleness by a retried archive — under
|
|
42
|
-
* its own literal-path rules. Only version 1 is refused (no restore digests).
|
|
43
|
-
*/
|
|
44
|
-
export const SNAPSHOT_COMPAT_VERSION = 2;
|
|
14
|
+
import { repoPath } from "../../envelope/json.js";
|
|
15
|
+
import { resolvePortableFileInsideLexically, UnsafePathError } from "../../kernel/path-safety.js";
|
|
16
|
+
// Every `isRecord` below guards the manifest loam itself wrote, read back inside
|
|
17
|
+
// a REFUSAL path — `../lock.ts` records why that is where a thrown `TypeError`
|
|
18
|
+
// costs the most.
|
|
19
|
+
import { isRecord } from "../../kernel/records.js";
|
|
20
|
+
import { isDigest, sha256 } from "../writes.js";
|
|
21
|
+
import { message, quietRm } from "../commit.js";
|
|
22
|
+
import { SNAPSHOT_COMPAT_VERSION, SNAPSHOT_DIR, SNAPSHOT_MANIFEST, SNAPSHOT_VERSION, snapshotDir, } from "./manifest.js";
|
|
45
23
|
/** A snapshot directory that must not be replaced, because it is the only copy of the true pre-images. */
|
|
46
24
|
export class SnapshotClobberError extends Error {
|
|
47
25
|
dir;
|
|
@@ -54,9 +32,6 @@ export class SnapshotClobberError extends Error {
|
|
|
54
32
|
this.dir = dir;
|
|
55
33
|
}
|
|
56
34
|
}
|
|
57
|
-
export function snapshotDir(featureDir) {
|
|
58
|
-
return join(featureDir, SNAPSHOT_DIR);
|
|
59
|
-
}
|
|
60
35
|
export async function writeSnapshot(request) {
|
|
61
36
|
const { featureDir, docsDir, staged } = request;
|
|
62
37
|
const { featureId, dirName } = request.feature;
|
|
@@ -89,6 +64,7 @@ export async function writeSnapshot(request) {
|
|
|
89
64
|
feature: featureId,
|
|
90
65
|
dirName,
|
|
91
66
|
archivedAt: new Date().toISOString(),
|
|
67
|
+
...(request.checklist === undefined ? {} : { checklist: request.checklist }),
|
|
92
68
|
files,
|
|
93
69
|
};
|
|
94
70
|
// Manifest last: its presence is what says the pre-images beside it are complete.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../../src/core/staging/snapshot/snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The undo snapshot: the bytes `archive` is about to overwrite, parked inside\n * the feature directory so they travel with it into `features/archive/`.\n *\n * Separate from the commit because the merge is not invertible — a MODIFIED\n * requirement's previous text appears nowhere in the delta, and a landscape\n * rewritten by hand since cannot be un-rewritten by re-reading one. `unarchive`\n * puts bytes back; it does not recompute them. Nothing on the commit path reads\n * this module: only `archive` writes a snapshot and only `unarchive` honours one.\n */\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { resolvePortableFileInsideLexically, UnsafePathError } from \"../../kernel/path-safety.js\";\n// Every `isRecord` below guards the manifest loam itself wrote, read back inside\n// a REFUSAL path — `../lock.ts` records why that is where a thrown `TypeError`\n// costs the most.\nimport { isRecord } from \"../../kernel/records.js\";\nimport { isDigest, sha256, type StagedWrite } from \"../writes.js\";\nimport { message, quietRm } from \"../commit.js\";\nimport {\n SNAPSHOT_COMPAT_VERSION,\n SNAPSHOT_DIR,\n SNAPSHOT_MANIFEST,\n SNAPSHOT_VERSION,\n snapshotDir,\n type SnapshotEntry,\n type SnapshotManifest,\n} from \"./manifest.js\";\n\n/** A snapshot directory that must not be replaced, because it is the only copy of the true pre-images. */\nexport class SnapshotClobberError extends Error {\n override readonly name = \"SnapshotClobberError\";\n\n constructor(readonly dir: string, detail: string) {\n super(\n `${dir} already holds an undo snapshot that does NOT match the living docs (${detail}) — a previous archive of ` +\n `this feature wrote to the living docs and never took them back, and these are the only copies of what they ` +\n `said before. Overwriting them would make that merge permanent and unreportable. Run \\`loam archive\\` again ` +\n `after restoring the living docs from version control, or delete ${SNAPSHOT_DIR}/ if you know the merge never landed.`,\n );\n }\n}\n\n/** The (service, artifact) key a `services/` path decomposes into. */\nexport interface ServiceKey {\n service: string;\n artifact: string;\n}\n\n/**\n * Everything `writeSnapshot` needs, as one object. The resolvers are INJECTED\n * by the caller — `archive` builds them from its view of the tree — because\n * how a path decomposes into a service identity is repo-layout knowledge, and\n * this module's subject is parking and checking bytes.\n */\nexport interface SnapshotRequest {\n featureDir: string;\n docsDir: string;\n feature: { featureId: string; dirName: string };\n staged: StagedWrite[];\n /** Docs-relative path → the service it belongs to; null for landscape/features paths. */\n serviceKeyOf: (rel: string) => ServiceKey | null;\n /** Service id → its CURRENT docs-relative directory; null when not enumerated. */\n serviceDirOf: (service: string) => string | null;\n /**\n * Recorded verbatim as the manifest's `checklist` when given. Opaque here —\n * this module parks bytes and never learns what a checklist is; `archive`\n * derives it and `gate --since` reads it back (`./manifest.ts`).\n */\n checklist?: string;\n}\n\nexport async function writeSnapshot(request: SnapshotRequest): Promise<void> {\n const { featureDir, docsDir, staged } = request;\n const { featureId, dirName } = request.feature;\n const dir = snapshotDir(featureDir);\n await assertSnapshotReplaceable(dir, docsDir, request.serviceDirOf);\n // A leftover from a rolled-back run would describe a merge that never happened.\n await quietRm(dir);\n\n const files: SnapshotEntry[] = [];\n for (const s of staged) {\n const rel = repoPath(docsDir, s.write.path);\n // services/ entries are re-keyed by (service, artifact) so they survive the\n // service directory moving between archive and unarchive; the literal path\n // is still recorded — it keys the pre-image below, and it is the fallback.\n const key = request.serviceKeyOf(rel);\n files.push({\n path: rel,\n ...key,\n existed: s.before !== null,\n after: sha256(s.content ?? Buffer.alloc(0)),\n before: s.before === null ? null : sha256(s.before),\n });\n if (s.before !== null) {\n const dest = join(dir, \"files\", rel);\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, s.before);\n }\n }\n\n const manifest: SnapshotManifest = {\n version: SNAPSHOT_VERSION,\n feature: featureId,\n dirName,\n archivedAt: new Date().toISOString(),\n ...(request.checklist === undefined ? {} : { checklist: request.checklist }),\n files,\n };\n // Manifest last: its presence is what says the pre-images beside it are complete.\n await mkdir(dir, { recursive: true });\n await writeFile(join(dir, SNAPSHOT_MANIFEST), JSON.stringify(manifest, null, 2) + \"\\n\", \"utf8\");\n}\n\n/**\n * Refuse to replace a snapshot whose pre-images are still the only record of\n * what the living docs said.\n *\n * A snapshot in an ACTIVE feature directory means a previous archive of this\n * same feature wrote it — and since a clean rollback deletes it, finding one\n * means that run either died mid-commit or reported `rollback-incomplete`. If\n * its pre-images still match the living docs, nothing of that run landed and\n * the snapshot is stale bookkeeping. If they do not, the living docs carry\n * that run's writes, these files are the only way back, and the unconditional\n * clear turned a repairable half-merge into a permanent one — after which\n * `unarchive` restored the half-merged text and said the docs were back.\n *\n * Version 2 manifests are graded by the same staleness question over their\n * literal paths — refusing them as \"a different loam\" would turn every leftover\n * version-2 snapshot into a permanent archive refusal on upgrade.\n */\nasync function assertSnapshotReplaceable(\n dir: string,\n docsDir: string,\n serviceDirOf: (service: string) => string | null,\n): Promise<void> {\n const manifestPath = join(dir, SNAPSHOT_MANIFEST);\n // The manifest is written LAST, so a `files/` tree without one is a snapshot\n // whose run died before it could swap anything: it describes no merge.\n if (!existsSync(manifestPath)) return;\n let manifest: unknown;\n try {\n manifest = JSON.parse(await readFile(manifestPath, \"utf8\"));\n } catch {\n throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);\n }\n // The cast that used to stand here asserted the shape rather than checking\n // it: well-formed JSON that is not an object answered `undefined` to every\n // field below and slipped through as \"a different loam\", and a bare `null`\n // threw a TypeError out of the refusal itself. Both left this directory —\n // which may hold the only copies of what the living docs said — one step from\n // being replaced, without the staleness question ever having been asked.\n if (!isRecord(manifest)) {\n throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);\n }\n if (\n (manifest.version !== SNAPSHOT_VERSION && manifest.version !== SNAPSHOT_COMPAT_VERSION) ||\n !Array.isArray(manifest.files)\n ) {\n throw new SnapshotClobberError(dir, `it was written by a different loam and cannot be checked against the living docs`);\n }\n // `Array.isArray` narrows to `any[]`; naming the element type here is what\n // keeps that `any` from spreading past the one row being read.\n const rows: unknown[] = manifest.files;\n const landed: string[] = [];\n for (const row of rows) {\n const entry = readSnapshotClaim(row);\n if (entry === null) {\n throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} is malformed, so nothing can say whether it is stale`);\n }\n let target: string;\n try {\n // These paths are data, and they are the ones this function goes and\n // reads. They name living documents, so containment is lexical: a docs\n // repo may legitimately mount a service directory through a symlink, and\n // `unarchive` resolves the same field the same way before it writes back.\n target = resolvePortableFileInsideLexically(docsDir, entry.path, `snapshot path '${entry.path}'`);\n // A re-keyed entry's CURRENT location is wherever the enumeration says\n // its service lives today — grading staleness at the old address would\n // call a merely-moved file a landed merge. The literal path was still\n // resolved above on purpose: its containment is part of what \"loam wrote\n // this manifest\" means, and it stays the pre-image's key. An id the\n // enumeration no longer answers falls back to that literal path.\n if (entry.service !== undefined && entry.artifact !== undefined) {\n const current = serviceDirOf(entry.service);\n if (current !== null) {\n target = resolvePortableFileInsideLexically(\n docsDir,\n `${current}/${entry.artifact}`,\n `snapshot path '${entry.path}'`,\n );\n }\n }\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n throw new SnapshotClobberError(dir, `${message(err)}, so nothing can say whether it is stale`);\n }\n const now = existsSync(target) ? sha256(await readFile(target)) : null;\n // `existed: false` means archive created the file, so \"nothing landed\"\n // means it is still absent.\n const untouched = entry.existed ? now === entry.before : now === null;\n if (!untouched) landed.push(entry.path);\n }\n if (landed.length > 0) throw new SnapshotClobberError(dir, `${landed.length} file(s) already carry that merge: ${landed.join(\", \")}`);\n}\n\n/**\n * One manifest row read back as the record `writeSnapshot` wrote, or null when\n * the file does not actually say.\n *\n * Every field the staleness question consumes is checked, because an unchecked\n * field does not read as missing here — it reads as an ANSWER. An `existed`\n * that is not a boolean is falsy, and a `false` claim is graded by asking\n * whether the file is ABSENT: a manifest recording a pre-image for a deleted\n * file therefore graded as stale bookkeeping and was cleared, taking the only\n * copy of that pre-image with it. A `before` of the wrong type equals no digest\n * at all, so the opposite row accused an untouched file of carrying a merge.\n *\n * `unarchive` asks a stricter version of the same question of the same file\n * (`after`, `archivedAt`, the feature name, no duplicates) because it is about\n * to write THROUGH it; that check stays where it is, with its own refusals.\n *\n * `service`/`artifact` are read when present (a version-3 `services/` row) and\n * must arrive as a pair: half a key is a manifest that does not say where the\n * file lives, and grading through the literal path anyway would be a guess.\n */\ninterface SnapshotClaim {\n path: string;\n existed: boolean;\n before: string | null;\n service?: string;\n artifact?: string;\n}\n\nfunction readSnapshotClaim(value: unknown): SnapshotClaim | null {\n if (!isRecord(value)) return null;\n const { path, existed, before, service, artifact } = value;\n if (typeof path !== \"string\" || typeof existed !== \"boolean\") return null;\n let key: { service: string; artifact: string } | Record<string, never>;\n if (service === undefined && artifact === undefined) key = {};\n else if (typeof service === \"string\" && typeof artifact === \"string\") key = { service, artifact };\n else return null;\n if (existed) return isDigest(before) ? { path, existed, before, ...key } : null;\n return before === null ? { path, existed, before, ...key } : null;\n}\n\n"]}
|
|
@@ -34,6 +34,17 @@ function execution(step) {
|
|
|
34
34
|
needs: ["compare the living docs with version control"],
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
// A vouch is a person's act, never an agent's (`loam vouch` refuses without a
|
|
38
|
+
// terminal or `--yes`), so the step is NOT runnable: an agent prepares the
|
|
39
|
+
// reading list with `loam vouch --pack` and hands the stamp to a person.
|
|
40
|
+
if (step.code === "next.revouch") {
|
|
41
|
+
return {
|
|
42
|
+
kind: "human-review",
|
|
43
|
+
runnable: false,
|
|
44
|
+
cwd: "service-repo",
|
|
45
|
+
needs: ["a person who re-reads the moved document and vouches for it from the service's own repository"],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
37
48
|
const needs = placeholders(step.command);
|
|
38
49
|
const serviceRepo = SERVICE_REPO_STEPS.has(step.code);
|
|
39
50
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution.js","sourceRoot":"","sources":["../../../../src/core/status/actions/execution.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,oBAAoB;IACpB,oBAAoB;IACpB,kBAAkB;IAClB,qBAAqB;IACrB,uBAAuB;CACxB,CAAC,CAAC;AAEH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,mBAAmB;IACnB,kBAAkB;IAClB,qBAAqB;IACrB,yBAAyB;IACzB,qBAAqB;IACrB,sBAAsB;CACvB,CAAC,CAAC;AAEH,SAAS,YAAY,CAAC,OAAe;IACnC,OAAO,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,SAAS,CAAC,IAAc;IAC/B,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,QAAQ,EAAE,KAAK;YACf,GAAG,EAAE,WAAW;YAChB,KAAK,EAAE,IAAI,CAAC,OAAO;YACnB,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,iCAAiC,CAAC;SACxD,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,qBAAqB,IAAI,IAAI,CAAC,OAAO,KAAK,oBAAoB,EAAE,CAAC;QACjF,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,QAAQ,EAAE,KAAK;YACf,GAAG,EAAE,WAAW;YAChB,KAAK,EAAE,CAAC,8CAA8C,CAAC;SACxD,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO;QACL,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS;QAC/C,QAAQ,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC;QAC5B,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,iBAAiB;QACrD,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;KAChE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAc;IAC3C,OAAO,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;AACjD,CAAC","sourcesContent":["/** The executable machine contract layered over status's legacy command hint. */\nimport type {\n ExecutableNextStep,\n NextExecution,\n NextStep,\n} from \"../report.js\";\n\nconst EDIT_STEPS = new Set([\n \"next.author-intent\",\n \"next.touch-service\",\n \"next.author-spec\",\n \"next.author-openapi\",\n \"next.author-scenarios\",\n]);\n\nconst SERVICE_REPO_STEPS = new Set([\n \"next.bind-service\",\n \"next.adopt-first\",\n \"next.generate-tests\",\n \"next.verify-unconfirmed\",\n \"next.attest-service\",\n \"next.verify-attested\",\n]);\n\nfunction placeholders(command: string): string[] {\n return [...command.matchAll(/<([^>]+)>/g)].map((match) => match[1]!);\n}\n\nfunction execution(step: NextStep): NextExecution {\n if (EDIT_STEPS.has(step.code)) {\n return {\n kind: \"edit\",\n runnable: false,\n cwd: \"docs-repo\",\n after: step.command,\n needs: [step.path ?? \"the artifact named in statement\"],\n };\n }\n if (step.code === \"next.recover-commit\" && step.command === \"loam doctor --json\") {\n return {\n kind: \"human-review\",\n runnable: false,\n cwd: \"docs-repo\",\n needs: [\"compare the living docs with version control\"],\n };\n }\n const needs = placeholders(step.command);\n const serviceRepo = SERVICE_REPO_STEPS.has(step.code);\n return {\n kind: serviceRepo ? \"external-repo\" : \"command\",\n runnable: needs.length === 0,\n cwd: serviceRepo ? \"service-repo\" : \"configured-repo\",\n ...(needs.length === 0 ? { command: step.command } : { needs }),\n };\n}\n\nexport function executableNext(step: NextStep): ExecutableNextStep {\n return { ...step, execution: execution(step) };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"execution.js","sourceRoot":"","sources":["../../../../src/core/status/actions/execution.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,oBAAoB;IACpB,oBAAoB;IACpB,kBAAkB;IAClB,qBAAqB;IACrB,uBAAuB;CACxB,CAAC,CAAC;AAEH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,mBAAmB;IACnB,kBAAkB;IAClB,qBAAqB;IACrB,yBAAyB;IACzB,qBAAqB;IACrB,sBAAsB;CACvB,CAAC,CAAC;AAEH,SAAS,YAAY,CAAC,OAAe;IACnC,OAAO,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,SAAS,CAAC,IAAc;IAC/B,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,QAAQ,EAAE,KAAK;YACf,GAAG,EAAE,WAAW;YAChB,KAAK,EAAE,IAAI,CAAC,OAAO;YACnB,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,iCAAiC,CAAC;SACxD,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,qBAAqB,IAAI,IAAI,CAAC,OAAO,KAAK,oBAAoB,EAAE,CAAC;QACjF,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,QAAQ,EAAE,KAAK;YACf,GAAG,EAAE,WAAW;YAChB,KAAK,EAAE,CAAC,8CAA8C,CAAC;SACxD,CAAC;IACJ,CAAC;IACD,8EAA8E;IAC9E,2EAA2E;IAC3E,yEAAyE;IACzE,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QACjC,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,QAAQ,EAAE,KAAK;YACf,GAAG,EAAE,cAAc;YACnB,KAAK,EAAE,CAAC,+FAA+F,CAAC;SACzG,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO;QACL,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS;QAC/C,QAAQ,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC;QAC5B,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,iBAAiB;QACrD,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;KAChE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAc;IAC3C,OAAO,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;AACjD,CAAC","sourcesContent":["/** The executable machine contract layered over status's legacy command hint. */\nimport type {\n ExecutableNextStep,\n NextExecution,\n NextStep,\n} from \"../report.js\";\n\nconst EDIT_STEPS = new Set([\n \"next.author-intent\",\n \"next.touch-service\",\n \"next.author-spec\",\n \"next.author-openapi\",\n \"next.author-scenarios\",\n]);\n\nconst SERVICE_REPO_STEPS = new Set([\n \"next.bind-service\",\n \"next.adopt-first\",\n \"next.generate-tests\",\n \"next.verify-unconfirmed\",\n \"next.attest-service\",\n \"next.verify-attested\",\n]);\n\nfunction placeholders(command: string): string[] {\n return [...command.matchAll(/<([^>]+)>/g)].map((match) => match[1]!);\n}\n\nfunction execution(step: NextStep): NextExecution {\n if (EDIT_STEPS.has(step.code)) {\n return {\n kind: \"edit\",\n runnable: false,\n cwd: \"docs-repo\",\n after: step.command,\n needs: [step.path ?? \"the artifact named in statement\"],\n };\n }\n if (step.code === \"next.recover-commit\" && step.command === \"loam doctor --json\") {\n return {\n kind: \"human-review\",\n runnable: false,\n cwd: \"docs-repo\",\n needs: [\"compare the living docs with version control\"],\n };\n }\n // A vouch is a person's act, never an agent's (`loam vouch` refuses without a\n // terminal or `--yes`), so the step is NOT runnable: an agent prepares the\n // reading list with `loam vouch --pack` and hands the stamp to a person.\n if (step.code === \"next.revouch\") {\n return {\n kind: \"human-review\",\n runnable: false,\n cwd: \"service-repo\",\n needs: [\"a person who re-reads the moved document and vouches for it from the service's own repository\"],\n };\n }\n const needs = placeholders(step.command);\n const serviceRepo = SERVICE_REPO_STEPS.has(step.code);\n return {\n kind: serviceRepo ? \"external-repo\" : \"command\",\n runnable: needs.length === 0,\n cwd: serviceRepo ? \"service-repo\" : \"configured-repo\",\n ...(needs.length === 0 ? { command: step.command } : { needs }),\n };\n}\n\nexport function executableNext(step: NextStep): ExecutableNextStep {\n return { ...step, execution: execution(step) };\n}\n"]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { repoPath } from "../../envelope/json.js";
|
|
9
9
|
import { featurePaths, featureSpecPaths } from "../../repo/paths.js";
|
|
10
|
-
import {
|
|
10
|
+
import { recordLocation } from "../../verify/location.js";
|
|
11
11
|
import { owesContract } from "../contracts.js";
|
|
12
12
|
import { verificationStatus } from "../verification.js";
|
|
13
13
|
/**
|
|
@@ -174,7 +174,7 @@ export function featureArtifacts(docsDir, feature, services, grading) {
|
|
|
174
174
|
out.push({
|
|
175
175
|
id: "verification",
|
|
176
176
|
service: null,
|
|
177
|
-
path: rel(
|
|
177
|
+
path: rel(recordLocation(feature.dir)),
|
|
178
178
|
exists: verification.state !== "absent",
|
|
179
179
|
required: true,
|
|
180
180
|
status: feeders.length === 0 ? "blocked" : verificationStatus(verification),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../../../src/core/status/feature/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAIlD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxD;;;;;GAKG;AACH,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC;IACxC,qCAAqC;IACrC,uCAAuC;IACvC,mCAAmC;IACnC,sBAAsB;IACtB,wCAAwC;CACzC,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,SAAS,eAAe,CAAC,IAAY,EAAE,OAAgB;IACrD,IAAI,IAAI,KAAK,eAAe,IAAI,IAAI,KAAK,sBAAsB;QAAE,OAAO,OAAO,CAAC;IAChF,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACzG,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAC;IAClD,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,UAAU,CAAC;IACpD,8EAA8E;IAC9E,uDAAuD;IACvD,8EAA8E;IAC9E,wEAAwE;IACxE,0EAA0E;IAC1E,4EAA4E;IAC5E,qBAAqB;IACrB,IAAI,yBAAyB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,cAAc,CAAC;IAC/D,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,MAAM,CAAC;IAC/G,0EAA0E;IAC1E,2EAA2E;IAC3E,qEAAqE;IACrE,IAAI,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrD,IAAI,IAAI,KAAK,gCAAgC;QAAE,OAAO,MAAM,CAAC;IAC7D,4EAA4E;IAC5E,2EAA2E;IAC3E,4EAA4E;IAC5E,8EAA8E;IAC9E,2EAA2E;IAC3E,0DAA0D;IAC1D,4EAA4E;IAC5E,sDAAsD;IACtD,IAAI,IAAI,KAAK,cAAc;QAAE,OAAO,QAAQ,CAAC;IAC7C,IAAI,IAAI,KAAK,sBAAsB;QAAE,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IACrF,OAAO,IAAI,CAAC;AACd,CAAC;AA2BD,MAAM,UAAU,gBAAgB,CAC9B,OAAgB,EAChB,OAAqB,EACrB,QAAoC,EACpC,OAAgB;IAEhB,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACtF,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,CAAC,GAAW,EAAU,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC3G,MAAM,OAAO,GAAG,CAAC,EAAc,EAAE,OAAsB,EAAW,EAAE,CAClE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,EAAE,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC;IAEvF,MAAM,GAAG,GAAoB;QAC3B,SAAS,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAChI,SAAS,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;KAC9H,CAAC;IAEF,4EAA4E;IAC5E,0EAA0E;IAC1E,sEAAsE;IACtE,6EAA6E;IAC7E,8EAA8E;IAC9E,6EAA6E;IAC7E,qEAAqE;IACrE,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,GAAG,CAAC,IAAI,CAAC;YACP,EAAE,EAAE,cAAc;YAClB,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,GAAG,CAAC,EAAE;YAClB,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YACnB,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;YAC5B,QAAQ,EAAE,KAAK;YACf,MAAM,EAAE,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;YACrF,SAAS,EAAE,EAAE;SACd,CAAC,CAAC;IACL,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACvC,yEAAyE;QACzE,0EAA0E;QAC1E,yEAAyE;QACzE,mEAAmE;QACnE,wEAAwE;QACxE,2DAA2D;QAC3D,kEAAkE;QAClE,EAAE;QACF,wEAAwE;QACxE,0EAA0E;QAC1E,qEAAqE;QACrE,0EAA0E;QAC1E,mEAAmE;QACnE,2EAA2E;QAC3E,0EAA0E;QAC1E,wCAAwC;QACxC,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC1C,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;QACrH,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;QAChH,GAAG,CAAC,IAAI,CACN,SAAS,CACP,SAAS,EACT,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EACrE,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EACpE,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CACxB,CACF,CAAC;QACF,2EAA2E;QAC3E,qEAAqE;QACrE,iEAAiE;QACjE,GAAG,CAAC,IAAI,CACN,SAAS,CACP,UAAU,EACV,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,EACvE,KAAK,EACL,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CACzB,CACF,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,EAAE;IACF,2EAA2E;IAC3E,8EAA8E;IAC9E,6EAA6E;IAC7E,uEAAuE;IACvE,qEAAqE;IACrE,EAAE;IACF,2EAA2E;IAC3E,wEAAwE;IACxE,0EAA0E;IAC1E,yEAAyE;IACzE,8EAA8E;IAC9E,4EAA4E;IAC5E,2DAA2D;IAC3D,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,KAAK,cAAc,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;IAC5F,GAAG,CAAC,IAAI,CAAC;QACP,EAAE,EAAE,cAAc;QAClB,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,EAAE,YAAY,CAAC,KAAK,KAAK,QAAQ;QACvC,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,YAAY,CAAC;QAC3E,SAAS,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE;KAC7F,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AASD,SAAS,SAAS,CAAC,EAAc,EAAE,IAAe,EAAE,QAAiB,EAAE,OAAgB;IACrF,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACvC,MAAM,MAAM,GAAmB,CAAC,MAAM;QACpC,CAAC,CAAC,QAAQ;YACR,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,MAAM;QACV,CAAC,CAAC,OAAO;YACP,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,MAAM,CAAC;IACb,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;AACxE,CAAC","sourcesContent":["/**\n * The artifact table: which files a feature owes, whether each is on disk, and\n * which finding turns a row `draft`. Nothing is graded here — the shared\n * checks already ran, and this module only attributes what they reported to\n * the file a reader should edit.\n */\nimport { existsSync } from \"node:fs\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport type { CapabilityDoc } from \"../../capabilities/tree.js\";\nimport type { PathableService } from \"../../kernel/ids/service.js\";\nimport type { FeatureEntry } from \"../../repo/entries.js\";\nimport { featurePaths, featureSpecPaths } from \"../../repo/paths.js\";\nimport { verificationPath } from \"../../verify/record.js\";\nimport type { Finding } from \"../../vocabulary/report.js\";\nimport { owesContract } from \"../contracts.js\";\nimport type { ServiceEntry } from \"../../repo/entries.js\";\nimport type { ArtifactId, ArtifactState, ArtifactStatus, VerificationState } from \"../report.js\";\nimport { verificationStatus } from \"../verification.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\n\n/**\n * The `capability.*` codes filed against a capability DOCUMENT rather than\n * against a service requirement's join line. Spelled out rather than matched by\n * prefix because the family straddles both corpora, and the whole point of this\n * table is to name one file.\n */\nconst CAPABILITY_DOCUMENT_CODES = new Set([\n \"capability.requirement-unidentified\",\n \"capability.requirement-service-scoped\",\n \"capability.requirement-inert-join\",\n \"capability.uncovered\",\n \"capability.remove-requirement-realized\",\n]);\n\n/**\n * Which artifact an error names.\n *\n * Attribution is by code family, because that is the only machine-readable\n * pointer an `Issue` carries: `subject` narrows to a NAME, never to a file.\n * The families are unambiguous — `c4-api.*`/`c4-event.*`/`c4.*` and the two\n * `delta.*` codes about the LikeC4 document itself grade the architecture\n * axis, `openapi.*` grades the API contract, `asyncapi.*` the event contract,\n * and the remaining `delta.*` codes plus `spec-api.*`/`spec-event.*` are the\n * delta-shape checks, every one of which is about a requirement in a spec\n * delta.\n *\n * An error that maps to nothing (or whose subject is unknown) still lands in\n * `checks.issues` and still drives `next.fix-coherence` — it just does not turn\n * any single artifact `draft`, because guessing which file to blame is how a\n * reader gets sent to edit the wrong one.\n *\n * THE BUSINESS CORPUS IS THE ONE PLACE THE CODE FAMILY IS NOT ENOUGH. A\n * `delta.*` issue's `subject` is a service id OR a capability id\n * (`core/delta/scope.ts`), and so is a `scaffold.placeholder`'s\n * (`core/coherence/authoring/scaffold.ts` files one per unedited capability\n * delta), and nothing in either code says which — so this\n * function answers `spec` for both and `featureArtifacts` below resolves it by\n * MEMBERSHIP, against the two lists it already holds. Where a name is only a\n * capability the capability row is turned `draft` and no service row is; where\n * a fleet holds a service AND a capability of the same name, BOTH are, which is\n * the pessimistic answer this module is required to give and strictly better\n * than the old one (it named the service's spec delta and was silently wrong).\n * The capability-document codes below need no such resolution: they are only\n * ever filed against a capability.\n */\nfunction faultedArtifact(code: string, subject?: string): ArtifactId | null {\n if (code === \"delta.invalid\" || code === \"delta.nothing-tagged\") return \"delta\";\n if (code.startsWith(\"c4-api.\") || code.startsWith(\"c4-event.\") || code.startsWith(\"c4.\")) return \"delta\";\n if (code.startsWith(\"openapi.\")) return \"openapi\";\n if (code.startsWith(\"asyncapi.\")) return \"asyncapi\";\n // The capability document's own grades, and the two halves of the `Realizes:`\n // join a feature can break — every one of them names a\n // `features/<FEAT>/capabilities/<id>/spec.md`, never a service delta. The two\n // `capability.*` codes deliberately absent are `capability.unknown` and\n // `capability.realizes-unknown`: those grade a SERVICE requirement's join\n // lines and carry a service id, so the prefix alone would send their reader\n // to the wrong file.\n if (CAPABILITY_DOCUMENT_CODES.has(code)) return \"capabilities\";\n if (code.startsWith(\"delta.\") || code.startsWith(\"spec-api.\") || code.startsWith(\"spec-event.\")) return \"spec\";\n // The two families validate contributes: the frontmatter checks read only\n // intent.md, and a requirement with no scenario is a requirement in a spec\n // delta. Both name exactly one file, so both may turn a row `draft`.\n if (code.startsWith(\"frontmatter.\")) return \"intent\";\n if (code === \"requirements.missing-scenarios\") return \"spec\";\n // The authoring gate names exactly one file per finding too — leaving these\n // unmapped had the table calling intent.md `done` (\"nothing is owed here\")\n // while the archive exited 1 because it says nothing. A placeholder finding\n // carries no subject when it is the C4 delta's scaffolded description; with a\n // subject it is a SPEC DELTA IN EITHER CORPUS — a service id when the text\n // sits in `specs/<svc>/`, a capability id when it sits in\n // `capabilities/<id>/` — which is why `spec` here is resolved by membership\n // below and must not be narrowed to the service list.\n if (code === \"intent.empty\") return \"intent\";\n if (code === \"scaffold.placeholder\") return subject === undefined ? \"delta\" : \"spec\";\n return null;\n}\n\n/**\n * The artifact table. Every entry is graded by two questions in order — is it\n * there, and does anything the shared checks reported name it — so a file that\n * exists and is wrong can never be reported the way one that was never written\n * is.\n */\n/** What the grading of one feature's files needs beyond the files themselves. */\nexport interface Grading {\n /** The findings that make an artifact `draft` rather than `done`. */\n blocking: Finding[];\n verification: VerificationState;\n /** Operations some OTHER feature's contract already holds. */\n contracted: ReadonlySet<string>;\n /** Operations this feature's own requirements govern. */\n governs: ReadonlySet<string>;\n /** The enumeration's entries by id — where each living service is and what it has (owesContract reads both). */\n living: ReadonlyMap<string, ServiceEntry>;\n /**\n * The capability documents this feature's delta carries. Empty for every\n * feature in a fleet that has not adopted the business axis, which is what\n * keeps the rows out of those payloads entirely.\n */\n capabilities: readonly CapabilityDoc[];\n}\n\nexport function featureArtifacts(\n docsDir: DocsDir,\n feature: FeatureEntry,\n services: readonly PathableService[],\n grading: Grading,\n): ArtifactState[] {\n const { blocking, verification, contracted, governs, living, capabilities } = grading;\n const paths = featurePaths(feature.dir);\n const rel = (abs: string): string => repoPath(docsDir, abs);\n const faults = blocking.map((f) => ({ artifact: faultedArtifact(f.code, f.subject), subject: f.subject }));\n const faulted = (id: ArtifactId, service: string | null): boolean =>\n faults.some((f) => f.artifact === id && (service === null || f.subject === service));\n\n const out: ArtifactState[] = [\n fileState(\"intent\", { service: null, path: rel(paths.intent), exists: existsSync(paths.intent) }, true, faulted(\"intent\", null)),\n fileState(\"delta\", { service: null, path: rel(paths.delta), exists: existsSync(paths.delta) }, false, faulted(\"delta\", null)),\n ];\n\n // One row per capability document the feature carries, and none at all when\n // it carries none: the row is the answer to \"which file do I edit\", and a\n // fleet that has not adopted the axis has no such file to name. Never\n // `required` — no feature owes a business change — so absence never shows up\n // as work owed, and only a fault turns one `draft`. A `delta.*` fault carries\n // the capability id in `subject` with nothing saying it is not a service, so\n // the membership test is the resolution the code family cannot give.\n for (const doc of capabilities) {\n out.push({\n id: \"capabilities\",\n service: null,\n capability: doc.id,\n path: rel(doc.spec),\n exists: existsSync(doc.spec),\n required: false,\n status: faulted(\"capabilities\", doc.id) || faulted(\"spec\", doc.id) ? \"draft\" : \"done\",\n blockedBy: [],\n });\n }\n\n for (const svc of services) {\n const p = featureSpecPaths(feature.dir, svc);\n const specFault = faulted(\"spec\", svc);\n // What a service delta owes is a REQUIREMENT DELTA, not specifically the\n // business one. `arch.spec.md` is the other corpus, with the same grammar\n // and the same delta algebra, and `featureChecklist` derives claims from\n // it — so a directory holding only the architectural document is a\n // complete delta, and every gate already reads it as one: `coherence`'s\n // `service.no-requirement-delta` counts the DIRECTORY, and\n // `validate --feature` grades an arch-only delta clean at exit 0.\n //\n // Requiring `spec.md` unconditionally made this module the one document\n // that disagreed. An architecture-only feature — an outbox added, a retry\n // policy, an alert; the changes `arch.spec.md` exists for because no\n // business scenario was ever going to carry them — reported `missing` and\n // emitted `next.author-spec`, sending the author to write business\n // requirements the change does not have, about a feature the gate accepts.\n // status may be redder than the gates on a question they also ask; it may\n // not invent an obligation they do not.\n const archExists = existsSync(p.archSpec);\n out.push(fileState(\"spec\", { service: svc, path: rel(p.spec), exists: existsSync(p.spec) }, !archExists, specFault));\n out.push(fileState(\"arch-spec\", { service: svc, path: rel(p.archSpec), exists: archExists }, false, specFault));\n out.push(\n fileState(\n \"openapi\",\n { service: svc, path: rel(p.openapi), exists: existsSync(p.openapi) },\n owesContract(living.get(svc), contracted.has(svc), governs.has(svc)),\n faulted(\"openapi\", svc),\n ),\n );\n // Never required: an event contract is genuinely optional — the axis's own\n // absence-grading rests on that — so absence is `done`, exactly like\n // arch.spec.md. Present and faulted still turns the row `draft`.\n out.push(\n fileState(\n \"asyncapi\",\n { service: svc, path: rel(p.asyncapi), exists: existsSync(p.asyncapi) },\n false,\n faulted(\"asyncapi\", svc),\n ),\n );\n }\n\n // The record is the one artifact with a real prerequisite: its checklist is\n // derived from the delta and the per-service deltas, so with none of them\n // written there is nothing to answer — authoring one is impossible rather\n // than merely premature, which is the whole distinction `blocked` carries.\n //\n // `capabilities` is excluded for exactly that reason and not by oversight:\n // `featureChecklist` derives no claim from a capability document — a business\n // promise is realized by service requirements, and those are what the record\n // answers — so counting one as a feeder would report a capability-only\n // feature as ready to verify against a checklist with nothing in it.\n //\n // `arch-spec` used to be excluded beside it, and that failed the same test\n // the comment above states: `featureChecklist` DOES read `arch.spec.md`\n // (`core/verify/checklist.ts`), and `loam verify` on an architecture-only\n // feature returns a real checklist. The row said `blocked … needs delta,\n // spec, openapi, asyncapi` and `next.verify` said there was nothing to derive\n // a checklist from, about a command that derives one — which is the failure\n // `blocked` is supposed to prevent, pointed the wrong way.\n const feeders = out.filter((a) => a.id !== \"intent\" && a.id !== \"capabilities\" && a.exists);\n out.push({\n id: \"verification\",\n service: null,\n path: rel(verificationPath(feature.dir)),\n exists: verification.state !== \"absent\",\n required: true,\n status: feeders.length === 0 ? \"blocked\" : verificationStatus(verification),\n blockedBy: feeders.length === 0 ? [\"delta\", \"spec\", \"arch-spec\", \"openapi\", \"asyncapi\"] : [],\n });\n return out;\n}\n\n/** Where an artifact is and whether it is there — the three facts a grade reads. */\ninterface FileFacts {\n service: string | null;\n path: string;\n exists: boolean;\n}\n\nfunction fileState(id: ArtifactId, file: FileFacts, required: boolean, faulted: boolean): ArtifactState {\n const { service, path, exists } = file;\n const status: ArtifactStatus = !exists\n ? required\n ? \"missing\"\n : \"done\"\n : faulted\n ? \"draft\"\n : \"done\";\n return { id, service, path, exists, required, status, blockedBy: [] };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../../../src/core/status/feature/artifacts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The artifact table: which files a feature owes, whether each is on disk, and\n * which finding turns a row `draft`. Nothing is graded here — the shared\n * checks already ran, and this module only attributes what they reported to\n * the file a reader should edit.\n */\nimport { existsSync } from \"node:fs\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport type { CapabilityDoc } from \"../../capabilities/tree.js\";\nimport type { PathableService } from \"../../kernel/ids/service.js\";\nimport type { FeatureEntry } from \"../../repo/entries.js\";\nimport { featurePaths, featureSpecPaths } from \"../../repo/paths.js\";\nimport { recordLocation } from \"../../verify/location.js\";\nimport type { Finding } from \"../../vocabulary/report.js\";\nimport { owesContract } from \"../contracts.js\";\nimport type { ServiceEntry } from \"../../repo/entries.js\";\nimport type { ArtifactId, ArtifactState, ArtifactStatus, VerificationState } from \"../report.js\";\nimport { verificationStatus } from \"../verification.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\n\n/**\n * The `capability.*` codes filed against a capability DOCUMENT rather than\n * against a service requirement's join line. Spelled out rather than matched by\n * prefix because the family straddles both corpora, and the whole point of this\n * table is to name one file.\n */\nconst CAPABILITY_DOCUMENT_CODES = new Set([\n \"capability.requirement-unidentified\",\n \"capability.requirement-service-scoped\",\n \"capability.requirement-inert-join\",\n \"capability.uncovered\",\n \"capability.remove-requirement-realized\",\n]);\n\n/**\n * Which artifact an error names.\n *\n * Attribution is by code family, because that is the only machine-readable\n * pointer an `Issue` carries: `subject` narrows to a NAME, never to a file.\n * The families are unambiguous — `c4-api.*`/`c4-event.*`/`c4.*` and the two\n * `delta.*` codes about the LikeC4 document itself grade the architecture\n * axis, `openapi.*` grades the API contract, `asyncapi.*` the event contract,\n * and the remaining `delta.*` codes plus `spec-api.*`/`spec-event.*` are the\n * delta-shape checks, every one of which is about a requirement in a spec\n * delta.\n *\n * An error that maps to nothing (or whose subject is unknown) still lands in\n * `checks.issues` and still drives `next.fix-coherence` — it just does not turn\n * any single artifact `draft`, because guessing which file to blame is how a\n * reader gets sent to edit the wrong one.\n *\n * THE BUSINESS CORPUS IS THE ONE PLACE THE CODE FAMILY IS NOT ENOUGH. A\n * `delta.*` issue's `subject` is a service id OR a capability id\n * (`core/delta/scope.ts`), and so is a `scaffold.placeholder`'s\n * (`core/coherence/authoring/scaffold.ts` files one per unedited capability\n * delta), and nothing in either code says which — so this\n * function answers `spec` for both and `featureArtifacts` below resolves it by\n * MEMBERSHIP, against the two lists it already holds. Where a name is only a\n * capability the capability row is turned `draft` and no service row is; where\n * a fleet holds a service AND a capability of the same name, BOTH are, which is\n * the pessimistic answer this module is required to give and strictly better\n * than the old one (it named the service's spec delta and was silently wrong).\n * The capability-document codes below need no such resolution: they are only\n * ever filed against a capability.\n */\nfunction faultedArtifact(code: string, subject?: string): ArtifactId | null {\n if (code === \"delta.invalid\" || code === \"delta.nothing-tagged\") return \"delta\";\n if (code.startsWith(\"c4-api.\") || code.startsWith(\"c4-event.\") || code.startsWith(\"c4.\")) return \"delta\";\n if (code.startsWith(\"openapi.\")) return \"openapi\";\n if (code.startsWith(\"asyncapi.\")) return \"asyncapi\";\n // The capability document's own grades, and the two halves of the `Realizes:`\n // join a feature can break — every one of them names a\n // `features/<FEAT>/capabilities/<id>/spec.md`, never a service delta. The two\n // `capability.*` codes deliberately absent are `capability.unknown` and\n // `capability.realizes-unknown`: those grade a SERVICE requirement's join\n // lines and carry a service id, so the prefix alone would send their reader\n // to the wrong file.\n if (CAPABILITY_DOCUMENT_CODES.has(code)) return \"capabilities\";\n if (code.startsWith(\"delta.\") || code.startsWith(\"spec-api.\") || code.startsWith(\"spec-event.\")) return \"spec\";\n // The two families validate contributes: the frontmatter checks read only\n // intent.md, and a requirement with no scenario is a requirement in a spec\n // delta. Both name exactly one file, so both may turn a row `draft`.\n if (code.startsWith(\"frontmatter.\")) return \"intent\";\n if (code === \"requirements.missing-scenarios\") return \"spec\";\n // The authoring gate names exactly one file per finding too — leaving these\n // unmapped had the table calling intent.md `done` (\"nothing is owed here\")\n // while the archive exited 1 because it says nothing. A placeholder finding\n // carries no subject when it is the C4 delta's scaffolded description; with a\n // subject it is a SPEC DELTA IN EITHER CORPUS — a service id when the text\n // sits in `specs/<svc>/`, a capability id when it sits in\n // `capabilities/<id>/` — which is why `spec` here is resolved by membership\n // below and must not be narrowed to the service list.\n if (code === \"intent.empty\") return \"intent\";\n if (code === \"scaffold.placeholder\") return subject === undefined ? \"delta\" : \"spec\";\n return null;\n}\n\n/**\n * The artifact table. Every entry is graded by two questions in order — is it\n * there, and does anything the shared checks reported name it — so a file that\n * exists and is wrong can never be reported the way one that was never written\n * is.\n */\n/** What the grading of one feature's files needs beyond the files themselves. */\nexport interface Grading {\n /** The findings that make an artifact `draft` rather than `done`. */\n blocking: Finding[];\n verification: VerificationState;\n /** Operations some OTHER feature's contract already holds. */\n contracted: ReadonlySet<string>;\n /** Operations this feature's own requirements govern. */\n governs: ReadonlySet<string>;\n /** The enumeration's entries by id — where each living service is and what it has (owesContract reads both). */\n living: ReadonlyMap<string, ServiceEntry>;\n /**\n * The capability documents this feature's delta carries. Empty for every\n * feature in a fleet that has not adopted the business axis, which is what\n * keeps the rows out of those payloads entirely.\n */\n capabilities: readonly CapabilityDoc[];\n}\n\nexport function featureArtifacts(\n docsDir: DocsDir,\n feature: FeatureEntry,\n services: readonly PathableService[],\n grading: Grading,\n): ArtifactState[] {\n const { blocking, verification, contracted, governs, living, capabilities } = grading;\n const paths = featurePaths(feature.dir);\n const rel = (abs: string): string => repoPath(docsDir, abs);\n const faults = blocking.map((f) => ({ artifact: faultedArtifact(f.code, f.subject), subject: f.subject }));\n const faulted = (id: ArtifactId, service: string | null): boolean =>\n faults.some((f) => f.artifact === id && (service === null || f.subject === service));\n\n const out: ArtifactState[] = [\n fileState(\"intent\", { service: null, path: rel(paths.intent), exists: existsSync(paths.intent) }, true, faulted(\"intent\", null)),\n fileState(\"delta\", { service: null, path: rel(paths.delta), exists: existsSync(paths.delta) }, false, faulted(\"delta\", null)),\n ];\n\n // One row per capability document the feature carries, and none at all when\n // it carries none: the row is the answer to \"which file do I edit\", and a\n // fleet that has not adopted the axis has no such file to name. Never\n // `required` — no feature owes a business change — so absence never shows up\n // as work owed, and only a fault turns one `draft`. A `delta.*` fault carries\n // the capability id in `subject` with nothing saying it is not a service, so\n // the membership test is the resolution the code family cannot give.\n for (const doc of capabilities) {\n out.push({\n id: \"capabilities\",\n service: null,\n capability: doc.id,\n path: rel(doc.spec),\n exists: existsSync(doc.spec),\n required: false,\n status: faulted(\"capabilities\", doc.id) || faulted(\"spec\", doc.id) ? \"draft\" : \"done\",\n blockedBy: [],\n });\n }\n\n for (const svc of services) {\n const p = featureSpecPaths(feature.dir, svc);\n const specFault = faulted(\"spec\", svc);\n // What a service delta owes is a REQUIREMENT DELTA, not specifically the\n // business one. `arch.spec.md` is the other corpus, with the same grammar\n // and the same delta algebra, and `featureChecklist` derives claims from\n // it — so a directory holding only the architectural document is a\n // complete delta, and every gate already reads it as one: `coherence`'s\n // `service.no-requirement-delta` counts the DIRECTORY, and\n // `validate --feature` grades an arch-only delta clean at exit 0.\n //\n // Requiring `spec.md` unconditionally made this module the one document\n // that disagreed. An architecture-only feature — an outbox added, a retry\n // policy, an alert; the changes `arch.spec.md` exists for because no\n // business scenario was ever going to carry them — reported `missing` and\n // emitted `next.author-spec`, sending the author to write business\n // requirements the change does not have, about a feature the gate accepts.\n // status may be redder than the gates on a question they also ask; it may\n // not invent an obligation they do not.\n const archExists = existsSync(p.archSpec);\n out.push(fileState(\"spec\", { service: svc, path: rel(p.spec), exists: existsSync(p.spec) }, !archExists, specFault));\n out.push(fileState(\"arch-spec\", { service: svc, path: rel(p.archSpec), exists: archExists }, false, specFault));\n out.push(\n fileState(\n \"openapi\",\n { service: svc, path: rel(p.openapi), exists: existsSync(p.openapi) },\n owesContract(living.get(svc), contracted.has(svc), governs.has(svc)),\n faulted(\"openapi\", svc),\n ),\n );\n // Never required: an event contract is genuinely optional — the axis's own\n // absence-grading rests on that — so absence is `done`, exactly like\n // arch.spec.md. Present and faulted still turns the row `draft`.\n out.push(\n fileState(\n \"asyncapi\",\n { service: svc, path: rel(p.asyncapi), exists: existsSync(p.asyncapi) },\n false,\n faulted(\"asyncapi\", svc),\n ),\n );\n }\n\n // The record is the one artifact with a real prerequisite: its checklist is\n // derived from the delta and the per-service deltas, so with none of them\n // written there is nothing to answer — authoring one is impossible rather\n // than merely premature, which is the whole distinction `blocked` carries.\n //\n // `capabilities` is excluded for exactly that reason and not by oversight:\n // `featureChecklist` derives no claim from a capability document — a business\n // promise is realized by service requirements, and those are what the record\n // answers — so counting one as a feeder would report a capability-only\n // feature as ready to verify against a checklist with nothing in it.\n //\n // `arch-spec` used to be excluded beside it, and that failed the same test\n // the comment above states: `featureChecklist` DOES read `arch.spec.md`\n // (`core/verify/checklist.ts`), and `loam verify` on an architecture-only\n // feature returns a real checklist. The row said `blocked … needs delta,\n // spec, openapi, asyncapi` and `next.verify` said there was nothing to derive\n // a checklist from, about a command that derives one — which is the failure\n // `blocked` is supposed to prevent, pointed the wrong way.\n const feeders = out.filter((a) => a.id !== \"intent\" && a.id !== \"capabilities\" && a.exists);\n out.push({\n id: \"verification\",\n service: null,\n path: rel(recordLocation(feature.dir)),\n exists: verification.state !== \"absent\",\n required: true,\n status: feeders.length === 0 ? \"blocked\" : verificationStatus(verification),\n blockedBy: feeders.length === 0 ? [\"delta\", \"spec\", \"arch-spec\", \"openapi\", \"asyncapi\"] : [],\n });\n return out;\n}\n\n/** Where an artifact is and whether it is there — the three facts a grade reads. */\ninterface FileFacts {\n service: string | null;\n path: string;\n exists: boolean;\n}\n\nfunction fileState(id: ArtifactId, file: FileFacts, required: boolean, faulted: boolean): ArtifactState {\n const { service, path, exists } = file;\n const status: ArtifactStatus = !exists\n ? required\n ? \"missing\"\n : \"done\"\n : faulted\n ? \"draft\"\n : \"done\";\n return { id, service, path, exists, required, status, blockedBy: [] };\n}\n"]}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { featureCoherence } from "../../coherence/coherence.js";
|
|
9
9
|
import { invalidSpecServiceFindings, livingMergeConflicts, unknownDeltaServices, } from "../../coherence/living.js";
|
|
10
10
|
import { analyzeDependencies } from "../../dependencies/dependencies.js";
|
|
11
|
+
import { missingScenariosFinding } from "../../document/scenarios.js";
|
|
11
12
|
import { repoPath } from "../../envelope/json.js";
|
|
12
13
|
import { FleetContext } from "../../fleet-context.js";
|
|
13
14
|
import { featureProvenance } from "../../provenance/findings.js";
|
|
@@ -28,8 +29,8 @@ import {} from "./state.js";
|
|
|
28
29
|
* complete either way, because a narrowed view that also hid which other
|
|
29
30
|
* services exist would read as a feature that touches exactly one.
|
|
30
31
|
*/
|
|
31
|
-
export async function featureStatus(docsDir, feature, opts
|
|
32
|
-
const context = opts
|
|
32
|
+
export async function featureStatus(docsDir, feature, opts) {
|
|
33
|
+
const { context } = opts;
|
|
33
34
|
const interrupted = await readInterruptedCommit(docsDir);
|
|
34
35
|
const services = await featureSpecServices(feature.dir, context);
|
|
35
36
|
const narrowed = opts.service;
|
|
@@ -153,15 +154,8 @@ async function featureFindings(docsDir, feature, scans, context) {
|
|
|
153
154
|
const capabilityDeltas = await context.featureCapabilityDeltas(feature.dir);
|
|
154
155
|
out.push(...(await livingMergeConflicts(docsDir, await featureSpecServices(feature.dir, context), capabilityDeltas.docs.map((d) => d.id), context)));
|
|
155
156
|
for (const scan of scans) {
|
|
156
|
-
if (scan.bare.length
|
|
157
|
-
|
|
158
|
-
out.push({
|
|
159
|
-
severity: "error",
|
|
160
|
-
code: "requirements.missing-scenarios",
|
|
161
|
-
subject: scan.service,
|
|
162
|
-
message: `${scan.label}: ${scan.bare.length} requirement(s) without a scenario`,
|
|
163
|
-
details: scan.bare.map((r) => r.name),
|
|
164
|
-
});
|
|
157
|
+
if (scan.bare.length > 0)
|
|
158
|
+
out.push(missingScenariosFinding(scan.label, scan.bare, scan.service));
|
|
165
159
|
}
|
|
166
160
|
return out;
|
|
167
161
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../../src/core/status/feature/feature.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One feature: the full report. `featureStatus` gathers what the shared gates\n * say about a feature in flight — findings, the artifact table, the record,\n * the dependency order — and rolls them up into the stage and the steps. The\n * vocabulary it reports in lives in ../report.ts, whose header holds the rules\n * this projection is held to.\n */\nimport { featureCoherence } from \"../../coherence/coherence.js\";\nimport {\n invalidSpecServiceFindings,\n livingMergeConflicts,\n unknownDeltaServices,\n} from \"../../coherence/living.js\";\nimport { analyzeDependencies } from \"../../dependencies/dependencies.js\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { FleetContext } from \"../../fleet-context.js\";\nimport { featureProvenance } from \"../../provenance/findings.js\";\nimport type { FeatureEntry } from \"../../repo/entries.js\";\nimport { featureSpecServices } from \"../../repo/repo.js\";\nimport { enumeratedServices } from \"../../repo/service-target.js\";\nimport { useCaseBlastRadius } from \"../../usecases/touch.js\";\nimport { issueFinding, type Issue } from \"../../vocabulary/issue.js\";\nimport type { Finding } from \"../../vocabulary/report.js\";\nimport { contractOwners, contractsHeldElsewhere } from \"../contracts.js\";\nimport { readInterruptedCommit } from \"../interrupted.js\";\nimport type { ArtifactStatus, FeatureStatusReport } from \"../report.js\";\nimport { governedServices, scanDeltas, type DeltaScan } from \"../scan.js\";\nimport { fullyVerified, verificationState } from \"../verification.js\";\nimport { featureArtifacts } from \"./artifacts.js\";\nimport { featureNext, unshippable } from \"./next.js\";\nimport { type FeatureState } from \"./state.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\n\n/**\n * Everything about one feature in flight. `service` narrows the per-service\n * artifacts and the per-service steps; the feature's own `services` list stays\n * complete either way, because a narrowed view that also hid which other\n * services exist would read as a feature that touches exactly one.\n */\nexport async function featureStatus(\n docsDir: DocsDir,\n feature: FeatureEntry,\n opts: { service?: string; boundService?: string; context?: FleetContext } = {},\n): Promise<FeatureStatusReport> {\n const context = opts.context ?? new FleetContext();\n const interrupted = await readInterruptedCommit(docsDir);\n const services = await featureSpecServices(feature.dir, context);\n const narrowed = opts.service;\n const inView = narrowed === undefined ? services : services.filter((s) => s === narrowed);\n\n const scans = feature.archived ? [] : await scanDeltas(docsDir, feature, services, context);\n const read = await verificationState(docsDir, feature);\n const verification = read.state;\n // An archived feature is history: its delta is already folded into the living\n // docs, so coherence would grade the change against a world that has absorbed\n // it and report contradictions that are only the merge having happened. The\n // same reason `list` never calls an archived record stale.\n const findings = feature.archived ? [] : await featureFindings(docsDir, feature, scans, context);\n // `verify.scenario-attested` rides in the same array an agent already parses,\n // under verify's own code — a feature confirmed the short way has to be\n // MARKED here, not merely absent from `stage: done`. It is a warning and it\n // gates nothing, so `coherent` is untouched.\n if (!feature.archived && read.notice !== null) {\n findings.push({ severity: read.notice.severity, code: read.notice.code, message: read.notice.message });\n }\n const errors = findings.filter((f) => f.severity === \"error\");\n const warnings = findings.filter((f) => f.severity === \"warn\").length;\n // The union of the two gates, and the set every verdict below is drawn from.\n const blocking = findings.filter(unshippable);\n\n const governs = governedServices(scans);\n // The whole feature is graded, then the view is narrowed — never the other\n // way round. `--service` is a lens on the table and the steps; a rollup that\n // moved with it would report a feature `ready` because the one service you\n // asked about happens to be written, while three others have nothing.\n const living = new Map((await enumeratedServices(docsDir, context)).map((s) => [s.id, s]));\n const graded = featureArtifacts(docsDir, feature, services, {\n blocking,\n verification,\n contracted: contractsHeldElsewhere(await contractOwners(docsDir, context), feature.id),\n governs,\n // Tolerant on purpose: status must keep answering in a docs repo whose\n // services/ is broken or missing — an unenumerable fleet is doctor's\n // diagnosis, and here it simply means every service reads as never-adopted.\n living,\n // Through the context's memo — the same walk `featureFindings` above and\n // the coherence pass inside it already ran. An archived feature carries no\n // delta to name, and the walk is one `existsSync` besides.\n capabilities: (await context.featureCapabilityDeltas(feature.dir)).docs,\n });\n const artifacts =\n narrowed === undefined ? graded : graded.filter((a) => a.service === null || a.service === narrowed);\n\n // The dependency index rather than the `*-pending` issue codes: those name the\n // other feature in prose, and prose is not an API — dependencies.ts opens with\n // exactly that argument. An archived feature waits on nothing; it landed.\n const graph = feature.archived\n ? null\n : await analyzeDependencies(docsDir, feature.id, context);\n const blockedBy = graph?.nodes.find((n) => n.id === feature.id)?.dependsOn ?? [];\n\n // The flows the services in view are already hops of. The fleet set is the\n // living services UNION the feature's own, so a service this feature\n // introduces resolves to itself rather than to a container's title — and the\n // load underneath gates itself, so a docs repo that declares no use case pays\n // a readdir here rather than a LikeC4 workspace.\n const useCases = await useCaseBlastRadius({\n docsDir,\n known: new Set([...living.keys(), ...services]),\n services: new Set(inView),\n });\n\n return {\n interrupted,\n feature: {\n id: feature.id,\n dirName: feature.dirName,\n path: repoPath(docsDir, feature.dir),\n archived: feature.archived,\n stage: featureStage(\n { feature, services, artifacts: graded, findings, blockedBy, verification, scans, interrupted },\n blocking.length,\n ),\n services,\n blockedBy,\n },\n service: narrowed ?? null,\n artifacts,\n checks: {\n ran: !feature.archived,\n coherent: errors.length === 0,\n errors: errors.length,\n warnings,\n gating: findings.filter((f) => f.gates === true).length,\n issues: findings,\n },\n verification,\n useCases,\n next: featureNext(\n { feature, services: inView, artifacts, findings, blockedBy, verification, scans, interrupted },\n opts.boundService,\n ),\n };\n}\n\n/**\n * Everything `loam validate --feature` and `loam archive` would say about this\n * feature, in one list, through the very functions they call.\n *\n * Coherence alone was not it. `validate --feature` also runs `featureProvenance`\n * over intent.md and the scenario-coverage check over every per-service delta,\n * and both grade ERRORS — so a feature whose requirements carry no scenario at\n * all read `coherent: true` here while `validate` exited 1 on the same tree.\n * The coherence findings carry `gates` resolved, because archive's question is\n * not severity's (issue.ts) and no consumer should re-derive it.\n */\nasync function featureFindings(\n docsDir: DocsDir,\n feature: FeatureEntry,\n scans: DeltaScan[],\n context: FleetContext,\n): Promise<Finding[]> {\n const issues: Issue[] = await featureCoherence({ docsDir, featureDir: feature.dir, featureId: feature.id, context });\n const out: Finding[] = issues.map(issueFinding);\n out.push(...(await featureProvenance(feature.dir, feature.id)).filter((f) => f.severity !== \"ok\"));\n // The three refusals `archive` makes before its merge plan runs, from the\n // same functions it calls: a per-service delta addressed to a service that\n // exists nowhere, a specs/ directory whose NAME the id grammar refuses\n // (that one is refused before archive plans anything — the name itself is\n // the path it would become), and a LIVING document still holding git\n // conflict markers. None are coherence issues — one is about a service id\n // that means nothing, one about a name that can never be a path, one about\n // a LIVING document nobody wrote — so they reach archive outside\n // `featureCoherence`, and a projection that only inherited coherence\n // printed \"ship it\" over all three. Errors: each is a refusal, and the rule\n // this module is held to is one-directional.\n out.push(...(await unknownDeltaServices(docsDir, feature.dir, feature.id, { context })));\n out.push(...(await invalidSpecServiceFindings(feature.dir, context)));\n // The capability half of the same scan: status may be redder than the gates\n // and never greener, so a living capability document this feature's delta\n // would rewrite has to be looked at here too. Through the context's memo —\n // this function always has one — so the walk is shared with the coherence\n // pass above rather than repeated.\n const capabilityDeltas = await context.featureCapabilityDeltas(feature.dir);\n out.push(\n ...(await livingMergeConflicts(docsDir, await featureSpecServices(feature.dir, context), capabilityDeltas.docs.map((d) => d.id), context)),\n );\n for (const scan of scans) {\n if (scan.bare.length === 0) continue;\n out.push({\n severity: \"error\",\n code: \"requirements.missing-scenarios\",\n subject: scan.service,\n message: `${scan.label}: ${scan.bare.length} requirement(s) without a scenario`,\n details: scan.bare.map((r) => r.name),\n });\n }\n return out;\n}\n\n/**\n * The feature's own rollup, worst first. The order is the order the work has to\n * happen in, so the winning branch is also the phase the feature is in:\n * unwritten artifacts come before an ordering problem (you can author while\n * another feature is still in flight), an ordering problem comes before this\n * feature's own errors (fixing them may be pointless until the other one\n * lands), and everything comes before the record — answers written over a\n * broken feature are answers to questions that are about to change.\n *\n * The record is excluded from the `missing` branch on purpose. It is not\n * something an author writes: it is the done-check's output, and counting its\n * absence as unwritten work would report every correctly-authored feature as\n * `missing` right up until somebody ran `loam verify` — which is the state\n * `ready` exists to name, and which the fleet form already names correctly.\n */\nfunction featureStage(state: FeatureState, blocking: number): ArtifactStatus {\n const { feature, artifacts, services, blockedBy, verification, interrupted } = state;\n // Ahead of `archived`, and ahead of everything else: a killed commit may be\n // this very feature's, half-applied, so \"it shipped\" is a claim about files\n // nobody has established the contents of. `blocked` is the honest word — the\n // work is stalled on something that is not this feature's artifact — and it\n // is what stops the projection printing `done` over a half-merged repo.\n if (interrupted !== null) return \"blocked\";\n if (feature.archived) return \"done\";\n // A feature with no `specs/<service>/` changes nothing. There is no artifact\n // row to be missing — the rows are per service — so the emptiness itself is\n // the finding, and `next.touch-service` is the step.\n if (services.length === 0) return \"missing\";\n if (artifacts.some((a) => a.id !== \"verification\" && a.status === \"missing\")) return \"missing\";\n if (blockedBy.length > 0) return \"blocked\";\n if (blocking > 0) return \"draft\";\n return fullyVerified(verification) ? \"done\" : \"ready\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../../src/core/status/feature/feature.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAc,MAAM,2BAA2B,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAkB,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAqB,MAAM,YAAY,CAAC;AAG/C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAgB,EAChB,OAAqB,EACrB,IAAwE;IAExE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACzB,MAAM,WAAW,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;IAC9B,MAAM,MAAM,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;IAE1F,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5F,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC;IAChC,8EAA8E;IAC9E,8EAA8E;IAC9E,4EAA4E;IAC5E,2DAA2D;IAC3D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACjG,8EAA8E;IAC9E,wEAAwE;IACxE,4EAA4E;IAC5E,6CAA6C;IAC7C,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1G,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IACtE,6EAA6E;IAC7E,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACxC,2EAA2E;IAC3E,6EAA6E;IAC7E,2EAA2E;IAC3E,sEAAsE;IACtE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3F,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC1D,QAAQ;QACR,YAAY;QACZ,UAAU,EAAE,sBAAsB,CAAC,MAAM,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC;QACtF,OAAO;QACP,uEAAuE;QACvE,qEAAqE;QACrE,4EAA4E;QAC5E,MAAM;QACN,yEAAyE;QACzE,2EAA2E;QAC3E,2DAA2D;QAC3D,YAAY,EAAE,CAAC,MAAM,OAAO,CAAC,uBAAuB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;KACxE,CAAC,CAAC;IACH,MAAM,SAAS,GACb,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC;IAEvG,+EAA+E;IAC/E,+EAA+E;IAC/E,0EAA0E;IAC1E,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ;QAC5B,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,MAAM,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;IAEjF,2EAA2E;IAC3E,qEAAqE;IACrE,6EAA6E;IAC7E,8EAA8E;IAC9E,iDAAiD;IACjD,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC;QACxC,OAAO;QACP,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,QAAQ,CAAC,CAAC;QAC/C,QAAQ,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC;KAC1B,CAAC,CAAC;IAEH,OAAO;QACL,WAAW;QACX,OAAO,EAAE;YACP,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC;YACpC,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,KAAK,EAAE,YAAY,CACjB,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,EAC/F,QAAQ,CAAC,MAAM,CAChB;YACD,QAAQ;YACR,SAAS;SACV;QACD,OAAO,EAAE,QAAQ,IAAI,IAAI;QACzB,SAAS;QACT,MAAM,EAAE;YACN,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ;YACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;YAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ;YACR,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,MAAM;YACvD,MAAM,EAAE,QAAQ;SACjB;QACD,YAAY;QACZ,QAAQ;QACR,IAAI,EAAE,WAAW,CACf,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,EAC/F,IAAI,CAAC,YAAY,CAClB;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,KAAK,UAAU,eAAe,CAC5B,OAAgB,EAChB,OAAqB,EACrB,KAAkB,EAClB,OAAqB;IAErB,MAAM,MAAM,GAAY,MAAM,gBAAgB,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACrH,MAAM,GAAG,GAAc,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAChD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,iBAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC;IACnG,0EAA0E;IAC1E,2EAA2E;IAC3E,uEAAuE;IACvE,0EAA0E;IAC1E,qEAAqE;IACrE,0EAA0E;IAC1E,2EAA2E;IAC3E,iEAAiE;IACjE,qEAAqE;IACrE,4EAA4E;IAC5E,6CAA6C;IAC7C,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACzF,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,0BAA0B,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACtE,4EAA4E;IAC5E,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,mCAAmC;IACnC,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,uBAAuB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5E,GAAG,CAAC,IAAI,CACN,GAAG,CAAC,MAAM,oBAAoB,CAAC,OAAO,EAAE,MAAM,mBAAmB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CAC3I,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,YAAY,CAAC,KAAmB,EAAE,QAAgB;IACzD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;IACrF,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,wEAAwE;IACxE,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,OAAO,CAAC,QAAQ;QAAE,OAAO,MAAM,CAAC;IACpC,6EAA6E;IAC7E,4EAA4E;IAC5E,qDAAqD;IACrD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5C,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/F,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IACjC,OAAO,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;AACxD,CAAC","sourcesContent":["/**\n * One feature: the full report. `featureStatus` gathers what the shared gates\n * say about a feature in flight — findings, the artifact table, the record,\n * the dependency order — and rolls them up into the stage and the steps. The\n * vocabulary it reports in lives in ../report.ts, whose header holds the rules\n * this projection is held to.\n */\nimport { featureCoherence } from \"../../coherence/coherence.js\";\nimport {\n invalidSpecServiceFindings,\n livingMergeConflicts,\n unknownDeltaServices,\n} from \"../../coherence/living.js\";\nimport { analyzeDependencies } from \"../../dependencies/dependencies.js\";\nimport { missingScenariosFinding } from \"../../document/scenarios.js\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { FleetContext } from \"../../fleet-context.js\";\nimport { featureProvenance } from \"../../provenance/findings.js\";\nimport type { FeatureEntry } from \"../../repo/entries.js\";\nimport { featureSpecServices } from \"../../repo/repo.js\";\nimport { enumeratedServices } from \"../../repo/service-target.js\";\nimport { useCaseBlastRadius } from \"../../usecases/touch.js\";\nimport { issueFinding, type Issue } from \"../../vocabulary/issue.js\";\nimport type { Finding } from \"../../vocabulary/report.js\";\nimport { contractOwners, contractsHeldElsewhere } from \"../contracts.js\";\nimport { readInterruptedCommit } from \"../interrupted.js\";\nimport type { ArtifactStatus, FeatureStatusReport } from \"../report.js\";\nimport { governedServices, scanDeltas, type DeltaScan } from \"../scan.js\";\nimport { fullyVerified, verificationState } from \"../verification.js\";\nimport { featureArtifacts } from \"./artifacts.js\";\nimport { featureNext, unshippable } from \"./next.js\";\nimport { type FeatureState } from \"./state.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\n\n/**\n * Everything about one feature in flight. `service` narrows the per-service\n * artifacts and the per-service steps; the feature's own `services` list stays\n * complete either way, because a narrowed view that also hid which other\n * services exist would read as a feature that touches exactly one.\n */\nexport async function featureStatus(\n docsDir: DocsDir,\n feature: FeatureEntry,\n opts: { service?: string; boundService?: string; context: FleetContext },\n): Promise<FeatureStatusReport> {\n const { context } = opts;\n const interrupted = await readInterruptedCommit(docsDir);\n const services = await featureSpecServices(feature.dir, context);\n const narrowed = opts.service;\n const inView = narrowed === undefined ? services : services.filter((s) => s === narrowed);\n\n const scans = feature.archived ? [] : await scanDeltas(docsDir, feature, services, context);\n const read = await verificationState(docsDir, feature);\n const verification = read.state;\n // An archived feature is history: its delta is already folded into the living\n // docs, so coherence would grade the change against a world that has absorbed\n // it and report contradictions that are only the merge having happened. The\n // same reason `list` never calls an archived record stale.\n const findings = feature.archived ? [] : await featureFindings(docsDir, feature, scans, context);\n // `verify.scenario-attested` rides in the same array an agent already parses,\n // under verify's own code — a feature confirmed the short way has to be\n // MARKED here, not merely absent from `stage: done`. It is a warning and it\n // gates nothing, so `coherent` is untouched.\n if (!feature.archived && read.notice !== null) {\n findings.push({ severity: read.notice.severity, code: read.notice.code, message: read.notice.message });\n }\n const errors = findings.filter((f) => f.severity === \"error\");\n const warnings = findings.filter((f) => f.severity === \"warn\").length;\n // The union of the two gates, and the set every verdict below is drawn from.\n const blocking = findings.filter(unshippable);\n\n const governs = governedServices(scans);\n // The whole feature is graded, then the view is narrowed — never the other\n // way round. `--service` is a lens on the table and the steps; a rollup that\n // moved with it would report a feature `ready` because the one service you\n // asked about happens to be written, while three others have nothing.\n const living = new Map((await enumeratedServices(docsDir, context)).map((s) => [s.id, s]));\n const graded = featureArtifacts(docsDir, feature, services, {\n blocking,\n verification,\n contracted: contractsHeldElsewhere(await contractOwners(docsDir, context), feature.id),\n governs,\n // Tolerant on purpose: status must keep answering in a docs repo whose\n // services/ is broken or missing — an unenumerable fleet is doctor's\n // diagnosis, and here it simply means every service reads as never-adopted.\n living,\n // Through the context's memo — the same walk `featureFindings` above and\n // the coherence pass inside it already ran. An archived feature carries no\n // delta to name, and the walk is one `existsSync` besides.\n capabilities: (await context.featureCapabilityDeltas(feature.dir)).docs,\n });\n const artifacts =\n narrowed === undefined ? graded : graded.filter((a) => a.service === null || a.service === narrowed);\n\n // The dependency index rather than the `*-pending` issue codes: those name the\n // other feature in prose, and prose is not an API — dependencies.ts opens with\n // exactly that argument. An archived feature waits on nothing; it landed.\n const graph = feature.archived\n ? null\n : await analyzeDependencies(docsDir, feature.id, context);\n const blockedBy = graph?.nodes.find((n) => n.id === feature.id)?.dependsOn ?? [];\n\n // The flows the services in view are already hops of. The fleet set is the\n // living services UNION the feature's own, so a service this feature\n // introduces resolves to itself rather than to a container's title — and the\n // load underneath gates itself, so a docs repo that declares no use case pays\n // a readdir here rather than a LikeC4 workspace.\n const useCases = await useCaseBlastRadius({\n docsDir,\n known: new Set([...living.keys(), ...services]),\n services: new Set(inView),\n });\n\n return {\n interrupted,\n feature: {\n id: feature.id,\n dirName: feature.dirName,\n path: repoPath(docsDir, feature.dir),\n archived: feature.archived,\n stage: featureStage(\n { feature, services, artifacts: graded, findings, blockedBy, verification, scans, interrupted },\n blocking.length,\n ),\n services,\n blockedBy,\n },\n service: narrowed ?? null,\n artifacts,\n checks: {\n ran: !feature.archived,\n coherent: errors.length === 0,\n errors: errors.length,\n warnings,\n gating: findings.filter((f) => f.gates === true).length,\n issues: findings,\n },\n verification,\n useCases,\n next: featureNext(\n { feature, services: inView, artifacts, findings, blockedBy, verification, scans, interrupted },\n opts.boundService,\n ),\n };\n}\n\n/**\n * Everything `loam validate --feature` and `loam archive` would say about this\n * feature, in one list, through the very functions they call.\n *\n * Coherence alone was not it. `validate --feature` also runs `featureProvenance`\n * over intent.md and the scenario-coverage check over every per-service delta,\n * and both grade ERRORS — so a feature whose requirements carry no scenario at\n * all read `coherent: true` here while `validate` exited 1 on the same tree.\n * The coherence findings carry `gates` resolved, because archive's question is\n * not severity's (issue.ts) and no consumer should re-derive it.\n */\nasync function featureFindings(\n docsDir: DocsDir,\n feature: FeatureEntry,\n scans: DeltaScan[],\n context: FleetContext,\n): Promise<Finding[]> {\n const issues: Issue[] = await featureCoherence({ docsDir, featureDir: feature.dir, featureId: feature.id, context });\n const out: Finding[] = issues.map(issueFinding);\n out.push(...(await featureProvenance(feature.dir, feature.id)).filter((f) => f.severity !== \"ok\"));\n // The three refusals `archive` makes before its merge plan runs, from the\n // same functions it calls: a per-service delta addressed to a service that\n // exists nowhere, a specs/ directory whose NAME the id grammar refuses\n // (that one is refused before archive plans anything — the name itself is\n // the path it would become), and a LIVING document still holding git\n // conflict markers. None are coherence issues — one is about a service id\n // that means nothing, one about a name that can never be a path, one about\n // a LIVING document nobody wrote — so they reach archive outside\n // `featureCoherence`, and a projection that only inherited coherence\n // printed \"ship it\" over all three. Errors: each is a refusal, and the rule\n // this module is held to is one-directional.\n out.push(...(await unknownDeltaServices(docsDir, feature.dir, feature.id, { context })));\n out.push(...(await invalidSpecServiceFindings(feature.dir, context)));\n // The capability half of the same scan: status may be redder than the gates\n // and never greener, so a living capability document this feature's delta\n // would rewrite has to be looked at here too. Through the context's memo —\n // this function always has one — so the walk is shared with the coherence\n // pass above rather than repeated.\n const capabilityDeltas = await context.featureCapabilityDeltas(feature.dir);\n out.push(\n ...(await livingMergeConflicts(docsDir, await featureSpecServices(feature.dir, context), capabilityDeltas.docs.map((d) => d.id), context)),\n );\n for (const scan of scans) {\n if (scan.bare.length > 0) out.push(missingScenariosFinding(scan.label, scan.bare, scan.service));\n }\n return out;\n}\n\n/**\n * The feature's own rollup, worst first. The order is the order the work has to\n * happen in, so the winning branch is also the phase the feature is in:\n * unwritten artifacts come before an ordering problem (you can author while\n * another feature is still in flight), an ordering problem comes before this\n * feature's own errors (fixing them may be pointless until the other one\n * lands), and everything comes before the record — answers written over a\n * broken feature are answers to questions that are about to change.\n *\n * The record is excluded from the `missing` branch on purpose. It is not\n * something an author writes: it is the done-check's output, and counting its\n * absence as unwritten work would report every correctly-authored feature as\n * `missing` right up until somebody ran `loam verify` — which is the state\n * `ready` exists to name, and which the fleet form already names correctly.\n */\nfunction featureStage(state: FeatureState, blocking: number): ArtifactStatus {\n const { feature, artifacts, services, blockedBy, verification, interrupted } = state;\n // Ahead of `archived`, and ahead of everything else: a killed commit may be\n // this very feature's, half-applied, so \"it shipped\" is a claim about files\n // nobody has established the contents of. `blocked` is the honest word — the\n // work is stalled on something that is not this feature's artifact — and it\n // is what stops the projection printing `done` over a half-merged repo.\n if (interrupted !== null) return \"blocked\";\n if (feature.archived) return \"done\";\n // A feature with no `specs/<service>/` changes nothing. There is no artifact\n // row to be missing — the rows are per service — so the emptiness itself is\n // the finding, and `next.touch-service` is the step.\n if (services.length === 0) return \"missing\";\n if (artifacts.some((a) => a.id !== \"verification\" && a.status === \"missing\")) return \"missing\";\n if (blockedBy.length > 0) return \"blocked\";\n if (blocking > 0) return \"draft\";\n return fullyVerified(verification) ? \"done\" : \"ready\";\n}\n"]}
|
|
@@ -33,8 +33,8 @@ import { fleetNext } from "./next.js";
|
|
|
33
33
|
* are drawn from artifact presence and from a record that already exists, and
|
|
34
34
|
* `next[]` hands off to `loam status <FEAT>` for everything else.
|
|
35
35
|
*/
|
|
36
|
-
export async function fleetStatus(docsDir, opts
|
|
37
|
-
const context = opts
|
|
36
|
+
export async function fleetStatus(docsDir, opts) {
|
|
37
|
+
const { context } = opts;
|
|
38
38
|
const interrupted = await readInterruptedCommit(docsDir);
|
|
39
39
|
const narrowed = opts.service;
|
|
40
40
|
const all = await listServices(docsDir, context);
|
|
@@ -88,6 +88,10 @@ export async function fleetStatus(docsDir, opts = {}) {
|
|
|
88
88
|
// headline, and "118 vouched" over a fleet where 90 of them were read
|
|
89
89
|
// from a sample is the misreading the scope exists to prevent.
|
|
90
90
|
sampledVouched: services.filter((s) => vouched(s) && s.vouchScope === "sampled").length,
|
|
91
|
+
// The other subset, for the same reason: "118 vouched" over a fleet where
|
|
92
|
+
// twenty documents were edited after the person read them is the
|
|
93
|
+
// trust headline lying by omission.
|
|
94
|
+
contentStale: services.filter((s) => vouched(s) && s.contentStale.length > 0).length,
|
|
91
95
|
},
|
|
92
96
|
features,
|
|
93
97
|
order: graph.order.filter((id) => features.some((f) => f.id === id)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleet.js","sourceRoot":"","sources":["../../../../src/core/status/fleet/fleet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The whole repository — why the fleet form is cheaper than the feature form.\n *\n * `loam status` with no feature runs over every active feature, and deriving a\n * coherence report or a verification checklist per feature means spinning a\n * fresh LikeC4 workspace per feature. On a fleet-sized repository that is the\n * cost that gets a command switched off — `list` made the same call about its\n * verification column. So the fleet form grades nothing: it reports artifact\n * PRESENCE, the dependency order, and a verification record only where one\n * already exists, and its `next[]` hands off to `loam status <FEAT>`, which is\n * where the checks actually run.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { analyzeDependencies } from \"../../dependencies/dependencies.js\";\nimport { type DependencyGraph } from \"../../dependencies/facts.js\";\nimport { isLandscapeStub } from \"../../scaffold/landscape.js\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { FleetContext } from \"../../fleet-context.js\";\nimport { compareIds, type FeatureEntry, type ServiceEntry } from \"../../repo/entries.js\";\nimport { featurePaths, featureSpecPaths, landscapePath } from \"../../repo/paths.js\";\nimport { listFeatures, listServices } from \"../../repo/repo.js\";\nimport { contractOwners, contractsHeldElsewhere, owesContract } from \"../contracts.js\";\nimport { readInterruptedCommit } from \"../interrupted.js\";\nimport {\n undocumented,\n vouched,\n type ArtifactStatus,\n type FleetFeatureState,\n type FleetStatusReport,\n type InterruptedCommit,\n} from \"../report.js\";\nimport { governedServices, scanDeltas } from \"../scan.js\";\nimport { fullyVerified, verificationState } from \"../verification.js\";\nimport { fleetNext, type Binding } from \"./next.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\n\n/**\n * The fleet view: how much of `services/` anyone has written down, every\n * feature in flight with the stage it has reached, and the order they have to\n * land in. Nothing here is graded — the module header says why — so the stages\n * are drawn from artifact presence and from a record that already exists, and\n * `next[]` hands off to `loam status <FEAT>` for everything else.\n */\nexport async function fleetStatus(\n docsDir: DocsDir,\n opts: { service?: string; bound?: string; context?: FleetContext } = {},\n): Promise<FleetStatusReport> {\n const context = opts.context ?? new FleetContext();\n const interrupted = await readInterruptedCommit(docsDir);\n const narrowed = opts.service;\n const all = await listServices(docsDir, context);\n const services = narrowed === undefined ? all : all.filter((s) => s.id === narrowed);\n // The service this repository says it IS, and whether the fleet has heard of\n // it. Asked against the UNNARROWED list, because \"is it adopted\" is a question\n // about the fleet and not about the view.\n //\n // Both facts travel, where only the unadopted case used to. This value was a\n // `string | null` that went null exactly when the service directory EXISTED,\n // so the walk could see the repository it stands in only while that\n // repository was undocumented — and a developer in an adopted service repo\n // got the whole fleet's worklist with their own boundary buried in it.\n const binding: Binding | null =\n opts.bound === undefined ? null : { id: opts.bound, adopted: all.some((s) => s.id === opts.bound) };\n const graph = await analyzeDependencies(docsDir, undefined, context);\n const entries = await listFeatures(docsDir, {}, context);\n const inScope = narrowed === undefined ? entries : entries.filter((f) => f.services.some((s) => s === narrowed));\n // Once for the fleet, not once per feature: which features hold a contract\n // for which service is one fact about the repository, and every feature below\n // reads its own row out of it. Built over the UNNARROWED list on purpose — a\n // `--service` view still has to know that a feature outside it discharges the\n // contract, or the narrowing would invent an obligation.\n const owners = await contractOwners(docsDir, context);\n\n const features = await Promise.all(\n inScope.map((f) =>\n fleetFeature(docsDir, f, {\n graph,\n contracted: contractsHeldElsewhere(owners, f.id),\n living: new Map(all.map((s) => [s.id, s])),\n context,\n interrupted,\n }),\n ),\n );\n features.sort((a, b) => compareIds(a.id, b.id));\n\n // The first-hour ladder's fact, computed exactly when the ladder can apply\n // — an unnarrowed run over a fleet with zero services and zero features —\n // so the read disappears from every repository that has anything in it.\n // Withheld on narrowed runs for the same reason the binding is withheld\n // below: `--service X` is an explicit question about X, and the\n // repository's onboarding ladder would be a different question's answer.\n const teaching =\n narrowed === undefined && services.length === 0 && features.length === 0\n ? { landscape: await landscapeTeaching(docsDir) }\n : null;\n\n return {\n interrupted,\n services: {\n total: services.length,\n undocumented: services.filter(undocumented).length,\n draft: services.filter((s) => !undocumented(s) && !vouched(s)).length,\n vouched: services.filter(vouched).length,\n // A subset of `vouched`, never a fourth bucket: those services ARE\n // vouched, and the three counts still sum to `total`. It rides beside\n // the count it qualifies because this line is the fleet's trust\n // headline, and \"118 vouched\" over a fleet where 90 of them were read\n // from a sample is the misreading the scope exists to prevent.\n sampledVouched: services.filter((s) => vouched(s) && s.vouchScope === \"sampled\").length,\n },\n features,\n order: graph.order.filter((id) => features.some((f) => f.id === id)),\n service: narrowed ?? null,\n // The binding is passed only when this run was NOT narrowed, and that guard\n // is unchanged now that the binding also decides ORDER. `--service X` is an\n // explicit question about X, and answering it with a step about whichever\n // service loam.json happens to name would be a different question's answer\n // at the top of the list — which is as true of a rung promoted to the front\n // as it was of `next.adopt-bound` emitted out of nowhere. A narrowed run\n // sees at most the one service it asked about anyway, so withholding the\n // binding here costs the reader nothing and keeps `--service X` answering\n // only about X.\n next: fleetNext(\n { services, features, graph, interrupted, teaching },\n narrowed === undefined ? binding : null,\n ),\n };\n}\n\n/**\n * Which first-hour state the fleet map is in: absent, still the scaffold's own\n * untouched bytes, or authored. One file read and a byte comparison — the\n * module header's cost rule stands, no LikeC4 workspace is spun here.\n */\nasync function landscapeTeaching(docsDir: DocsDir): Promise<\"missing\" | \"stub\" | \"authored\"> {\n const lp = landscapePath(docsDir);\n if (!existsSync(lp)) return \"missing\";\n try {\n return isLandscapeStub(await readFile(lp, \"utf8\")) ? \"stub\" : \"authored\";\n } catch {\n // Unreadable is not untouched. A map that cannot be read — a directory in\n // the file's place, a permission — must not be called the scaffold's stub\n // (\"missing\" and \"stub\" would each put a false statement in next[]), so it\n // reads as \"authored\", which only drops the one teaching step: the gate\n // entry still names `loam validate --all`, where an unreadable landscape\n // is actually graded, and letting this read throw would hand the whole\n // orientation surface — a command that reports and never gates — to the\n // repository-unavailable refusal over a file status does not even parse.\n return \"authored\";\n }\n}\n\n/** What a feature's fleet row needs about the fleet AROUND it. */\ninterface FleetView {\n graph: DependencyGraph;\n /** Operations some other feature's contract already holds. */\n contracted: ReadonlySet<string>;\n /** The enumeration's entries by id — where each living service is and what it has (owesContract reads both). */\n living: ReadonlyMap<string, ServiceEntry>;\n context: FleetContext;\n interrupted: InterruptedCommit | null;\n}\n\nasync function fleetFeature(\n docsDir: DocsDir,\n feature: FeatureEntry,\n view: FleetView,\n): Promise<FleetFeatureState> {\n const { graph, contracted, living, context, interrupted } = view;\n const paths = featurePaths(feature.dir);\n const missing: string[] = [];\n if (!existsSync(paths.intent)) missing.push(\"intent\");\n if (feature.services.length === 0) missing.push(\"spec\");\n // Requirement text, not a coherence run: `Operations:` lines are read off the\n // same cached parse `list` already pays for, and the contract question needs\n // them (owesContract). No LikeC4 workspace is spun here, which is the cost\n // this form actually refuses.\n const governs = governedServices(await scanDeltas(docsDir, feature, feature.services, context));\n for (const svc of feature.services) {\n const p = featureSpecPaths(feature.dir, svc);\n // EITHER corpus satisfies it. What a service delta owes is a requirement\n // delta, and `arch.spec.md` is the other one — same grammar, same delta\n // algebra, read by `featureChecklist` — so an architecture-only feature is\n // complete and every gate already says so. This form asked for `spec.md`\n // specifically, which broke the rule the comment two lines down states\n // about the contract question: the two forms disagreed, `loam status`\n // reporting `missing <svc>/spec` over a feature `loam status <FEAT>` and\n // `loam validate --feature` both called complete. The name stays\n // `<svc>/spec` when NEITHER exists, because the business delta is the one\n // an author writes by default.\n if (!existsSync(p.spec) && !existsSync(p.archSpec)) missing.push(`${svc}/spec`);\n // The same question featureArtifacts asks, through the same function: the\n // two forms must never disagree about whether a feature owes a contract.\n if (owesContract(living.get(svc), contracted.has(svc), governs.has(svc)) && !existsSync(p.openapi)) {\n missing.push(`${svc}/openapi`);\n }\n }\n const verification = (await verificationState(docsDir, feature)).state;\n const blockedBy = graph.nodes.find((n) => n.id === feature.id)?.dependsOn ?? [];\n\n // No `draft` here, ever: that verdict needs the coherence run this form\n // refuses to pay for, and inferring it from presence alone would be a second\n // opinion about validity — the one thing status must never invent.\n //\n // The interrupted commit leads, exactly as it does in the feature form: every\n // presence test below is a question about files a killed `archive` may have\n // half-written, so answering `done` from them is a claim about bytes nobody\n // has established. Without this the two forms contradicted each other over\n // one repository — `loam status --json` said `done` and offered \"ship it\"\n // while `loam status <FEAT>` said `blocked` — and the rule in report.ts's\n // header is that this projection is never greener than the gates.\n const stage: ArtifactStatus =\n interrupted !== null\n ? \"blocked\"\n : missing.length > 0\n ? \"missing\"\n : blockedBy.length > 0\n ? \"blocked\"\n : fullyVerified(verification)\n ? \"done\"\n : \"ready\";\n\n return {\n id: feature.id,\n dirName: feature.dirName,\n path: repoPath(docsDir, feature.dir),\n stage,\n services: feature.services,\n blockedBy,\n missing,\n verification,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fleet.js","sourceRoot":"","sources":["../../../../src/core/status/fleet/fleet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAwB,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAwC,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACvF,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EACL,YAAY,EACZ,OAAO,GAKR,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAgB,MAAM,WAAW,CAAC;AAGpD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAgB,EAChB,IAAiE;IAEjE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACzB,MAAM,WAAW,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;IAC9B,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC;IACrF,6EAA6E;IAC7E,+EAA+E;IAC/E,0CAA0C;IAC1C,EAAE;IACF,6EAA6E;IAC7E,6EAA6E;IAC7E,oEAAoE;IACpE,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,OAAO,GACX,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IACtG,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC;IACjH,2EAA2E;IAC3E,8EAA8E;IAC9E,6EAA6E;IAC7E,8EAA8E;IAC9E,yDAAyD;IACzD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAEtD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAChB,YAAY,CAAC,OAAO,EAAE,CAAC,EAAE;QACvB,KAAK;QACL,UAAU,EAAE,sBAAsB,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;QAChD,MAAM,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1C,OAAO;QACP,WAAW;KACZ,CAAC,CACH,CACF,CAAC;IACF,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEhD,2EAA2E;IAC3E,0EAA0E;IAC1E,wEAAwE;IACxE,wEAAwE;IACxE,gEAAgE;IAChE,yEAAyE;IACzE,MAAM,QAAQ,GACZ,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QACtE,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC,OAAO,CAAC,EAAE;QACjD,CAAC,CAAC,IAAI,CAAC;IAEX,OAAO;QACL,WAAW;QACX,QAAQ,EAAE;YACR,KAAK,EAAE,QAAQ,CAAC,MAAM;YACtB,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM;YAClD,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM;YACrE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM;YACxC,mEAAmE;YACnE,sEAAsE;YACtE,gEAAgE;YAChE,sEAAsE;YACtE,+DAA+D;YAC/D,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,MAAM;YACvF,0EAA0E;YAC1E,iEAAiE;YACjE,oCAAoC;YACpC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM;SACrF;QACD,QAAQ;QACR,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACpE,OAAO,EAAE,QAAQ,IAAI,IAAI;QACzB,4EAA4E;QAC5E,4EAA4E;QAC5E,0EAA0E;QAC1E,2EAA2E;QAC3E,4EAA4E;QAC5E,yEAAyE;QACzE,yEAAyE;QACzE,0EAA0E;QAC1E,gBAAgB;QAChB,IAAI,EAAE,SAAS,CACb,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,EACpD,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CACxC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,iBAAiB,CAAC,OAAgB;IAC/C,MAAM,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,eAAe,CAAC,MAAM,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,0EAA0E;QAC1E,2EAA2E;QAC3E,wEAAwE;QACxE,yEAAyE;QACzE,uEAAuE;QACvE,wEAAwE;QACxE,yEAAyE;QACzE,OAAO,UAAU,CAAC;IACpB,CAAC;AACH,CAAC;AAaD,KAAK,UAAU,YAAY,CACzB,OAAgB,EAChB,OAAqB,EACrB,IAAe;IAEf,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACjE,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtD,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxD,8EAA8E;IAC9E,6EAA6E;IAC7E,2EAA2E;IAC3E,8BAA8B;IAC9B,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAChG,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC7C,yEAAyE;QACzE,wEAAwE;QACxE,2EAA2E;QAC3E,yEAAyE;QACzE,uEAAuE;QACvE,sEAAsE;QACtE,yEAAyE;QACzE,iEAAiE;QACjE,0EAA0E;QAC1E,+BAA+B;QAC/B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC;QAChF,0EAA0E;QAC1E,yEAAyE;QACzE,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YACnG,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IACD,MAAM,YAAY,GAAG,CAAC,MAAM,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IACvE,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;IAEhF,wEAAwE;IACxE,6EAA6E;IAC7E,mEAAmE;IACnE,EAAE;IACF,8EAA8E;IAC9E,4EAA4E;IAC5E,4EAA4E;IAC5E,2EAA2E;IAC3E,0EAA0E;IAC1E,0EAA0E;IAC1E,kEAAkE;IAClE,MAAM,KAAK,GACT,WAAW,KAAK,IAAI;QAClB,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YAClB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;gBACpB,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC;oBAC3B,CAAC,CAAC,MAAM;oBACR,CAAC,CAAC,OAAO,CAAC;IAEpB,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC;QACpC,KAAK;QACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS;QACT,OAAO;QACP,YAAY;KACb,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The whole repository — why the fleet form is cheaper than the feature form.\n *\n * `loam status` with no feature runs over every active feature, and deriving a\n * coherence report or a verification checklist per feature means spinning a\n * fresh LikeC4 workspace per feature. On a fleet-sized repository that is the\n * cost that gets a command switched off — `list` made the same call about its\n * verification column. So the fleet form grades nothing: it reports artifact\n * PRESENCE, the dependency order, and a verification record only where one\n * already exists, and its `next[]` hands off to `loam status <FEAT>`, which is\n * where the checks actually run.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { analyzeDependencies } from \"../../dependencies/dependencies.js\";\nimport { type DependencyGraph } from \"../../dependencies/facts.js\";\nimport { isLandscapeStub } from \"../../scaffold/landscape.js\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { FleetContext } from \"../../fleet-context.js\";\nimport { compareIds, type FeatureEntry, type ServiceEntry } from \"../../repo/entries.js\";\nimport { featurePaths, featureSpecPaths, landscapePath } from \"../../repo/paths.js\";\nimport { listFeatures, listServices } from \"../../repo/repo.js\";\nimport { contractOwners, contractsHeldElsewhere, owesContract } from \"../contracts.js\";\nimport { readInterruptedCommit } from \"../interrupted.js\";\nimport {\n undocumented,\n vouched,\n type ArtifactStatus,\n type FleetFeatureState,\n type FleetStatusReport,\n type InterruptedCommit,\n} from \"../report.js\";\nimport { governedServices, scanDeltas } from \"../scan.js\";\nimport { fullyVerified, verificationState } from \"../verification.js\";\nimport { fleetNext, type Binding } from \"./next.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\n\n/**\n * The fleet view: how much of `services/` anyone has written down, every\n * feature in flight with the stage it has reached, and the order they have to\n * land in. Nothing here is graded — the module header says why — so the stages\n * are drawn from artifact presence and from a record that already exists, and\n * `next[]` hands off to `loam status <FEAT>` for everything else.\n */\nexport async function fleetStatus(\n docsDir: DocsDir,\n opts: { service?: string; bound?: string; context: FleetContext },\n): Promise<FleetStatusReport> {\n const { context } = opts;\n const interrupted = await readInterruptedCommit(docsDir);\n const narrowed = opts.service;\n const all = await listServices(docsDir, context);\n const services = narrowed === undefined ? all : all.filter((s) => s.id === narrowed);\n // The service this repository says it IS, and whether the fleet has heard of\n // it. Asked against the UNNARROWED list, because \"is it adopted\" is a question\n // about the fleet and not about the view.\n //\n // Both facts travel, where only the unadopted case used to. This value was a\n // `string | null` that went null exactly when the service directory EXISTED,\n // so the walk could see the repository it stands in only while that\n // repository was undocumented — and a developer in an adopted service repo\n // got the whole fleet's worklist with their own boundary buried in it.\n const binding: Binding | null =\n opts.bound === undefined ? null : { id: opts.bound, adopted: all.some((s) => s.id === opts.bound) };\n const graph = await analyzeDependencies(docsDir, undefined, context);\n const entries = await listFeatures(docsDir, {}, context);\n const inScope = narrowed === undefined ? entries : entries.filter((f) => f.services.some((s) => s === narrowed));\n // Once for the fleet, not once per feature: which features hold a contract\n // for which service is one fact about the repository, and every feature below\n // reads its own row out of it. Built over the UNNARROWED list on purpose — a\n // `--service` view still has to know that a feature outside it discharges the\n // contract, or the narrowing would invent an obligation.\n const owners = await contractOwners(docsDir, context);\n\n const features = await Promise.all(\n inScope.map((f) =>\n fleetFeature(docsDir, f, {\n graph,\n contracted: contractsHeldElsewhere(owners, f.id),\n living: new Map(all.map((s) => [s.id, s])),\n context,\n interrupted,\n }),\n ),\n );\n features.sort((a, b) => compareIds(a.id, b.id));\n\n // The first-hour ladder's fact, computed exactly when the ladder can apply\n // — an unnarrowed run over a fleet with zero services and zero features —\n // so the read disappears from every repository that has anything in it.\n // Withheld on narrowed runs for the same reason the binding is withheld\n // below: `--service X` is an explicit question about X, and the\n // repository's onboarding ladder would be a different question's answer.\n const teaching =\n narrowed === undefined && services.length === 0 && features.length === 0\n ? { landscape: await landscapeTeaching(docsDir) }\n : null;\n\n return {\n interrupted,\n services: {\n total: services.length,\n undocumented: services.filter(undocumented).length,\n draft: services.filter((s) => !undocumented(s) && !vouched(s)).length,\n vouched: services.filter(vouched).length,\n // A subset of `vouched`, never a fourth bucket: those services ARE\n // vouched, and the three counts still sum to `total`. It rides beside\n // the count it qualifies because this line is the fleet's trust\n // headline, and \"118 vouched\" over a fleet where 90 of them were read\n // from a sample is the misreading the scope exists to prevent.\n sampledVouched: services.filter((s) => vouched(s) && s.vouchScope === \"sampled\").length,\n // The other subset, for the same reason: \"118 vouched\" over a fleet where\n // twenty documents were edited after the person read them is the\n // trust headline lying by omission.\n contentStale: services.filter((s) => vouched(s) && s.contentStale.length > 0).length,\n },\n features,\n order: graph.order.filter((id) => features.some((f) => f.id === id)),\n service: narrowed ?? null,\n // The binding is passed only when this run was NOT narrowed, and that guard\n // is unchanged now that the binding also decides ORDER. `--service X` is an\n // explicit question about X, and answering it with a step about whichever\n // service loam.json happens to name would be a different question's answer\n // at the top of the list — which is as true of a rung promoted to the front\n // as it was of `next.adopt-bound` emitted out of nowhere. A narrowed run\n // sees at most the one service it asked about anyway, so withholding the\n // binding here costs the reader nothing and keeps `--service X` answering\n // only about X.\n next: fleetNext(\n { services, features, graph, interrupted, teaching },\n narrowed === undefined ? binding : null,\n ),\n };\n}\n\n/**\n * Which first-hour state the fleet map is in: absent, still the scaffold's own\n * untouched bytes, or authored. One file read and a byte comparison — the\n * module header's cost rule stands, no LikeC4 workspace is spun here.\n */\nasync function landscapeTeaching(docsDir: DocsDir): Promise<\"missing\" | \"stub\" | \"authored\"> {\n const lp = landscapePath(docsDir);\n if (!existsSync(lp)) return \"missing\";\n try {\n return isLandscapeStub(await readFile(lp, \"utf8\")) ? \"stub\" : \"authored\";\n } catch {\n // Unreadable is not untouched. A map that cannot be read — a directory in\n // the file's place, a permission — must not be called the scaffold's stub\n // (\"missing\" and \"stub\" would each put a false statement in next[]), so it\n // reads as \"authored\", which only drops the one teaching step: the gate\n // entry still names `loam validate --all`, where an unreadable landscape\n // is actually graded, and letting this read throw would hand the whole\n // orientation surface — a command that reports and never gates — to the\n // repository-unavailable refusal over a file status does not even parse.\n return \"authored\";\n }\n}\n\n/** What a feature's fleet row needs about the fleet AROUND it. */\ninterface FleetView {\n graph: DependencyGraph;\n /** Operations some other feature's contract already holds. */\n contracted: ReadonlySet<string>;\n /** The enumeration's entries by id — where each living service is and what it has (owesContract reads both). */\n living: ReadonlyMap<string, ServiceEntry>;\n context: FleetContext;\n interrupted: InterruptedCommit | null;\n}\n\nasync function fleetFeature(\n docsDir: DocsDir,\n feature: FeatureEntry,\n view: FleetView,\n): Promise<FleetFeatureState> {\n const { graph, contracted, living, context, interrupted } = view;\n const paths = featurePaths(feature.dir);\n const missing: string[] = [];\n if (!existsSync(paths.intent)) missing.push(\"intent\");\n if (feature.services.length === 0) missing.push(\"spec\");\n // Requirement text, not a coherence run: `Operations:` lines are read off the\n // same cached parse `list` already pays for, and the contract question needs\n // them (owesContract). No LikeC4 workspace is spun here, which is the cost\n // this form actually refuses.\n const governs = governedServices(await scanDeltas(docsDir, feature, feature.services, context));\n for (const svc of feature.services) {\n const p = featureSpecPaths(feature.dir, svc);\n // EITHER corpus satisfies it. What a service delta owes is a requirement\n // delta, and `arch.spec.md` is the other one — same grammar, same delta\n // algebra, read by `featureChecklist` — so an architecture-only feature is\n // complete and every gate already says so. This form asked for `spec.md`\n // specifically, which broke the rule the comment two lines down states\n // about the contract question: the two forms disagreed, `loam status`\n // reporting `missing <svc>/spec` over a feature `loam status <FEAT>` and\n // `loam validate --feature` both called complete. The name stays\n // `<svc>/spec` when NEITHER exists, because the business delta is the one\n // an author writes by default.\n if (!existsSync(p.spec) && !existsSync(p.archSpec)) missing.push(`${svc}/spec`);\n // The same question featureArtifacts asks, through the same function: the\n // two forms must never disagree about whether a feature owes a contract.\n if (owesContract(living.get(svc), contracted.has(svc), governs.has(svc)) && !existsSync(p.openapi)) {\n missing.push(`${svc}/openapi`);\n }\n }\n const verification = (await verificationState(docsDir, feature)).state;\n const blockedBy = graph.nodes.find((n) => n.id === feature.id)?.dependsOn ?? [];\n\n // No `draft` here, ever: that verdict needs the coherence run this form\n // refuses to pay for, and inferring it from presence alone would be a second\n // opinion about validity — the one thing status must never invent.\n //\n // The interrupted commit leads, exactly as it does in the feature form: every\n // presence test below is a question about files a killed `archive` may have\n // half-written, so answering `done` from them is a claim about bytes nobody\n // has established. Without this the two forms contradicted each other over\n // one repository — `loam status --json` said `done` and offered \"ship it\"\n // while `loam status <FEAT>` said `blocked` — and the rule in report.ts's\n // header is that this projection is never greener than the gates.\n const stage: ArtifactStatus =\n interrupted !== null\n ? \"blocked\"\n : missing.length > 0\n ? \"missing\"\n : blockedBy.length > 0\n ? \"blocked\"\n : fullyVerified(verification)\n ? \"done\"\n : \"ready\";\n\n return {\n id: feature.id,\n dirName: feature.dirName,\n path: repoPath(docsDir, feature.dir),\n stage,\n services: feature.services,\n blockedBy,\n missing,\n verification,\n };\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { serviceTreePath } from "../../kernel/ids/dirs.js";
|
|
2
2
|
import { compareIds } from "../../repo/entries.js";
|
|
3
3
|
import { recoverStep } from "../interrupted.js";
|
|
4
|
-
import { undocumented, } from "../report.js";
|
|
4
|
+
import { undocumented, vouched, } from "../report.js";
|
|
5
5
|
/** How urgent a stage is at fleet level. See {@link fleetNext}. */
|
|
6
6
|
const STAGE_RANK = {
|
|
7
7
|
done: 0,
|
|
@@ -87,8 +87,13 @@ export function fleetNext(fleet, binding) {
|
|
|
87
87
|
steps.push({
|
|
88
88
|
code: "next.bind-service",
|
|
89
89
|
statement: "No service repository is bound to this fleet yet. From a service's own repo, point --docs " +
|
|
90
|
-
"here and --service at its canonical id, then commit the
|
|
91
|
-
|
|
90
|
+
"here and --service at its canonical id with the service agent profile, then commit the " +
|
|
91
|
+
"loam.json it writes.",
|
|
92
|
+
// `--agent-profile service`, because the default is `full`, and `full`
|
|
93
|
+
// writes loam-feature and loam-ship into the service checkout — the two
|
|
94
|
+
// workflows this docs repo keeps, which README tells every service
|
|
95
|
+
// repo to leave out. An agent runs this literal, not the README.
|
|
96
|
+
command: "loam init --docs <path-to-this-docs-repo> --service <service-id> --agent-profile service",
|
|
92
97
|
});
|
|
93
98
|
steps.push({
|
|
94
99
|
code: "next.adopt-first",
|
|
@@ -124,6 +129,24 @@ export function fleetNext(fleet, binding) {
|
|
|
124
129
|
service: s.id,
|
|
125
130
|
});
|
|
126
131
|
}
|
|
132
|
+
// A vouch the document has moved out from under. `content.stale` needs
|
|
133
|
+
// nothing but the document, so unlike `sources.stale` this form CAN see it —
|
|
134
|
+
// and before it looked, a fleet whose specs were edited after the person read
|
|
135
|
+
// them got `next.fleet-clean`: "nothing is owed" over words nobody had
|
|
136
|
+
// re-read, while `loam validate` warned about each one. The re-vouch is a
|
|
137
|
+
// person's act from that service's own repository (`loam vouch` restamps the
|
|
138
|
+
// code side too), so the step names the documents and that repository, and
|
|
139
|
+
// its execution contract is a human review rather than a command to run.
|
|
140
|
+
for (const s of services.filter((x) => vouched(x) && x.contentStale.length > 0)) {
|
|
141
|
+
const files = s.contentStale.map((file) => `${serviceTreePath(s)}/${file}`);
|
|
142
|
+
perService.push({
|
|
143
|
+
code: "next.revouch",
|
|
144
|
+
statement: `${files.join(" and ")} changed since ${s.id} was vouched — \`status: verified\` stands over words nobody has re-read (content.stale). ` +
|
|
145
|
+
`A person re-reads what moved (\`loam vouch --pack --service ${s.id}\` lists it) and re-vouches from ${s.id}'s own repository.`,
|
|
146
|
+
command: `loam vouch --service ${s.id}`,
|
|
147
|
+
service: s.id,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
127
150
|
// The boundary this repository IS comes first among the boundaries it merely
|
|
128
151
|
// shares a fleet with. A stable PARTITION and deliberately not a sort: the
|
|
129
152
|
// bound service's rungs move to the front and every other service keeps the
|