@ybotok/loam 0.2.0-alpha.7 → 0.2.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1315 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +83 -7
- package/SCHEMA.md +569 -98
- package/WORKFLOW.md +272 -62
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +33 -11
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/explore.js +4 -8
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +28 -33
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +21 -25
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +3 -5
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +5 -8
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +14 -41
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +17 -38
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +32 -5
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +23 -23
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +6 -10
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +1 -3
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +8 -28
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/report.js +5 -4
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +16 -104
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +1 -1
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +54 -51
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +56 -101
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +4 -3
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +32 -18
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +9 -3
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +20 -16
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +50 -18
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +6 -18
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +15 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/documents.js +31 -6
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +6 -17
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/findings.js +15 -20
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/coherence.js +30 -16
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +119 -8
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +3 -6
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +28 -16
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +5 -2
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +4 -2
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +5 -36
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +22 -8
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +56 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +2 -16
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/repo.js +24 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/walk.js +8 -30
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/staging/commit.js +10 -12
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +174 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +3 -0
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +12 -2
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +21 -6
- package/examples/docs/architecture/landscape.likec4 +8 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"implement.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/implement.ts"],"names":[],"mappings":"AAEA,6FAA6F;AAC7F,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,sGAAsG;IACxG,YAAY,EAAE,qBAAqB;IACnC,OAAO,EACL,oMAAoM;IACtM,UAAU,EAAE,wCAAwC;IACpD,YAAY,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC;IACpC,KAAK,EAAE;QACL,mFAAmF;QACnF,8FAA8F;QAC9F,0GAA0G;QAC1G,6FAA6F;QAC7F,2FAA2F;QAC3F,iEAAiE;QACjE,iGAAiG;QACjG,qGAAqG;KACtG;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/** Building one service's slice: the delta brief, generated gherkin, steps, code, verify. */\nexport const LOAM_IMPLEMENT: CommandContent = {\n name: \"loam-implement\",\n description:\n \"Implement a loam feature's slice for one service — generated gherkin first, tests from it, then code\",\n argumentHint: \"<FEAT-id> [service]\",\n purpose:\n \"Build one service's part of a feature, in that service's own repository. The generated Gherkin is the acceptance criterion — it comes before the step definitions, and those come before the code.\",\n invocation: \"loam instructions loam-implement $1 $2\",\n placeholders: [\"feature\", \"service\"],\n spine: [\n \"`loam status` — where this feature actually is, and what is owed before you start\",\n \"`loam context` — the briefing: the service's living slice, and every delta in flight over it\",\n \"`loam delta` — the task: the intent, every requirement verbatim, the endpoints, and the calls in and out\",\n \"`loam gherkin` in the service's repo — the digest-stamped `.feature` files. Never edit them\",\n \"write step definitions for the generated scenarios first, outside the generated directory\",\n \"implement until the suite passes, running it with a JSON report\",\n \"honour the contract in both directions: expose every inbound operation, call every outbound one\",\n \"`loam rebase` if building made you change the feature's documents at all, then validate the feature\",\n ],\n body: `Implement one service's part of a feature. Every step runs in the SERVICE'S own\nrepository, which needs its own committed ./loam.json — if there is none,\n\\`loam init --docs <path-to-docs-repo> --service $2\\` then \\`loam doctor\\` first.\n\n0. \\`loam status $1 --json\\` — where this feature actually is. Run it before\n anything else if you joined halfway or lost the session: it says which\n artifacts are still owed, whether another feature has to archive first, and\n what the next step is, each \\`next[]\\` entry carrying the literal command. It\n writes nothing, so it is always safe to re-run.\n1. \\`loam context $2 --feature $1 --json\\` — the briefing to load before touching\n code: the service's living requirements and arch requirements verbatim, its\n operations and messages as they stand today, the permissions and capabilities\n its requirements join to, the fleet edges around it, and this feature's delta\n over it (\\`--feature $1\\` narrows the in-flight section; drop it to see every\n feature working over this service). The delta below is WHAT CHANGES; the pack\n is what it changes INTO — read it first so a MODIFIED requirement lands as an\n edit rather than a rewrite, and an operation you are about to add is caught\n when the living contract already defines it. Exit 1 with \\`ok: true\\` means a\n document behind the pack did not parse; the empty section beside the flag is\n the parse failure, not \"nothing here\".\n2. \\`loam delta $1 --service $2 --json\\` (drop \\`--service\\` to use the service configured\n in ./loam.json). That output IS the task:\n - \\`intent\\` — why this exists\n - \\`requirements[]\\` — what to build, with \\`scenarios[].lines\\` verbatim\n - \\`archRequirements[]\\` — the architectural obligations (outbox, retries, alerts),\n same shape; their scenarios become integration/ops tests, and \\`covers\\` names\n the model objects each exercises\n - \\`architecture\\` — whether this service is new, and the calls in and out of it\n - \\`api\\` — the endpoints this feature adds or retires for this service: path,\n method, operationId and summary, with removals spelled \\`REMOVE <METHOD> <path>\\`\n - \\`openapi\\` — whether that contract could be read at all: \\`unreadable\\`, plus an\n \\`error\\` when the parser gave a message. It rides beside \\`api\\` rather than\n inside it because \\`api\\` stays the operations array it has always been, and it\n is what tells an empty \\`api\\` apart from a contract delta that did not parse\n - \\`services\\` — every service this feature projects onto, so a run without\n \\`--service\\` tells you which ones to ask for rather than guessing\n Exit 1 with \\`ok: true\\` means one of the two authored documents behind this brief\n did not parse: \\`architecture.errors\\` non-empty (delta.likec4), or\n \\`openapi.unreadable\\` true (the feature's openapi.yaml for this service). The empty\n slice is the parse failure itself, not \"nothing changes there\". Stop and fix that\n document before building anything — for the delta, \\`loam validate $1 --json\\` names\n the error; the contract delta is not graded by validate, so read the parser message\n the payload carries.\n3. In the service's repo, \\`loam gherkin $1 --json\\` — one \\`.feature\\` file per changed\n requirement lands under \\`<gherkinDir>/loam/\\` (default \\`features/loam/\\`), scenarios\n digest-stamped, arch requirements tagged \\`@architecture\\`. Those files ARE the\n acceptance criteria. Never edit them: regeneration rewrites the directory, and\n \\`loam validate\\` reports the suite stale by digest, not by your intentions.\n4. Write step definitions for the generated scenarios FIRST — outside \\`loam/\\`.\n Do not paraphrase a scenario into something easier to pass; it is the acceptance\n criterion someone else reviews against.\n Run \\`loam steps --service $2 --json\\` before you write any: it collapses every\n written step onto the phrase a definition actually matches, so you see the size\n of the registry you owe and which phrases already exist. Write ONE definition per\n phrase row. If \\`nearDuplicates\\` is non-empty, two rows differ by an article or a\n trailing \\`because …\\` clause and one definition was meant — fix the SPEC's wording\n and regenerate, rather than writing the second definition.\n A step's argument arrives as an argument: a \\`\"\"\"\\` docstring is the payload, an\n indented table is a DataTable. Assert against them; do not re-parse the step text.\n5. Implement until the suite passes — run it with a JSON report\n (\\`cucumber-js --format json:report.json\\`):\n \\`loam verify $1 --service $2 --results report.json\\` consumes that report as the\n done-check's answer sheet. Record from THIS repo, with \\`--service\\` — the\n \\`--service\\`-less \\`--record\\` form writes the whole record from one place and is\n refused (\\`record-federated\\`) once another service has attested.\n6. Honour the contract: every operation in \\`architecture.inbound\\` must exist under\n exactly that operationId, and every one in \\`architecture.outbound\\` must be called.\n7. If building made you change the feature's documents at all — a requirement's\n wording, an operation in its openapi.yaml — **\\`loam rebase $1\\`** before you\n validate. Every edit you make in the docs repo while other features are in\n flight is written against a living document that may have moved since the\n feature directory was created, and the pins are the only thing that lets the\n merge tell an EDIT from a QUOTE — for operations, path-level keys and\n components alike. \\`delta.baseline-missing\\` and\n \\`openapi.baseline-missing\\` are the warnings that say you skipped it — archive\n refuses on them until you rebase (or a human \\`--approve\\`s the unpinned merge);\n \\`delta.baseline-stale\\` / \\`openapi.baseline-stale\\` are archive refusing because\n somebody landed a change underneath you — re-read theirs, fold in what you\n still mean, then rebase again. Re-pinning without re-reading is how you\n overwrite someone else's requirement with loam's blessing.\n8. \\`loam validate --feature $1 --json\\` before handing back.\n\nIf the requirement is ambiguous, say so and stop — do not invent behaviour and\nleave the spec disagreeing with the code.\n`,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"implement.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/implement.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,sGAAsG;IACxG,YAAY,EAAE,qBAAqB;IACnC,OAAO,EACL,oMAAoM;IACtM,UAAU,EAAE,wCAAwC;IACpD,YAAY,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC;IACpC,KAAK,EAAE;QACL,mFAAmF;QACnF,8FAA8F;QAC9F,0GAA0G;QAC1G,kIAAkI;QAClI,6FAA6F;QAC7F,2FAA2F;QAC3F,iEAAiE;QACjE,iGAAiG;QACjG,qGAAqG;KACtG;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmJP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/**\n * Building one service's slice: the delta brief, then a unit at a time —\n * generated gherkin, steps, code, verify.\n *\n * Steps 3-5 are a LOOP, not a sequence, and that is why `loam tasks` sits at\n * the head of them. They used to be three things done in order — generate the\n * suite, write the definitions, implement — which is only the right order for a\n * slice whose every requirement already owns a scenario. Where one does not,\n * the next thing owed is a sentence in the docs repo, and the body had no step\n * that could say so; where the suite is half written, nothing here answered\n * which half. `at` answers both: the projection names which of four states the\n * next unit is in, and each state names exactly one action, so the numbering\n * below is a table of destinations rather than an itinerary. An editor adding a\n * step must therefore say which `at` value routes to it, or it is unreachable.\n */\nexport const LOAM_IMPLEMENT: CommandContent = {\n name: \"loam-implement\",\n description:\n \"Implement a loam feature's slice for one service — generated gherkin first, tests from it, then code\",\n argumentHint: \"<FEAT-id> [service]\",\n purpose:\n \"Build one service's part of a feature, in that service's own repository. The generated Gherkin is the acceptance criterion — it comes before the step definitions, and those come before the code.\",\n invocation: \"loam instructions loam-implement $1 $2\",\n placeholders: [\"feature\", \"service\"],\n spine: [\n \"`loam status` — where this feature actually is, and what is owed before you start\",\n \"`loam context` — the briefing: the service's living slice, and every delta in flight over it\",\n \"`loam delta` — the task: the intent, every requirement verbatim, the endpoints, and the calls in and out\",\n \"`loam tasks` — the open units, ordered; `--next` is the single next unit, with the scenario verbatim and the fact that closes it\",\n \"`loam gherkin` in the service's repo — the digest-stamped `.feature` files. Never edit them\",\n \"write step definitions for the generated scenarios first, outside the generated directory\",\n \"implement until the suite passes, running it with a JSON report\",\n \"honour the contract in both directions: expose every inbound operation, call every outbound one\",\n \"`loam rebase` if building made you change the feature's documents at all, then validate the feature\",\n ],\n body: `Implement one service's part of a feature. Every step runs in the SERVICE'S own\nrepository, which needs its own committed ./loam.json — if there is none,\n\\`loam init --docs <path-to-docs-repo> --service $2 --agent-profile service\\` then\n\\`loam doctor\\` first.\n\n0. \\`loam status $1 --json\\` — where this feature actually is. Run it before\n anything else if you joined halfway or lost the session: it says which\n artifacts are still owed, whether another feature has to archive first, and\n what the next step is, each \\`next[]\\` entry carrying the literal command. It\n writes nothing, so it is always safe to re-run.\n1. \\`loam context $2 --feature $1 --json\\` — the briefing to load before touching\n code: the service's living requirements and arch requirements verbatim, its\n operations and messages as they stand today, the permissions and capabilities\n its requirements join to, the fleet edges around it, and this feature's delta\n over it (\\`--feature $1\\` narrows the in-flight section; drop it to see every\n feature working over this service). The delta below is WHAT CHANGES; the pack\n is what it changes INTO — read it first so a MODIFIED requirement lands as an\n edit rather than a rewrite, and an operation you are about to add is caught\n when the living contract already defines it. Exit 1 with \\`ok: true\\` means a\n document behind the pack did not parse; the empty section beside the flag is\n the parse failure, not \"nothing here\".\n2. \\`loam delta $1 --service $2 --json\\` (drop \\`--service\\` to use the service configured\n in ./loam.json). That output IS the task:\n - \\`intent\\` — why this exists\n - \\`requirements[]\\` — what to build, with \\`scenarios[].lines\\` verbatim\n - \\`archRequirements[]\\` — the architectural obligations (outbox, retries, alerts),\n same shape; their scenarios become integration/ops tests, and \\`covers\\` names\n the model objects each exercises\n - \\`architecture\\` — whether this service is new, and the calls in and out of it\n - \\`api\\` — the endpoints this feature adds or retires for this service: path,\n method, operationId and summary, with removals spelled \\`REMOVE <METHOD> <path>\\`\n - \\`openapi\\` — whether that contract could be read at all: \\`unreadable\\`, plus an\n \\`error\\` when the parser gave a message. It rides beside \\`api\\` rather than\n inside it because \\`api\\` stays the operations array it has always been, and it\n is what tells an empty \\`api\\` apart from a contract delta that did not parse\n - \\`services\\` — every service this feature projects onto, so a run without\n \\`--service\\` tells you which ones to ask for rather than guessing\n Exit 1 with \\`ok: true\\` means one of the two authored documents behind this brief\n did not parse: \\`architecture.errors\\` non-empty (delta.likec4), or\n \\`openapi.unreadable\\` true (the feature's openapi.yaml for this service). The empty\n slice is the parse failure itself, not \"nothing changes there\". Stop and fix that\n document before building anything — for the delta, \\`loam validate $1 --json\\` names\n the error; the contract delta is not graded by validate, so read the parser message\n the payload carries.\n3. \\`loam tasks $1 --service $2 --next --json\\` — ONE unit of work, and the loop\n every step below runs inside. Step 2 is the whole brief; this is the next thing\n to do in it. It writes nothing: the projection is derived from the verify\n checklist, the delta scans, the feature's verification record and — in this,\n the bound service repo — the digest stamps in the generated \\`.feature\\` files.\n Drop \\`--next\\` to see every open unit instead of only the head.\n Each entry of \\`units[]\\` carries its \\`id\\` (the same id the verify checklist uses\n for that claim), \\`kind\\` (\\`service.exists\\`, \\`api.exposes\\`, \\`event.declares\\`,\n \\`c4.calls\\`, \\`scenario.tested\\` or \\`requirement.described\\`), \\`subject\\`, \\`claim\\`,\n \\`digest\\`, \\`dependsOn\\`, \\`source\\` — the path, line, requirement and scenario it was\n derived from, with the scenario's own \\`lines\\` verbatim — and \\`done.check\\`.\n\n \\`at\\` is the instruction. It says what the unit NEEDS, and each of its four\n values names exactly one action:\n - \\`describe\\` — the requirement owns no scenarios yet. Write the Given/When/Then\n in the DOCS repo, on the requirement \\`source\\` names, and nothing else. loam\n does not author, and no amount of code closes a unit sitting here.\n - \\`generate\\` — no stamped \\`.feature\\` carries this scenario's digest. The only\n action is \\`loam gherkin $1\\` in this repo (step 4). Do not write code.\n - \\`implement\\` — the stamped scenario exists and no recorded run has confirmed\n it. Write step definitions outside \\`loam/\\`, implement until the suite passes,\n then \\`loam verify $1 --service $2 --results report.json\\` (step 5).\n - \\`attested\\` — the claim was closed on somebody's word rather than on a run.\n Run the suite and re-record it so it stops resting on an assertion. An\n agent-attested scenario stays visible at \\`attested\\` forever, deliberately: it\n is the one state that cannot be made to go away by ignoring it.\n\n \\`done.check\\` is the FACT that closes the unit, and the RUNNER decides whether\n that fact holds — never your own reading of the code you just wrote. Done means\n the unit no longer appears, and it stops appearing only once its claim is\n recorded confirmed in a verification record that is not stale. So re-run\n \\`loam tasks $1 --service $2 --json\\` after every recording instead of working\n down a list you copied out: \\`frontier\\` (the first ten open unit ids, in order)\n and \\`counts\\` (\\`open\\`, \\`done\\`, \\`attested\\`) are recomputed from the documents on\n every run, which is what makes taking the head of it always safe.\n \\`checklist\\` is the digest of the question set the whole projection derives from.\n If it differs from the one you started against, the question set moved under\n you — re-read the feature before continuing, because what you closed earlier may\n not be what is being asked now.\n \\`record\\` says whether the answers were found at all (\\`absent\\`, \\`recorded\\`,\n \\`stale\\`, \\`unreadable\\`) and \\`stamps\\` whether this repo's generated suite could be\n read (\\`read\\`, \\`unknown\\`); an \\`unknown\\` makes every \\`generate\\` verdict a guess,\n so fix the binding before acting on one. \\`contracts\\` restates step 6 as data\n (\\`expose[]\\` and \\`call[]\\`), \\`size\\` the shape of the packet, \\`review[]\\` what a\n reviewer of it will read, and \\`waitingOn\\` — \\`--service\\` runs only — what another\n service still owes before your frontier can move.\n Exit 1 with \\`ok: true\\` is step 2's rule again: delta.likec4 or one of the\n contract axes did not parse, so the projection is SHORT by everything that\n document would have contributed. Fix the document; do not work the units that\n happened to survive it.\n4. \\`at: \"generate\"\\` — in the service's repo, \\`loam gherkin $1 --json\\`. One\n \\`.feature\\` file per changed requirement lands under \\`<gherkinDir>/loam/\\`\n (default \\`features/loam/\\`), scenarios digest-stamped, arch requirements tagged\n \\`@architecture\\`. Those files ARE the acceptance criteria. Never edit them:\n regeneration rewrites the directory, and \\`loam validate\\` reports the suite stale\n by digest, not by your intentions. Generating is the WHOLE action for a unit at\n \\`generate\\`: go back to step 3, where the same unit now reads \\`implement\\`.\n5. \\`at: \"implement\"\\` — step definitions FIRST, outside \\`loam/\\`, then the code.\n Do not paraphrase a scenario into something easier to pass; it is the acceptance\n criterion someone else reviews against.\n Run \\`loam steps --service $2 --json\\` before you write any: it collapses every\n written step onto the phrase a definition actually matches, so you see the size\n of the registry you owe and which phrases already exist. Write ONE definition per\n phrase row. If \\`nearDuplicates\\` is non-empty, two rows differ by an article or a\n trailing \\`because …\\` clause and one definition was meant — fix the SPEC's wording\n and regenerate, rather than writing the second definition.\n A step's argument arrives as an argument: a \\`\"\"\"\\` docstring is the payload, an\n indented table is a DataTable. Assert against them; do not re-parse the step text.\n Then implement until the suite passes — run it with a JSON report\n (\\`cucumber-js --format json:report.json\\`; cucumber-jvm, behave, Reqnroll,\n pytest-bdd \\`--cucumberjson\\` and godog write the same cucumber JSON):\n \\`loam verify $1 --service $2 --results report.json\\` consumes that report as the\n done-check's answer sheet. A runner with no cucumber formatter answers through\n loam's runner-neutral shape instead, keyed by each scenario's \\`@loam-digest\\`\n tag — adapting the runner's report into it is a build step you own:\n \\`{\"loamScenarioReport\": 1, \"results\": [{\"digest\": \"<16 hex>\", \"status\": \"passed\"}]}\\`.\n A cucumber run must have run every step of the stamped scenario: a report\n entry whose step count differs is not that scenario's run and stays\n unconfirmed. Record from THIS repo, with \\`--service\\` — the\n \\`--service\\`-less \\`--record\\` form writes the whole record from one place and is\n refused (\\`record-federated\\`) once another service has attested. Recording is what\n closes the unit, so step 3 is the command AFTER it, not the end of the run.\n6. Honour the contract: every operation in \\`architecture.inbound\\` must exist under\n exactly that operationId, and every one in \\`architecture.outbound\\` must be called.\n7. If building made you change the feature's documents at all — a requirement's\n wording, an operation in its openapi.yaml — **\\`loam rebase $1\\`** before you\n validate. Every edit you make in the docs repo while other features are in\n flight is written against a living document that may have moved since the\n feature directory was created, and the pins are the only thing that lets the\n merge tell an EDIT from a QUOTE — for operations, path-level keys and\n components alike. \\`delta.baseline-missing\\` and\n \\`openapi.baseline-missing\\` are the warnings that say you skipped it — archive\n refuses on them until you rebase (or a human \\`--approve\\`s the unpinned merge);\n \\`delta.baseline-stale\\` / \\`openapi.baseline-stale\\` are archive refusing because\n somebody landed a change underneath you — re-read theirs, fold in what you\n still mean, then rebase again. Re-pinning without re-reading is how you\n overwrite someone else's requirement with loam's blessing.\n8. \\`loam validate --feature $1 --json\\` before handing back.\n\nIf the requirement is ambiguous, say so and stop — do not invent behaviour and\nleave the spec disagreeing with the code. Write the question under\n\\`## Open questions\\` in the feature's \\`intent.md\\`, where loam-feature's analyze\nstep put the ones it found, so a person answers it once for every service.\n`,\n};\n"]}
|
|
@@ -161,7 +161,9 @@ the forgery — editing a spec after it was vouched used to leave \`status: veri
|
|
|
161
161
|
standing over words nobody read. \`loam validate\` recomputes it wherever it can
|
|
162
162
|
read the doc (no service repo needed, so it fires from the docs repo too) and
|
|
163
163
|
reports \`content.stale\` (warn): only a person can say whether verified still
|
|
164
|
-
holds of the new words.
|
|
164
|
+
holds of the new words. The fleet readouts say the same: \`loam list\` shows
|
|
165
|
+
\`vouched (stale)\` and owes the row a re-vouch, and \`loam status\` emits
|
|
166
|
+
\`next.revouch\` — a step for a person, never for you.
|
|
165
167
|
|
|
166
168
|
\`vouch_scope\` appears only when the person vouched after reading a SAMPLE of the
|
|
167
169
|
document — \`loam vouch --sample <n>\`, whose whole purpose is that a partial read
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authoring.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/authoring.ts"],"names":[],"mappings":"AAgBA,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,qGAAqG;IACvG,6EAA6E;IAC7E,+CAA+C;IAC/C,YAAY,EAAE,EAAE;IAChB,OAAO,EACL,0MAA0M;IAC5M,UAAU,EAAE,kCAAkC;IAC9C,YAAY,EAAE,EAAE;IAChB,KAAK,EAAE;QACL,4BAA4B;QAC5B,6BAA6B;QAC7B,mDAAmD;KACpD;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgMP;CACA,CAAC","sourcesContent":["/**\n * The `loam-authoring` reference page: the grammars of the documents you write.\n *\n * The architecture spec axis (`arch.spec.md` and its `Covers:` line), the\n * generated Gherkin suite, and the frontmatter/vouch chain that ties a\n * document to the code it was written from.\n *\n * All three are consulted at a moment you can name — while authoring one file\n * — and none of them is needed to form the question. That is the split this\n * page exists to make; ./reference.ts records why it had to be made at all.\n *\n * The text is the three AGENTS.md sections verbatim, in their original order,\n * so a cross-reference elsewhere in the corpus still names the same headings.\n */\nimport type { CommandContent } from \"../../contract.js\";\n\nexport const LOAM_AUTHORING: CommandContent = {\n name: \"loam-authoring\",\n description:\n \"Reference: the grammars you author against — arch.spec.md, the generated Gherkin suite, frontmatter\",\n // No arguments: one document, printed whole. See ./spine.ts for why the hint\n // is empty rather than a spelled-out \"<none>\".\n argumentHint: \"\",\n purpose:\n \"What each document you author by hand must contain: the architecture requirements axis and its `Covers:` line, how scenarios become an executable suite, and the frontmatter fields `loam vouch` stamps.\",\n invocation: \"loam instructions loam-authoring\",\n placeholders: [],\n spine: [\n \"The architecture spec axis\",\n \"The generated Gherkin suite\",\n \"Frontmatter — who vouched for this, and from what\",\n ],\n body: `The grammars you author against: the architecture spec axis, the generated Gherkin suite, and frontmatter.\n\n## The architecture spec axis\n\nThe business spec will never mention the transactional outbox — that is\narchitecture. Retries, idempotency, metrics, alerts: real obligations no business\nscenario states, and exactly where generated code cuts corners unless the\nobligations are derived mechanically. They live in \\`arch.spec.md\\` — living and\ndelta, same grammar, same delta algebra, merged by \\`loam archive\\` through the\nsame code path (the two files are separate requirement namespaces).\n\nWhere a business requirement carries \\`Operations:\\`, an arch requirement carries\n\\`Covers:\\` — the model objects its scenarios exercise, comma-separated:\n\n\\`\\`\\`\nCovers: paymentService.db, paymentService -> kafka, alert:payment_5xx, sli:availability\n\\`\\`\\`\n\na C4 element (id, or the service a bound element stands for), an edge\n(\\`source -> target\\`), or a health signal the service's health.yaml declares.\nEvery entry must resolve — \\`covers.unknown\\` (warn) is the typo guard, because a\nmistyped entry silently costs the coverage it was written for.\n\nCoverage is then derived, never trusted, as two warnings: a feature's NEW tagged\nelements and edges want a covering requirement in its arch.spec.md deltas\n(\\`c4.uncovered\\`), and every alert/SLI health.yaml declares wants one in the\nliving arch spec (\\`health.uncovered\\`). None of this gates \\`loam archive\\`;\n\\`--strict\\` is the CI escalation. An absent arch.spec.md is not a finding —\npartial adoption is supported — but write one for any service with real\narchitecture, or its obligations ship unchecked.\n\n**Effective configuration and dependency semantics live here too, by\nconvention.** Facts about the service that no artifact structurally holds —\nthe value a deploy chart actually sets, the retry budget a client library\nhardcodes (\"retries: -1 is unbounded\"), the delivery or idempotency guarantee\na driver decides — are ARCH REQUIREMENTS: prose with a \\`Covers:\\` line naming\nthe element or edge whose behaviour the fact decides. loam will never verify\nthe values; naming the home is the point. A service whose timeout comes from\nan env var in a chart in another repository is otherwise described by no file\nin this repo, and two adoptions will file the same fact two different ways.\n\n**Test levels, mapped once.** A business scenario is an acceptance test. An arch\nscenario is an integration/ops test — the outbox relay under a dead broker, the\nretry that stays idempotent, the alert rule that actually fires. \\`api.exposes\\`\nis a contract test. Unit tests sit below spec granularity and are your TDD\nconcern, not the specs'.\n\n## The generated Gherkin suite\n\nScenarios are the acceptance criteria, and \\`loam gherkin\\` makes them\nexecutable: real \\`.feature\\` files, written into the SERVICE'S repo — the one\nloam command that writes there, because tests live with the code they gate.\nOutput lands in \\`<gherkinDir>/loam/\\` (\\`gherkinDir\\` is an optional loam.json\nfact, default \\`features\\`), and that \\`loam/\\` subdirectory is loam's own\nderived space — and a committed one: an emission lands through the same lock\nand journal the docs repo's writers use, as \\`loam/.loam-lock\\` and\n\\`loam/.loam-commit\\` dotfiles that exist only while a run is in flight (or\nafter a kill, where the journal is what lets the next run finish the job).\nRegeneration rewrites its scope and deletes its orphans, so\nnever hand-edit inside it — step definitions and hand-written features belong\noutside it, and loam never touches a byte outside \\`loam/\\`.\n\n- \\`loam gherkin FEAT-101\\` — the feature's ADDED/MODIFIED requirements for this\n service, both spec axes. Files carry \\`@FEAT-101\\`; arch-axis files add\n \\`@architecture\\` and are named \\`arch--<slug>.feature\\` — their scenarios are\n integration/ops tests, not acceptance tests.\n- \\`loam gherkin --service <id>\\` (no feature) — the full living suite from\n spec.md + arch.spec.md: the regression skeleton a legacy service gets at\n adoption.\n\nOne file per requirement (\\`Feature:\\` is the requirement name). Bullet lines\nopening with Given/When/Then/And/But — the \\`- **WHEN** ...\\` convention\nincluded — become steps; every other body line is kept as scenario\ndescription. A valid Markdown table AT THE MARGIN becomes the \\`Examples:\\` of\na \\`Scenario Outline\\`; a malformed table stays in the description and is\nreported, because it will run once rather than once per row.\n\nA step takes an ARGUMENT by carrying it INDENTED underneath: an indented table\nbecomes that step's data table (the rows an outbox or a DB table must hold after\nthe pass), a fenced block becomes its docstring, indentation intact (the request\npayload). Indented under a step means this step's rows; at the margin means the\nscenario's cases. A block no step precedes stays description and is reported as\n\\`strandedBlocks\\` — the document reads, and the TEST lost its argument. Each scenario is\ntagged \\`@loam-digest-<16hex>\\`: the same body\nhash \\`loam verify\\` folds into its claim ids, riding into cucumber's JSON\nreport as a tag, so the suite, the claim and the report cannot quietly\ndisagree about what a scenario says. \\`loam validate\n--service <id>\\`, run in the service repo, grades the suite by those digests\n(\\`gherkin.missing\\` / \\`gherkin.stale\\` / \\`gherkin.orphaned\\`, all warn) — the\nfix is always regeneration, never editing a generated file.\n\nThe flow, closed end to end: \\`loam gherkin FEAT-101\\` → \\`loam steps --service\n<id>\\` (the phrase inventory: how many step definitions this suite needs, one\nper phrase row, and which phrases differ only by an article or a trailing\nclause) → write step definitions (outside \\`loam/\\`) → run the suite with a JSON report\n(\\`cucumber-js --format json:report.json\\`) → implement until green →\n\\`loam verify FEAT-101 --service <id> --results report.json [--contract-results contract.json] [--record rest.json]\\`,\nrun in that same service repo. The digest\ntags ride through the runner into the report, so the \\`scenario.tested\\` claims\nare answered mechanically from the passing report rather than anybody's word.\nThis proves which digest-matched report bytes loam accepted; it does not prove\nthe report was produced by executing the attested commit. See\n\\`loam instructions loam-done-check\\` for what \\`--record\\` still covers.\n\n## Frontmatter — who vouched for this, and from what\n\nEvery markdown artifact opens with a YAML block:\n\n\\`\\`\\`yaml\n---\nservice: payment-service # or feature: FEAT-101\nstatus: verified # services: draft -> verified\n # features: proposed -> in_progress -> built -> done\nowner: payments-team\nlast_verified: 2026-07-31 # written by \\`loam vouch\\` — do not hand-edit\nsources: # files/directories in the SERVICE'S repo this was written from — no globs\n - src/main/java/com/shop/payment/\nsources_digest: 6f1c0a… # written by \\`loam vouch\\` — do not hand-edit\ncontent_digest: 9b2f41… # written by \\`loam vouch\\` — do not hand-edit\nsources_files: | # written by \\`loam vouch\\` — do not hand-edit\n 9b2f41… src/main/java/com/shop/payment/Api.java\nvouch_scope: sampled 2/9 seed=1a2b3c4d5e6f7089 # only when the vouch read a SAMPLE\n---\n\\`\\`\\`\n\n\\`sources\\` matters more than it looks. Everything else loam checks is internal\nconsistency — and a corpus can agree with itself perfectly while describing nothing\nthat exists. \\`sources\\` is the one thing tying a document to code: \\`loam validate\\`,\nrun inside that service's repository, checks every listed path is still there.\nEntries are literal files and directories only — a directory covers everything\nbeneath it. Glob patterns are refused loudly (\\`loam vouch\\` will not stamp them,\n\\`loam validate\\` errors on them): a pattern dialect nobody can be sure of digests\na different file set than intended, corrupting the staleness signal silently.\n\n\\`sources_digest\\` is what makes that tie say something over time: a hash of the\nCONTENT of those files, taken when a human last vouched for the document. Every\nlater \\`loam validate\\` re-computes it, so it can tell a document nobody has checked\n(\\`sources.unvouched\\`) from one that still matches the code (\\`sources.current\\`) from\none the code has moved out from under (\\`sources.stale\\`). On \\`sources.stale\\` or\n\\`content.stale\\`, \\`loam vouch --pack --service <id>\\` (read-only, stamps nothing)\nprints exactly what to re-read before the human re-vouches — the body's diff since\nthe last vouch, the source files that moved, and the sections already covered.\n\n\\`content_digest\\` is the same promise about the document itself: a hash of the\nbody below the frontmatter, stamped by the same vouch. It closes the other half of\nthe forgery — editing a spec after it was vouched used to leave \\`status: verified\\`\nstanding over words nobody read. \\`loam validate\\` recomputes it wherever it can\nread the doc (no service repo needed, so it fires from the docs repo too) and\nreports \\`content.stale\\` (warn): only a person can say whether verified still\nholds of the new words.\n\n\\`vouch_scope\\` appears only when the person vouched after reading a SAMPLE of the\ndocument — \\`loam vouch --sample <n>\\`, whose whole purpose is that a partial read\nis recorded as a partial read. Its value is one flat string,\n\\`sampled <k>/<n> seed=<16 hex>\\`: k sections read of n, and the seed those k were\nchosen with. The seed is \\`sha256(<service> NUL <content_digest> NUL <sources_digest>)\\`\ntruncated to 16 hex characters, and the sections are ranked by\n\\`sha256(<seed> NUL <index> NUL <heading>)\\` with the lowest k taken, over every H2\nand H3 heading of the body outside code fences — so anybody holding the document\ncan recompute exactly which sections that person was shown, and an agent cannot\nsteer the pick without rewriting the document (which changes the seed and voids the\nstamp). \\`status\\` stays \\`verified\\` and the maturity rung stays \\`vouched\\`: the scope\nqualifies that claim, it is not a fourth status. \\`loam validate\\` reports\n\\`sources.sampled-vouch\\` (warn) while it is there, \\`loam list\\` shows\n\\`vouched (sampled)\\`, and a later FULL \\`loam vouch\\` deletes the field. Never write\nor delete it by hand: removing it turns a partial read into a full one on every\nsurface at once, which is the one forgery this field exists to make impossible.\n\nWrite \\`sources\\` for anything you author from reading code, and leave \\`status: draft\\`\nuntil a human has read it. Promoting draft to verified is their call, not yours, and\nit has its own command — \\`loam vouch --service <id>\\`, run inside that service's repo,\nwhich stamps the status, the date, the identity (\\`vouched_by\\`, from git), both digests\nand the per-file index\n(\\`sources_files\\`, which is what lets a later \\`sources.stale\\` name the paths that\nmoved) together — into the living spec.md\nand, when the service has one, arch.spec.md, in one all-or-nothing run: a file whose\nsources cannot be verified refuses the whole vouch. Never write those six fields by\nhand: a status with no digest behind it is a claim with nothing behind it. If the\ndocument changed under vouch between the read and the write, it refuses with\n\\`vouch-raced\\` and stamps nothing — re-read and re-run.\n\n**\\`loam vouch\\` is not yours to run**, and it is built so that it cannot be. It is\nthe one command whose output is a claim about a HUMAN act — everything else loam\nchecks is internal consistency, which fluent prose satisfies on its own — so it\nrefuses \\`vouch-unattended\\` when stdin is not a terminal (and in \\`--json\\` mode,\nwhere a question cannot be asked), answers \\`vouch-declined\\` when the person says\nno, and refuses \\`vouch-unattributable\\` when git can name nobody, because a stamp\nwith no \\`vouched_by\\` behind it records only that the word was written. \\`--yes\\`\nexists for a person's own scripted run; typing it on someone's behalf is\nwriting their name. The generated command and skill files pre-approve loam's\nread-only and authoring verbs one by one and deliberately not this one: when\nthe work is done, hand back and say a vouch is owed.\n`,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"authoring.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/authoring.ts"],"names":[],"mappings":"AAgBA,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,qGAAqG;IACvG,6EAA6E;IAC7E,+CAA+C;IAC/C,YAAY,EAAE,EAAE;IAChB,OAAO,EACL,0MAA0M;IAC5M,UAAU,EAAE,kCAAkC;IAC9C,YAAY,EAAE,EAAE;IAChB,KAAK,EAAE;QACL,4BAA4B;QAC5B,6BAA6B;QAC7B,mDAAmD;KACpD;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkMP;CACA,CAAC","sourcesContent":["/**\n * The `loam-authoring` reference page: the grammars of the documents you write.\n *\n * The architecture spec axis (`arch.spec.md` and its `Covers:` line), the\n * generated Gherkin suite, and the frontmatter/vouch chain that ties a\n * document to the code it was written from.\n *\n * All three are consulted at a moment you can name — while authoring one file\n * — and none of them is needed to form the question. That is the split this\n * page exists to make; ./reference.ts records why it had to be made at all.\n *\n * The text is the three AGENTS.md sections verbatim, in their original order,\n * so a cross-reference elsewhere in the corpus still names the same headings.\n */\nimport type { CommandContent } from \"../../contract.js\";\n\nexport const LOAM_AUTHORING: CommandContent = {\n name: \"loam-authoring\",\n description:\n \"Reference: the grammars you author against — arch.spec.md, the generated Gherkin suite, frontmatter\",\n // No arguments: one document, printed whole. See ./spine.ts for why the hint\n // is empty rather than a spelled-out \"<none>\".\n argumentHint: \"\",\n purpose:\n \"What each document you author by hand must contain: the architecture requirements axis and its `Covers:` line, how scenarios become an executable suite, and the frontmatter fields `loam vouch` stamps.\",\n invocation: \"loam instructions loam-authoring\",\n placeholders: [],\n spine: [\n \"The architecture spec axis\",\n \"The generated Gherkin suite\",\n \"Frontmatter — who vouched for this, and from what\",\n ],\n body: `The grammars you author against: the architecture spec axis, the generated Gherkin suite, and frontmatter.\n\n## The architecture spec axis\n\nThe business spec will never mention the transactional outbox — that is\narchitecture. Retries, idempotency, metrics, alerts: real obligations no business\nscenario states, and exactly where generated code cuts corners unless the\nobligations are derived mechanically. They live in \\`arch.spec.md\\` — living and\ndelta, same grammar, same delta algebra, merged by \\`loam archive\\` through the\nsame code path (the two files are separate requirement namespaces).\n\nWhere a business requirement carries \\`Operations:\\`, an arch requirement carries\n\\`Covers:\\` — the model objects its scenarios exercise, comma-separated:\n\n\\`\\`\\`\nCovers: paymentService.db, paymentService -> kafka, alert:payment_5xx, sli:availability\n\\`\\`\\`\n\na C4 element (id, or the service a bound element stands for), an edge\n(\\`source -> target\\`), or a health signal the service's health.yaml declares.\nEvery entry must resolve — \\`covers.unknown\\` (warn) is the typo guard, because a\nmistyped entry silently costs the coverage it was written for.\n\nCoverage is then derived, never trusted, as two warnings: a feature's NEW tagged\nelements and edges want a covering requirement in its arch.spec.md deltas\n(\\`c4.uncovered\\`), and every alert/SLI health.yaml declares wants one in the\nliving arch spec (\\`health.uncovered\\`). None of this gates \\`loam archive\\`;\n\\`--strict\\` is the CI escalation. An absent arch.spec.md is not a finding —\npartial adoption is supported — but write one for any service with real\narchitecture, or its obligations ship unchecked.\n\n**Effective configuration and dependency semantics live here too, by\nconvention.** Facts about the service that no artifact structurally holds —\nthe value a deploy chart actually sets, the retry budget a client library\nhardcodes (\"retries: -1 is unbounded\"), the delivery or idempotency guarantee\na driver decides — are ARCH REQUIREMENTS: prose with a \\`Covers:\\` line naming\nthe element or edge whose behaviour the fact decides. loam will never verify\nthe values; naming the home is the point. A service whose timeout comes from\nan env var in a chart in another repository is otherwise described by no file\nin this repo, and two adoptions will file the same fact two different ways.\n\n**Test levels, mapped once.** A business scenario is an acceptance test. An arch\nscenario is an integration/ops test — the outbox relay under a dead broker, the\nretry that stays idempotent, the alert rule that actually fires. \\`api.exposes\\`\nis a contract test. Unit tests sit below spec granularity and are your TDD\nconcern, not the specs'.\n\n## The generated Gherkin suite\n\nScenarios are the acceptance criteria, and \\`loam gherkin\\` makes them\nexecutable: real \\`.feature\\` files, written into the SERVICE'S repo — the one\nloam command that writes there, because tests live with the code they gate.\nOutput lands in \\`<gherkinDir>/loam/\\` (\\`gherkinDir\\` is an optional loam.json\nfact, default \\`features\\`), and that \\`loam/\\` subdirectory is loam's own\nderived space — and a committed one: an emission lands through the same lock\nand journal the docs repo's writers use, as \\`loam/.loam-lock\\` and\n\\`loam/.loam-commit\\` dotfiles that exist only while a run is in flight (or\nafter a kill, where the journal is what lets the next run finish the job).\nRegeneration rewrites its scope and deletes its orphans, so\nnever hand-edit inside it — step definitions and hand-written features belong\noutside it, and loam never touches a byte outside \\`loam/\\`.\n\n- \\`loam gherkin FEAT-101\\` — the feature's ADDED/MODIFIED requirements for this\n service, both spec axes. Files carry \\`@FEAT-101\\`; arch-axis files add\n \\`@architecture\\` and are named \\`arch--<slug>.feature\\` — their scenarios are\n integration/ops tests, not acceptance tests.\n- \\`loam gherkin --service <id>\\` (no feature) — the full living suite from\n spec.md + arch.spec.md: the regression skeleton a legacy service gets at\n adoption.\n\nOne file per requirement (\\`Feature:\\` is the requirement name). Bullet lines\nopening with Given/When/Then/And/But — the \\`- **WHEN** ...\\` convention\nincluded — become steps; every other body line is kept as scenario\ndescription. A valid Markdown table AT THE MARGIN becomes the \\`Examples:\\` of\na \\`Scenario Outline\\`; a malformed table stays in the description and is\nreported, because it will run once rather than once per row.\n\nA step takes an ARGUMENT by carrying it INDENTED underneath: an indented table\nbecomes that step's data table (the rows an outbox or a DB table must hold after\nthe pass), a fenced block becomes its docstring, indentation intact (the request\npayload). Indented under a step means this step's rows; at the margin means the\nscenario's cases. A block no step precedes stays description and is reported as\n\\`strandedBlocks\\` — the document reads, and the TEST lost its argument. Each scenario is\ntagged \\`@loam-digest-<16hex>\\`: the same body\nhash \\`loam verify\\` folds into its claim ids, riding into cucumber's JSON\nreport as a tag, so the suite, the claim and the report cannot quietly\ndisagree about what a scenario says. \\`loam validate\n--service <id>\\`, run in the service repo, grades the suite by those digests\n(\\`gherkin.missing\\` / \\`gherkin.stale\\` / \\`gherkin.orphaned\\`, all warn) — the\nfix is always regeneration, never editing a generated file.\n\nThe flow, closed end to end: \\`loam gherkin FEAT-101\\` → \\`loam steps --service\n<id>\\` (the phrase inventory: how many step definitions this suite needs, one\nper phrase row, and which phrases differ only by an article or a trailing\nclause) → write step definitions (outside \\`loam/\\`) → run the suite with a JSON report\n(\\`cucumber-js --format json:report.json\\`) → implement until green →\n\\`loam verify FEAT-101 --service <id> --results report.json [--contract-results contract.json] [--record rest.json]\\`,\nrun in that same service repo. The digest\ntags ride through the runner into the report, so the \\`scenario.tested\\` claims\nare answered mechanically from the passing report rather than anybody's word.\nThis proves which digest-matched report bytes loam accepted; it does not prove\nthe report was produced by executing the attested commit. See\n\\`loam instructions loam-done-check\\` for what \\`--record\\` still covers.\n\n## Frontmatter — who vouched for this, and from what\n\nEvery markdown artifact opens with a YAML block:\n\n\\`\\`\\`yaml\n---\nservice: payment-service # or feature: FEAT-101\nstatus: verified # services: draft -> verified\n # features: proposed -> in_progress -> built -> done\nowner: payments-team\nlast_verified: 2026-07-31 # written by \\`loam vouch\\` — do not hand-edit\nsources: # files/directories in the SERVICE'S repo this was written from — no globs\n - src/main/java/com/shop/payment/\nsources_digest: 6f1c0a… # written by \\`loam vouch\\` — do not hand-edit\ncontent_digest: 9b2f41… # written by \\`loam vouch\\` — do not hand-edit\nsources_files: | # written by \\`loam vouch\\` — do not hand-edit\n 9b2f41… src/main/java/com/shop/payment/Api.java\nvouch_scope: sampled 2/9 seed=1a2b3c4d5e6f7089 # only when the vouch read a SAMPLE\n---\n\\`\\`\\`\n\n\\`sources\\` matters more than it looks. Everything else loam checks is internal\nconsistency — and a corpus can agree with itself perfectly while describing nothing\nthat exists. \\`sources\\` is the one thing tying a document to code: \\`loam validate\\`,\nrun inside that service's repository, checks every listed path is still there.\nEntries are literal files and directories only — a directory covers everything\nbeneath it. Glob patterns are refused loudly (\\`loam vouch\\` will not stamp them,\n\\`loam validate\\` errors on them): a pattern dialect nobody can be sure of digests\na different file set than intended, corrupting the staleness signal silently.\n\n\\`sources_digest\\` is what makes that tie say something over time: a hash of the\nCONTENT of those files, taken when a human last vouched for the document. Every\nlater \\`loam validate\\` re-computes it, so it can tell a document nobody has checked\n(\\`sources.unvouched\\`) from one that still matches the code (\\`sources.current\\`) from\none the code has moved out from under (\\`sources.stale\\`). On \\`sources.stale\\` or\n\\`content.stale\\`, \\`loam vouch --pack --service <id>\\` (read-only, stamps nothing)\nprints exactly what to re-read before the human re-vouches — the body's diff since\nthe last vouch, the source files that moved, and the sections already covered.\n\n\\`content_digest\\` is the same promise about the document itself: a hash of the\nbody below the frontmatter, stamped by the same vouch. It closes the other half of\nthe forgery — editing a spec after it was vouched used to leave \\`status: verified\\`\nstanding over words nobody read. \\`loam validate\\` recomputes it wherever it can\nread the doc (no service repo needed, so it fires from the docs repo too) and\nreports \\`content.stale\\` (warn): only a person can say whether verified still\nholds of the new words. The fleet readouts say the same: \\`loam list\\` shows\n\\`vouched (stale)\\` and owes the row a re-vouch, and \\`loam status\\` emits\n\\`next.revouch\\` — a step for a person, never for you.\n\n\\`vouch_scope\\` appears only when the person vouched after reading a SAMPLE of the\ndocument — \\`loam vouch --sample <n>\\`, whose whole purpose is that a partial read\nis recorded as a partial read. Its value is one flat string,\n\\`sampled <k>/<n> seed=<16 hex>\\`: k sections read of n, and the seed those k were\nchosen with. The seed is \\`sha256(<service> NUL <content_digest> NUL <sources_digest>)\\`\ntruncated to 16 hex characters, and the sections are ranked by\n\\`sha256(<seed> NUL <index> NUL <heading>)\\` with the lowest k taken, over every H2\nand H3 heading of the body outside code fences — so anybody holding the document\ncan recompute exactly which sections that person was shown, and an agent cannot\nsteer the pick without rewriting the document (which changes the seed and voids the\nstamp). \\`status\\` stays \\`verified\\` and the maturity rung stays \\`vouched\\`: the scope\nqualifies that claim, it is not a fourth status. \\`loam validate\\` reports\n\\`sources.sampled-vouch\\` (warn) while it is there, \\`loam list\\` shows\n\\`vouched (sampled)\\`, and a later FULL \\`loam vouch\\` deletes the field. Never write\nor delete it by hand: removing it turns a partial read into a full one on every\nsurface at once, which is the one forgery this field exists to make impossible.\n\nWrite \\`sources\\` for anything you author from reading code, and leave \\`status: draft\\`\nuntil a human has read it. Promoting draft to verified is their call, not yours, and\nit has its own command — \\`loam vouch --service <id>\\`, run inside that service's repo,\nwhich stamps the status, the date, the identity (\\`vouched_by\\`, from git), both digests\nand the per-file index\n(\\`sources_files\\`, which is what lets a later \\`sources.stale\\` name the paths that\nmoved) together — into the living spec.md\nand, when the service has one, arch.spec.md, in one all-or-nothing run: a file whose\nsources cannot be verified refuses the whole vouch. Never write those six fields by\nhand: a status with no digest behind it is a claim with nothing behind it. If the\ndocument changed under vouch between the read and the write, it refuses with\n\\`vouch-raced\\` and stamps nothing — re-read and re-run.\n\n**\\`loam vouch\\` is not yours to run**, and it is built so that it cannot be. It is\nthe one command whose output is a claim about a HUMAN act — everything else loam\nchecks is internal consistency, which fluent prose satisfies on its own — so it\nrefuses \\`vouch-unattended\\` when stdin is not a terminal (and in \\`--json\\` mode,\nwhere a question cannot be asked), answers \\`vouch-declined\\` when the person says\nno, and refuses \\`vouch-unattributable\\` when git can name nobody, because a stamp\nwith no \\`vouched_by\\` behind it records only that the word was written. \\`--yes\\`\nexists for a person's own scripted run; typing it on someone's behalf is\nwriting their name. The generated command and skill files pre-approve loam's\nread-only and authoring verbs one by one and deliberately not this one: when\nthe work is done, hand back and say a vouch is owed.\n`,\n};\n"]}
|
|
@@ -14,14 +14,16 @@
|
|
|
14
14
|
* written — every byte comes from ../../agents-md/, which is where the sections
|
|
15
15
|
* already lived and where they stayed. The parts still carry their own trailing
|
|
16
16
|
* newlines and are joined with no separator, so the printed page is the old
|
|
17
|
-
* section byte for byte.
|
|
17
|
+
* section byte for byte. That is the assembly contract every part's header
|
|
18
|
+
* points back at: each starts at the first character of its opening line and
|
|
19
|
+
* ends with the newline that closes its last one, or two parts glue onto one
|
|
20
|
+
* line on the printed page.
|
|
18
21
|
*
|
|
19
22
|
* WHAT IS AND IS NOT DUPLICATED. `## Reading loam's output` still exists in
|
|
20
|
-
* AGENTS.md (../../agents-md
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* below appears in exactly one place.
|
|
23
|
+
* AGENTS.md (../../agents-md.ts's ORIENTATION), and it is orientation, not a
|
|
24
|
+
* summary of this page: the envelope's keys, branching on codes rather than
|
|
25
|
+
* prose, severity versus gating, and a pointer here. The per-invocation
|
|
26
|
+
* inventory below appears in exactly one place.
|
|
25
27
|
*/
|
|
26
28
|
import { CODE_MAP } from "../../agents-md/command-map.js";
|
|
27
29
|
import { CONTEXT_COMMAND } from "../../agents-md/map/lenses/context.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codes.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/codes.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"codes.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAGvD,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,sEAAsE;IACnF,6EAA6E;IAC7E,+CAA+C;IAC/C,YAAY,EAAE,EAAE;IAChB,OAAO,EACL,iRAAiR;IACnR,UAAU,EAAE,8BAA8B;IAC1C,YAAY,EAAE,EAAE;IAChB,2EAA2E;IAC3E,4EAA4E;IAC5E,+CAA+C;IAC/C,KAAK,EAAE;QACL,wCAAwC;QACxC,qDAAqD;QACrD,8DAA8D;QAC9D,6DAA6D;KAC9D;IACD,0EAA0E;IAC1E,gDAAgD;IAChD,IAAI,EACF,QAAQ;QACR,eAAe;QACf,YAAY;QACZ,kBAAkB;QAClB,aAAa;QACb,YAAY;QACZ,eAAe;QACf,WAAW;QACX,QAAQ;CACX,CAAC","sourcesContent":["/**\n * The `loam-codes` reference page: which codes each INVOCATION can raise.\n *\n * The largest of the four pages and the one this migration is mostly about. As\n * a section of AGENTS.md it was 44,433 bytes — 41% of a 109,399-byte file that\n * Codex truncates at 32,768 and Windsurf at 12,000 characters, both silently.\n * It is also the half a reader consults at a moment it can NAME (a run reported\n * something) rather than the half it needs to form a question at all, which is\n * what decided which side of the split it went. ./reference.ts carries the rest\n * of that argument.\n *\n * This module does one thing: it CONCATENATES, in the order ../../agents-md.ts\n * used to. Nothing here reformats a part on the way past, and nothing here is\n * written — every byte comes from ../../agents-md/, which is where the sections\n * already lived and where they stayed. The parts still carry their own trailing\n * newlines and are joined with no separator, so the printed page is the old\n * section byte for byte. That is the assembly contract every part's header\n * points back at: each starts at the first character of its opening line and\n * ends with the newline that closes its last one, or two parts glue onto one\n * line on the printed page.\n *\n * WHAT IS AND IS NOT DUPLICATED. `## Reading loam's output` still exists in\n * AGENTS.md (../../agents-md.ts's ORIENTATION), and it is orientation, not a\n * summary of this page: the envelope's keys, branching on codes rather than\n * prose, severity versus gating, and a pointer here. The per-invocation\n * inventory below appears in exactly one place.\n */\nimport { CODE_MAP } from \"../../agents-md/command-map.js\";\nimport { CONTEXT_COMMAND } from \"../../agents-md/map/lenses/context.js\";\nimport { DIFF_COMMAND } from \"../../agents-md/map/lenses/diff.js\";\nimport { GATE_COMMAND } from \"../../agents-md/map/lenses/gate.js\";\nimport { EXPLAIN_COMMAND } from \"../../agents-md/map/explain.js\";\nimport { MCP_COMMAND } from \"../../agents-md/map/mcp.js\";\nimport { SUBSYSTEM_COMMANDS } from \"../../agents-md/map/subsystem.js\";\nimport { USECASE_VIEWS } from \"../../agents-md/map/usecases.js\";\nimport { REFUSALS } from \"../../agents-md/refusals.js\";\nimport type { CommandContent } from \"../../contract.js\";\n\nexport const LOAM_CODES: CommandContent = {\n name: \"loam-codes\",\n description: \"Reference: which codes each invocation can raise, command by command\",\n // No arguments: one document, printed whole. See ./spine.ts for why the hint\n // is empty rather than a spelled-out \"<none>\".\n argumentHint: \"\",\n purpose:\n \"The per-invocation inventory: which findings `validate --service` / `--feature` / `--all`, `status`, `doctor`, `gate`, `context`, `diff`, `explore`, `dependencies`, `rebase`, `seed`, `subsystem` and `mcp` can report, and the refusals the containment surface answers with.\",\n invocation: \"loam instructions loam-codes\",\n placeholders: [],\n // The page carries no `## ` headings — it is one list, and the bullets are\n // the structure. The spine names the invocations it walks instead, which is\n // what a reader is actually looking one of up.\n spine: [\n \"validate --service / --feature / --all\",\n \"status, doctor, rebase, dependencies, explore, seed\",\n \"context, gate, subsystem, use-case views, diff, explain, mcp\",\n \"the containment refusals and the OpenSpec migration surface\",\n ],\n // CODE_MAP already opens with the page's own \"**What this page is.**\" and\n // leads with `status`, the orientation command.\n body:\n CODE_MAP +\n CONTEXT_COMMAND +\n GATE_COMMAND +\n SUBSYSTEM_COMMANDS +\n USECASE_VIEWS +\n DIFF_COMMAND +\n EXPLAIN_COMMAND +\n MCP_COMMAND +\n REFUSALS,\n};\n"]}
|
|
@@ -30,9 +30,12 @@ so a JUnit or pytest fleet is not held at \`attested\` by a file format —
|
|
|
30
30
|
\`loam verify <FEAT> --results report.json\`. A claim is confirmed only when a
|
|
31
31
|
report scenario carrying its \`@loam-digest-<16hex>\` tag ran at least one step
|
|
32
32
|
and every step passed — every occurrence, when the report holds a re-run, and
|
|
33
|
-
every before/after hook with it
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
every before/after hook with it — and, in a cucumber report, ran exactly as
|
|
34
|
+
many steps as the stamped scenario has: a hand-tagged stub is not that
|
|
35
|
+
scenario's run. A failed, undefined, pending or skipped step, a failed hook, a
|
|
36
|
+
step count that differs, or no matching scenario at all, is \`unconfirmed\` with
|
|
37
|
+
the reason. A report that is not JSON (a JUnit XML file) is refused
|
|
38
|
+
\`answers-unreadable\`, naming both shapes. The digest is the only identity: a reworded spec scenario matches
|
|
36
39
|
nothing until the suite is regenerated and re-run. When a report exists,
|
|
37
40
|
ALWAYS pass \`--results\` — under it the runner owns every scenario claim, and
|
|
38
41
|
an answers-file entry for one is refused.
|
|
@@ -92,9 +95,12 @@ re-checks the pins and reports drift under the \`evidence.*\` findings; nothing
|
|
|
92
95
|
there moves a verdict either.
|
|
93
96
|
|
|
94
97
|
Two more codes belong to the record itself. \`verify.record-miscounted\` (error):
|
|
95
|
-
a
|
|
96
|
-
record is refused as unreadable rather than believed in either half, and
|
|
97
|
-
|
|
98
|
+
a record file whose \`summary\` block contradicts its own \`claims[]\` — the
|
|
99
|
+
record is refused as unreadable rather than believed in either half, and the
|
|
100
|
+
repair is never editing the counts: a service's own \`verification/<id>.yaml\`
|
|
101
|
+
is deleted and recorded again by that service, and the one-file
|
|
102
|
+
\`verification.yaml\` is restored from version control and then recorded over.
|
|
103
|
+
\`verify.digest-contested\`
|
|
98
104
|
(warn): two or more services word a scenario identically, so they share one
|
|
99
105
|
\`@loam-digest-…\` tag and no single cucumber report can say whose suite ran it —
|
|
100
106
|
those scenario claims are left unconfirmed, and each service records its own
|
|
@@ -113,7 +119,10 @@ services that all answered a since-rewritten delta agree with each other and are
|
|
|
113
119
|
every one of them stale. Differing \`docsCommit\` values are NOT a finding:
|
|
114
120
|
recording a week apart is normal for a feature in flight.
|
|
115
121
|
Normal mid-rollout and it gates nothing; re-record the services on the older
|
|
116
|
-
version from their own repositories
|
|
122
|
+
version from their own repositories. In a record kept as per-service files the
|
|
123
|
+
answers of the services it names do not count until they do: the record
|
|
124
|
+
answers the newest version its files answered, and an answer filed against an
|
|
125
|
+
older one could otherwise fill the slot of the question that replaced it.
|
|
117
126
|
Attestations written before the field existed carry no \`checklist\` and are not
|
|
118
127
|
counted as a third version — silence is not disagreement.
|
|
119
128
|
|
|
@@ -126,8 +135,9 @@ commit. It does not prove the file came from executing that commit; no digest
|
|
|
126
135
|
can.
|
|
127
136
|
|
|
128
137
|
Everything else you answer from the code, and \`--record answers.json\` takes
|
|
129
|
-
those back
|
|
130
|
-
|
|
138
|
+
those back — into \`features/<FEAT>/verification/<svc>.yaml\` with \`--service\`,
|
|
139
|
+
into the one file \`features/<FEAT>/verification.yaml\` without — and either
|
|
140
|
+
travels into the archive with the feature and reads without loam. Alongside the mechanical
|
|
131
141
|
flags, the answers file must answer exactly the claims no report owns: an
|
|
132
142
|
entry for a report-owned claim is refused (\`answers-mismatch\`), and a
|
|
133
143
|
mechanical flag alone is refused while claims outside its kind are
|
|
@@ -147,9 +157,16 @@ loam verify FEAT-101 --service payment-service --results report.json --contract-
|
|
|
147
157
|
\`\`\`
|
|
148
158
|
|
|
149
159
|
\`--service <id>\` narrows the checklist to that service's claims and binds the
|
|
150
|
-
answers to this repository's commit, producing
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
answers to this repository's commit, producing that service's **attestation**
|
|
161
|
+
as a file of its own, \`features/<FEAT>/verification/<id>.yaml\`. The directory
|
|
162
|
+
is the record: loam adds the files up when it reads, and the verdict and
|
|
163
|
+
counts are computed from them, never stored. Re-running it in the next repo
|
|
164
|
+
writes that repo's file and touches no other — which is why two teams'
|
|
165
|
+
attestations on two docs-repo branches merge cleanly, where one shared file
|
|
166
|
+
conflicted every time. A one-file federated \`verification.yaml\` written
|
|
167
|
+
before the directory is still read; a service that records again supersedes
|
|
168
|
+
its share of it, and the file goes once every service it names has its own.
|
|
169
|
+
Omitting
|
|
153
170
|
\`--service\` in a repo whose \`loam.json\` declares one substitutes it, so the
|
|
154
171
|
short form is safe there; a \`--service\` naming a service this repo does not
|
|
155
172
|
declare is refused (\`service-mismatch\`), and \`--service\` from a repo that
|
|
@@ -161,9 +178,14 @@ no \`--service\` writes the whole record from one place.** Over a record that
|
|
|
161
178
|
already carries other services' attestations it is refused outright
|
|
162
179
|
(\`record-federated\`, naming the attestors) rather than merged or migrated — the
|
|
163
180
|
alternative was one repo silently erasing nine others' evidence. Use it only for
|
|
164
|
-
a single-service feature that has never been recorded federated. A
|
|
165
|
-
|
|
166
|
-
never overwritten and never reported as "no
|
|
181
|
+
a single-service feature that has never been recorded federated. A record file
|
|
182
|
+
that exists but cannot be read — broken YAML, git conflict markers, a miscount
|
|
183
|
+
— is \`record-unreadable\`: it is never overwritten and never reported as "no
|
|
184
|
+
record", and the message names the file and the repair that is safe for it. A
|
|
185
|
+
service's own \`verification/<id>.yaml\` holds only that service's word, so that
|
|
186
|
+
service may delete it and record again; the one-file \`verification.yaml\` may
|
|
187
|
+
hold other services' attestations, so it is restored from version control
|
|
188
|
+
(after a merge, the copy on the branch you merged into) and never deleted.
|
|
167
189
|
|
|
168
190
|
Reading is not writing: \`loam verify <FEAT> --service <id>\` **without**
|
|
169
191
|
\`--record\` is a pure lens on the checklist. A read-only verify checklist needs no
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"done-check.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/done-check.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,MAAM,eAAe,GAAmB;IAC7C,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,4FAA4F;IAC9F,6EAA6E;IAC7E,+CAA+C;IAC/C,YAAY,EAAE,EAAE;IAChB,OAAO,EACL,mOAAmO;IACrO,UAAU,EAAE,mCAAmC;IAC/C,YAAY,EAAE,EAAE;IAChB,KAAK,EAAE,CAAC,gBAAgB,CAAC;IACzB,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+KP;CACA,CAAC","sourcesContent":["/**\n * The `loam-done-check` reference page: how `loam verify` derives its claims,\n * who may answer them, and what separates **verified** from **attested**.\n *\n * It is the longest single section the corpus had after the code inventory —\n * three answer channels, the federated recording form, the evidence pins and\n * the six codes the record itself can carry — and every word of it is read at\n * one moment in the cycle, step 7. AGENTS.md's own step 7 names this page.\n *\n * The text is the AGENTS.md section verbatim. See ./reference.ts for why the\n * four pages exist and what was traded for them.\n */\nimport type { CommandContent } from \"../../contract.js\";\n\nexport const LOAM_DONE_CHECK: CommandContent = {\n name: \"loam-done-check\",\n description:\n \"Reference: the done-check — how `loam verify` derives claims, and verified versus attested\",\n // No arguments: one document, printed whole. See ./spine.ts for why the hint\n // is empty rather than a spelled-out \"<none>\".\n argumentHint: \"\",\n purpose:\n \"What `loam verify` asks, which of the three channels may answer each kind of claim, how a cross-service feature is recorded from each service's own repository, and why an agent's word yields `attested` rather than `verified`.\",\n invocation: \"loam instructions loam-done-check\",\n placeholders: [],\n spine: [\"The done-check\"],\n body: `The done-check: what \\`loam verify\\` asks, who may answer it, and what the answer is worth.\n\n## The done-check\n\n\\`loam verify <FEAT>\\` derives a checklist from the feature's own artifacts —\none claim per new service, per operation the delta adds, per tagged edge that\nnames an operation, and per scenario of every changed requirement, arch.spec.md\ndeltas included (those claims name their file, and the test they ask for is an\nintegration/ops test, not an acceptance test). Each claim has a stable id, so\ntwo runs are diffable and an answer cannot drift onto a different question.\n\nThree answer channels, and they never overlap. The \\`scenario.tested\\` claims\nare the TEST RUNNER's to answer: run the generated suite and pass its report\nback — cucumber JSON (\\`cucumber-js --format json:report.json\\`), or loam's own\nrunner-neutral shape \\`{\"loamScenarioReport\": 1, \"results\": [{\"digest\": \"…\",\n\"status\": \"passed\"}]}\\` that any runner can be adapted into, chosen by its\nmarker key. The digest is the contract, never the dialect: both read\n\\`answered_by: runner\\` because both are a real run reporting that digest green,\nso a JUnit or pytest fleet is not held at \\`attested\\` by a file format —\n\\`loam verify <FEAT> --results report.json\\`. A claim is confirmed only when a\nreport scenario carrying its \\`@loam-digest-<16hex>\\` tag ran at least one step\nand every step passed — every occurrence, when the report holds a re-run, and\nevery before/after hook with it. A failed, undefined, pending or skipped step,\na failed hook, or no matching scenario at all, is \\`unconfirmed\\` with the\nreason. The digest is the only identity: a reworded spec scenario matches\nnothing until the suite is regenerated and re-run. When a report exists,\nALWAYS pass \\`--results\\` — under it the runner owns every scenario claim, and\nan answers-file entry for one is refused.\n\nThe \\`api.exposes\\` claims have a mechanical channel of their own: an API\ncontract-test run, passed back with\n\\`loam verify <FEAT> --contract-results contract.json\\`. loam accepts one\nshape — its own documented contract-results JSON,\n\\`{\"loamContractReport\": 1, \"results\": [{\"operationId\": \"createSplit\",\n\"status\": \"passed\"}]}\\` — emitted from your contract tool's report in one\ntransform (Specmatic's own coverage JSON is refused by design: its \"covered\"\nstatus means exercised, not passed, so reading it would confirm what a red run\nrefuted). An entry whose status is exactly \\`passed\\` confirms the claim whose\noperationId it names — the record says \\`answered_by: external-runner\\` and the\nviews mark the line \\`[contract]\\` — while a failed or unknown status and an\noperation the report never exercised leave the claim \\`unconfirmed\\` with the\nreason; entries naming operations outside the checklist are ignored.\n\\`verify.operation-contested\\` (warn) is \\`verify.digest-contested\\`'s twin on\nthis axis: an operationId is unique per contract document, not per fleet, so\nwhen two services on the checklist expose the same one, a report entry — which\nnames no service — cannot say whose suite exercised it, those claims stay\nunconfirmed, and each service records its own with \\`--service\\`. Under the\nflag the contract report OWNS every api.exposes claim in scope, so an\nanswers-file entry for one is refused (\\`answers-mismatch\\`), and the record\npins the file consumed in \\`contractReport\\` (path, sha256 of the bytes, mtime,\noperation count) exactly as \\`report\\` pins the cucumber one. The verdict\nladder does not move: external evidence never answers a \\`scenario.tested\\`\nclaim, so \\`attested\\` versus \\`verified\\` still turns on scenario claims\nalone.\n\n\\`--record\\` without \\`--results\\` is the fallback for a service with no runnable\nsuite yet: there your word answers the scenario claims too. It still works, and\nit costs something you must know about. The record says who answered\n(\\`answered_by: agent\\`), and that answer changes the VERDICT: a feature whose\nscenario claims rest on an agent's word is **attested**, not verified.\n\\`verify --json\\` reports \\`verdict: \"attested\"\\` with \\`verified: false\\` and an\n\\`attested\\` count, \\`loam status\\` marks it and adds a \\`next.verify-attested\\` step,\nand the notice rides in \\`notices[]\\` under \\`verify.scenario-attested\\` (warn —\nnothing gates on it, because a legacy service with no suite must still be able\nto ship). Answer them mechanically the moment a suite runs:\n\\`loam verify <FEAT> --results <report.json>\\`.\n\nA record that leaves any claim unconfirmed or unanswered carries\n\\`verify.claims-open\\` (warn) in \\`notices[]\\` — the one-line \"not a clean\nresult\" summary with all four counts, on the read view and the frozen\npost-archive view alike; it gates nothing, and a feature with no record at all\nnever carries it, because not-started is not partial.\n\nFederated \\`--record\\` also stamps \\`evidence_pins\\` on each agent-confirmed\ncitation — the cited file's sha256 at the attested commit, the cited line's\ntext, and the literal token the claim asserts (operationId, message name, edge\nop). One notice rides beside the stamp: \\`verify.evidence-token-missing\\` (warn,\nverdict-neutral, gates nothing) says a cited file at the attested commit does\nnot contain the claim's token — read that evidence first, while the answer can\nstill be corrected. \\`loam validate\\`, run later in the service's repo,\nre-checks the pins and reports drift under the \\`evidence.*\\` findings; nothing\nthere moves a verdict either.\n\nTwo more codes belong to the record itself. \\`verify.record-miscounted\\` (error):\na \\`verification.yaml\\` whose \\`summary\\` block contradicts its own \\`claims[]\\` — the\nrecord is refused as unreadable rather than believed in either half, and\nre-recording is the repair, never editing the counts. \\`verify.digest-contested\\`\n(warn): two or more services word a scenario identically, so they share one\n\\`@loam-digest-…\\` tag and no single cucumber report can say whose suite ran it —\nthose scenario claims are left unconfirmed, and each service records its own\nfrom its own repository with \\`--service\\`.\n\nA federated record also says WHAT EACH SERVICE ANSWERED AGAINST. Every\nattestation carries the \\`checklist\\` digest it answered and, when the docs repo\nis a git checkout, the \\`docsCommit\\` that checklist was derived from — so the\nrecord pins both sides, the service repo through \\`commit\\` and the question set\nthrough these two. \\`verify.checklist-forked\\` (warn) is what that buys: it names the\nservices whose attestation answers a DIFFERENT checklist than the feature now\nasks, which the record-level \\`checklist\\` field alone could never show — it\nflagged every service or none, and never said which answers went stale. It is\nmeasured against the present rather than pairwise between attestations, because\nservices that all answered a since-rewritten delta agree with each other and are\nevery one of them stale. Differing \\`docsCommit\\` values are NOT a finding:\nrecording a week apart is normal for a feature in flight.\nNormal mid-rollout and it gates nothing; re-record the services on the older\nversion from their own repositories, or accept the split knowingly.\nAttestations written before the field existed carry no \\`checklist\\` and are not\ncounted as a third version — silence is not disagreement.\n\nA consumed report is written down: \\`report\\` and \\`contractReport\\` on the\nrecord (or on that repository's attestation) each carry the path, a sha256 of\nthe bytes, the file's mtime and how much it held — tagged scenarios for the\none, distinct operationIds for the other. That identifies WHICH file answered\nthe claims, and — when the file is committed — that it matches the attested\ncommit. It does not prove the file came from executing that commit; no digest\ncan.\n\nEverything else you answer from the code, and \\`--record answers.json\\` takes\nthose back, writing \\`features/<FEAT>/verification.yaml\\` — it travels into the\narchive with the feature and reads without loam. Alongside the mechanical\nflags, the answers file must answer exactly the claims no report owns: an\nentry for a report-owned claim is refused (\\`answers-mismatch\\`), and a\nmechanical flag alone is refused while claims outside its kind are\noutstanding. Every recorded verdict says who answered it\n(\\`answered_by: runner | external-runner | agent\\`), so a reviewer can tell a\ngreen run from somebody's word.\n\n**A cross-service feature is verified once per service, and the form matters.**\nThe claims of one feature belong to several services, and each service's code\nlives in its own repository — so the recording form is federated:\n\n\\`\\`\\`sh\n# in EACH affected service's own repo, not in the docs repo:\nloam verify FEAT-101 --service payment-service --results report.json --record answers.json\n# with an API contract suite, its report answers the api.exposes claims too:\nloam verify FEAT-101 --service payment-service --results report.json --contract-results contract.json --record answers.json\n\\`\\`\\`\n\n\\`--service <id>\\` narrows the checklist to that service's claims and binds the\nanswers to this repository's commit, producing one **attestation** per service\ninside a single \\`schema: 2\\` \\`verification.yaml\\`. Re-running it in the next\nrepo adds that repo's attestation and leaves the others standing. Omitting\n\\`--service\\` in a repo whose \\`loam.json\\` declares one substitutes it, so the\nshort form is safe there; a \\`--service\\` naming a service this repo does not\ndeclare is refused (\\`service-mismatch\\`), and \\`--service\\` from a repo that\ndeclares none is refused (\\`repository-unavailable\\`) — an attestation has to be\nbound to the code it is about.\n\n⚠ **The legacy all-at-once form \\`loam verify <FEAT> --record answers.json\\` with\nno \\`--service\\` writes the whole record from one place.** Over a record that\nalready carries other services' attestations it is refused outright\n(\\`record-federated\\`, naming the attestors) rather than merged or migrated — the\nalternative was one repo silently erasing nine others' evidence. Use it only for\na single-service feature that has never been recorded federated. A\n\\`verification.yaml\\` that exists but cannot be read is \\`record-unreadable\\`: it is\nnever overwritten and never reported as \"no record\".\n\nReading is not writing: \\`loam verify <FEAT> --service <id>\\` **without**\n\\`--record\\` is a pure lens on the checklist. A read-only verify checklist needs no\nbinding and works from the docs repo, or from anywhere else — the refusals above\nare about WRITING an attestation, and only about that. Every run lists all\nsubjects in \\`services\\`, and a recorded run reports in \\`discarded\\` every previous\nanswer the new record does not carry.\n\nIt refuses an answer set that does not answer the current checklist: an id that\nis not on it (\\`answers-mismatch\\`), a claim with no answer (same), or a\n\\`confirmed\\` with no evidence (\\`answers-unevidenced\\`). Evidence is \\`file:line\\`.\nA claim you cannot show is \\`unconfirmed\\` with a note saying why — that is a\nsuccessful record, and it is more useful to the next reader than a yes.\n\n**Nothing gates on it.** \\`loam archive\\` will ship an unverified feature. That\nis deliberate: coherence is something loam computed from the documents, and a\nverdict is your word about code loam never read. A gate in front of shipping\nwould only teach everyone that the cheapest way past it is to say yes. The\nrecord is for the reviewer who comes later, so leave it true.\n`,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"done-check.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/done-check.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,MAAM,eAAe,GAAmB;IAC7C,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,4FAA4F;IAC9F,6EAA6E;IAC7E,+CAA+C;IAC/C,YAAY,EAAE,EAAE;IAChB,OAAO,EACL,mOAAmO;IACrO,UAAU,EAAE,mCAAmC;IAC/C,YAAY,EAAE,EAAE;IAChB,KAAK,EAAE,CAAC,gBAAgB,CAAC;IACzB,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqMP;CACA,CAAC","sourcesContent":["/**\n * The `loam-done-check` reference page: how `loam verify` derives its claims,\n * who may answer them, and what separates **verified** from **attested**.\n *\n * It is the longest single section the corpus had after the code inventory —\n * three answer channels, the federated recording form, the evidence pins and\n * the six codes the record itself can carry — and every word of it is read at\n * one moment in the cycle, step 7. AGENTS.md's own step 7 names this page.\n *\n * The text is the AGENTS.md section verbatim. See ./reference.ts for why the\n * four pages exist and what was traded for them.\n */\nimport type { CommandContent } from \"../../contract.js\";\n\nexport const LOAM_DONE_CHECK: CommandContent = {\n name: \"loam-done-check\",\n description:\n \"Reference: the done-check — how `loam verify` derives claims, and verified versus attested\",\n // No arguments: one document, printed whole. See ./spine.ts for why the hint\n // is empty rather than a spelled-out \"<none>\".\n argumentHint: \"\",\n purpose:\n \"What `loam verify` asks, which of the three channels may answer each kind of claim, how a cross-service feature is recorded from each service's own repository, and why an agent's word yields `attested` rather than `verified`.\",\n invocation: \"loam instructions loam-done-check\",\n placeholders: [],\n spine: [\"The done-check\"],\n body: `The done-check: what \\`loam verify\\` asks, who may answer it, and what the answer is worth.\n\n## The done-check\n\n\\`loam verify <FEAT>\\` derives a checklist from the feature's own artifacts —\none claim per new service, per operation the delta adds, per tagged edge that\nnames an operation, and per scenario of every changed requirement, arch.spec.md\ndeltas included (those claims name their file, and the test they ask for is an\nintegration/ops test, not an acceptance test). Each claim has a stable id, so\ntwo runs are diffable and an answer cannot drift onto a different question.\n\nThree answer channels, and they never overlap. The \\`scenario.tested\\` claims\nare the TEST RUNNER's to answer: run the generated suite and pass its report\nback — cucumber JSON (\\`cucumber-js --format json:report.json\\`), or loam's own\nrunner-neutral shape \\`{\"loamScenarioReport\": 1, \"results\": [{\"digest\": \"…\",\n\"status\": \"passed\"}]}\\` that any runner can be adapted into, chosen by its\nmarker key. The digest is the contract, never the dialect: both read\n\\`answered_by: runner\\` because both are a real run reporting that digest green,\nso a JUnit or pytest fleet is not held at \\`attested\\` by a file format —\n\\`loam verify <FEAT> --results report.json\\`. A claim is confirmed only when a\nreport scenario carrying its \\`@loam-digest-<16hex>\\` tag ran at least one step\nand every step passed — every occurrence, when the report holds a re-run, and\nevery before/after hook with it — and, in a cucumber report, ran exactly as\nmany steps as the stamped scenario has: a hand-tagged stub is not that\nscenario's run. A failed, undefined, pending or skipped step, a failed hook, a\nstep count that differs, or no matching scenario at all, is \\`unconfirmed\\` with\nthe reason. A report that is not JSON (a JUnit XML file) is refused\n\\`answers-unreadable\\`, naming both shapes. The digest is the only identity: a reworded spec scenario matches\nnothing until the suite is regenerated and re-run. When a report exists,\nALWAYS pass \\`--results\\` — under it the runner owns every scenario claim, and\nan answers-file entry for one is refused.\n\nThe \\`api.exposes\\` claims have a mechanical channel of their own: an API\ncontract-test run, passed back with\n\\`loam verify <FEAT> --contract-results contract.json\\`. loam accepts one\nshape — its own documented contract-results JSON,\n\\`{\"loamContractReport\": 1, \"results\": [{\"operationId\": \"createSplit\",\n\"status\": \"passed\"}]}\\` — emitted from your contract tool's report in one\ntransform (Specmatic's own coverage JSON is refused by design: its \"covered\"\nstatus means exercised, not passed, so reading it would confirm what a red run\nrefuted). An entry whose status is exactly \\`passed\\` confirms the claim whose\noperationId it names — the record says \\`answered_by: external-runner\\` and the\nviews mark the line \\`[contract]\\` — while a failed or unknown status and an\noperation the report never exercised leave the claim \\`unconfirmed\\` with the\nreason; entries naming operations outside the checklist are ignored.\n\\`verify.operation-contested\\` (warn) is \\`verify.digest-contested\\`'s twin on\nthis axis: an operationId is unique per contract document, not per fleet, so\nwhen two services on the checklist expose the same one, a report entry — which\nnames no service — cannot say whose suite exercised it, those claims stay\nunconfirmed, and each service records its own with \\`--service\\`. Under the\nflag the contract report OWNS every api.exposes claim in scope, so an\nanswers-file entry for one is refused (\\`answers-mismatch\\`), and the record\npins the file consumed in \\`contractReport\\` (path, sha256 of the bytes, mtime,\noperation count) exactly as \\`report\\` pins the cucumber one. The verdict\nladder does not move: external evidence never answers a \\`scenario.tested\\`\nclaim, so \\`attested\\` versus \\`verified\\` still turns on scenario claims\nalone.\n\n\\`--record\\` without \\`--results\\` is the fallback for a service with no runnable\nsuite yet: there your word answers the scenario claims too. It still works, and\nit costs something you must know about. The record says who answered\n(\\`answered_by: agent\\`), and that answer changes the VERDICT: a feature whose\nscenario claims rest on an agent's word is **attested**, not verified.\n\\`verify --json\\` reports \\`verdict: \"attested\"\\` with \\`verified: false\\` and an\n\\`attested\\` count, \\`loam status\\` marks it and adds a \\`next.verify-attested\\` step,\nand the notice rides in \\`notices[]\\` under \\`verify.scenario-attested\\` (warn —\nnothing gates on it, because a legacy service with no suite must still be able\nto ship). Answer them mechanically the moment a suite runs:\n\\`loam verify <FEAT> --results <report.json>\\`.\n\nA record that leaves any claim unconfirmed or unanswered carries\n\\`verify.claims-open\\` (warn) in \\`notices[]\\` — the one-line \"not a clean\nresult\" summary with all four counts, on the read view and the frozen\npost-archive view alike; it gates nothing, and a feature with no record at all\nnever carries it, because not-started is not partial.\n\nFederated \\`--record\\` also stamps \\`evidence_pins\\` on each agent-confirmed\ncitation — the cited file's sha256 at the attested commit, the cited line's\ntext, and the literal token the claim asserts (operationId, message name, edge\nop). One notice rides beside the stamp: \\`verify.evidence-token-missing\\` (warn,\nverdict-neutral, gates nothing) says a cited file at the attested commit does\nnot contain the claim's token — read that evidence first, while the answer can\nstill be corrected. \\`loam validate\\`, run later in the service's repo,\nre-checks the pins and reports drift under the \\`evidence.*\\` findings; nothing\nthere moves a verdict either.\n\nTwo more codes belong to the record itself. \\`verify.record-miscounted\\` (error):\na record file whose \\`summary\\` block contradicts its own \\`claims[]\\` — the\nrecord is refused as unreadable rather than believed in either half, and the\nrepair is never editing the counts: a service's own \\`verification/<id>.yaml\\`\nis deleted and recorded again by that service, and the one-file\n\\`verification.yaml\\` is restored from version control and then recorded over.\n\\`verify.digest-contested\\`\n(warn): two or more services word a scenario identically, so they share one\n\\`@loam-digest-…\\` tag and no single cucumber report can say whose suite ran it —\nthose scenario claims are left unconfirmed, and each service records its own\nfrom its own repository with \\`--service\\`.\n\nA federated record also says WHAT EACH SERVICE ANSWERED AGAINST. Every\nattestation carries the \\`checklist\\` digest it answered and, when the docs repo\nis a git checkout, the \\`docsCommit\\` that checklist was derived from — so the\nrecord pins both sides, the service repo through \\`commit\\` and the question set\nthrough these two. \\`verify.checklist-forked\\` (warn) is what that buys: it names the\nservices whose attestation answers a DIFFERENT checklist than the feature now\nasks, which the record-level \\`checklist\\` field alone could never show — it\nflagged every service or none, and never said which answers went stale. It is\nmeasured against the present rather than pairwise between attestations, because\nservices that all answered a since-rewritten delta agree with each other and are\nevery one of them stale. Differing \\`docsCommit\\` values are NOT a finding:\nrecording a week apart is normal for a feature in flight.\nNormal mid-rollout and it gates nothing; re-record the services on the older\nversion from their own repositories. In a record kept as per-service files the\nanswers of the services it names do not count until they do: the record\nanswers the newest version its files answered, and an answer filed against an\nolder one could otherwise fill the slot of the question that replaced it.\nAttestations written before the field existed carry no \\`checklist\\` and are not\ncounted as a third version — silence is not disagreement.\n\nA consumed report is written down: \\`report\\` and \\`contractReport\\` on the\nrecord (or on that repository's attestation) each carry the path, a sha256 of\nthe bytes, the file's mtime and how much it held — tagged scenarios for the\none, distinct operationIds for the other. That identifies WHICH file answered\nthe claims, and — when the file is committed — that it matches the attested\ncommit. It does not prove the file came from executing that commit; no digest\ncan.\n\nEverything else you answer from the code, and \\`--record answers.json\\` takes\nthose back — into \\`features/<FEAT>/verification/<svc>.yaml\\` with \\`--service\\`,\ninto the one file \\`features/<FEAT>/verification.yaml\\` without — and either\ntravels into the archive with the feature and reads without loam. Alongside the mechanical\nflags, the answers file must answer exactly the claims no report owns: an\nentry for a report-owned claim is refused (\\`answers-mismatch\\`), and a\nmechanical flag alone is refused while claims outside its kind are\noutstanding. Every recorded verdict says who answered it\n(\\`answered_by: runner | external-runner | agent\\`), so a reviewer can tell a\ngreen run from somebody's word.\n\n**A cross-service feature is verified once per service, and the form matters.**\nThe claims of one feature belong to several services, and each service's code\nlives in its own repository — so the recording form is federated:\n\n\\`\\`\\`sh\n# in EACH affected service's own repo, not in the docs repo:\nloam verify FEAT-101 --service payment-service --results report.json --record answers.json\n# with an API contract suite, its report answers the api.exposes claims too:\nloam verify FEAT-101 --service payment-service --results report.json --contract-results contract.json --record answers.json\n\\`\\`\\`\n\n\\`--service <id>\\` narrows the checklist to that service's claims and binds the\nanswers to this repository's commit, producing that service's **attestation**\nas a file of its own, \\`features/<FEAT>/verification/<id>.yaml\\`. The directory\nis the record: loam adds the files up when it reads, and the verdict and\ncounts are computed from them, never stored. Re-running it in the next repo\nwrites that repo's file and touches no other — which is why two teams'\nattestations on two docs-repo branches merge cleanly, where one shared file\nconflicted every time. A one-file federated \\`verification.yaml\\` written\nbefore the directory is still read; a service that records again supersedes\nits share of it, and the file goes once every service it names has its own.\nOmitting\n\\`--service\\` in a repo whose \\`loam.json\\` declares one substitutes it, so the\nshort form is safe there; a \\`--service\\` naming a service this repo does not\ndeclare is refused (\\`service-mismatch\\`), and \\`--service\\` from a repo that\ndeclares none is refused (\\`repository-unavailable\\`) — an attestation has to be\nbound to the code it is about.\n\n⚠ **The legacy all-at-once form \\`loam verify <FEAT> --record answers.json\\` with\nno \\`--service\\` writes the whole record from one place.** Over a record that\nalready carries other services' attestations it is refused outright\n(\\`record-federated\\`, naming the attestors) rather than merged or migrated — the\nalternative was one repo silently erasing nine others' evidence. Use it only for\na single-service feature that has never been recorded federated. A record file\nthat exists but cannot be read — broken YAML, git conflict markers, a miscount\n— is \\`record-unreadable\\`: it is never overwritten and never reported as \"no\nrecord\", and the message names the file and the repair that is safe for it. A\nservice's own \\`verification/<id>.yaml\\` holds only that service's word, so that\nservice may delete it and record again; the one-file \\`verification.yaml\\` may\nhold other services' attestations, so it is restored from version control\n(after a merge, the copy on the branch you merged into) and never deleted.\n\nReading is not writing: \\`loam verify <FEAT> --service <id>\\` **without**\n\\`--record\\` is a pure lens on the checklist. A read-only verify checklist needs no\nbinding and works from the docs repo, or from anywhere else — the refusals above\nare about WRITING an attestation, and only about that. Every run lists all\nsubjects in \\`services\\`, and a recorded run reports in \\`discarded\\` every previous\nanswer the new record does not carry.\n\nIt refuses an answer set that does not answer the current checklist: an id that\nis not on it (\\`answers-mismatch\\`), a claim with no answer (same), or a\n\\`confirmed\\` with no evidence (\\`answers-unevidenced\\`). Evidence is \\`file:line\\`.\nA claim you cannot show is \\`unconfirmed\\` with a note saying why — that is a\nsuccessful record, and it is more useful to the next reader than a yes.\n\n**Nothing gates on it.** \\`loam archive\\` will ship an unverified feature. That\nis deliberate: coherence is something loam computed from the documents, and a\nverdict is your word about code loam never read. A gate in front of shipping\nwould only teach everyone that the cheapest way past it is to say yes. The\nrecord is for the reviewer who comes later, so leave it true.\n`,\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reference.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/reference.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reference.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/reference.ts"],"names":[],"mappings":"AA0DA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAqB;IAC1C,UAAU;IACV,UAAU;IACV,cAAc;IACd,eAAe;CAChB,CAAC","sourcesContent":["/**\n * Why the reference pages exist, what decided which content became one, and the\n * list itself.\n *\n * THE MEASUREMENT. AGENTS.md is auto-loaded from the working directory by every\n * agents.md-aware host, on every session, and `loam init` writes it once and\n * never refreshes it. It had reached 109,399 bytes. Codex truncates the\n * AGENTS.md chain at 32,768 bytes and Windsurf caps a workspace rule file at\n * 12,000 characters — both silently, with no error and no way for the reader to\n * tell which half it was holding. Roughly seventy percent of the document was\n * being dropped on those two hosts, and which seventy percent depended on the\n * host rather than on what the reader needed.\n *\n * THE SPLIT RULE, which is not \"move the big parts\". Two kinds of content were\n * mixed in one file:\n *\n * - what a reader needs to FORM a question — the layout, the cycle, which\n * element is which service, what gates and what only advises, what the words\n * mean. Without it an agent cannot ask anything, so it cannot be behind a\n * command it does not know to run. That stays in AGENTS.md;\n * - what a reader CONSULTS at a moment it can name — the grammars for writing\n * one document, the inventory of what a command can report, the done-check's\n * channels. The moment is nameable (\"I am writing an arch.spec.md\", \"a run\n * reported something\"), so the page can be fetched then, and paying for it on\n * every invocation of every session bought nothing.\n *\n * That rule is why the binding rule — which element IS which service — stayed\n * in AGENTS.md while the rest of the ID spine left (it is ORIENTATION's\n * `## The identity rule` in ../../agents-md.ts now, condensed from the section\n * that used to stay behind), and why the per-invocation code inventory left\n * while the facts about the `--json` envelope stayed. Size followed from the\n * rule; it did not drive it.\n *\n * WHAT A PAGE COSTS AND WHAT IT BUYS. A page printed by the binary describes\n * the loam you are about to run, not the one that scaffolded the repository —\n * which is strictly better than a file `loam init` writes once and never\n * refreshes, and is why `agents.stale` exists at all. What it costs is\n * discoverability: a reference nobody can find is content deleted with extra\n * steps. Three things pay that back, and all three are asserted rather than\n * hoped for — AGENTS.md's own \"Reference pages\" section names each page\n * with the exact command (test/agents.test.ts), bare `loam instructions` lists\n * them beside the workflows (test/instructions.test.ts), and every stable code\n * that left is still in the corpus test/codes-drift.test.ts reads, because\n * {@link REFERENCES} feeds `PROTOCOLS`.\n *\n * WHAT A PAGE IS NOT. It is not a workflow. It has no steps, takes no\n * arguments, and `loam init` writes NO file for it — ../../protocol.ts feeds\n * this list to `PROTOCOLS` and `placeholderProblems` and to nothing else, so\n * `SLASH_COMMANDS` and `plannedCommandFiles` never see it and no repository\n * gains a generated artifact. Four pages are four more files in every repo loam\n * touches, and the whole point of the move was to stop shipping bytes nobody\n * asked for.\n *\n * THE MOVE WAS A MOVE. Every page's text is its AGENTS.md section verbatim.\n * Nothing was rewritten on the way, because a paraphrase is a second copy that\n * can drift — and removing one of those is what this whole change is for.\n */\nimport type { CommandContent } from \"../../contract.js\";\nimport { LOAM_AUTHORING } from \"./authoring.js\";\nimport { LOAM_CODES } from \"./codes.js\";\nimport { LOAM_DONE_CHECK } from \"./done-check.js\";\nimport { LOAM_SPINE } from \"./spine.js\";\n\n/**\n * The four pages, in the order AGENTS.md's own index names them — which is the\n * order their sections stood in the document they left, so a reader who knew\n * the file finds them where they were.\n */\nexport const REFERENCES: CommandContent[] = [\n LOAM_CODES,\n LOAM_SPINE,\n LOAM_AUTHORING,\n LOAM_DONE_CHECK,\n];\n"]}
|
|
@@ -199,16 +199,44 @@ the shape that reverts someone else's landed change at exit 0. A migrated
|
|
|
199
199
|
corpus is not stranded by this: \`loam rebase <FEAT>\` pins every delta in one
|
|
200
200
|
command, and a human's \`--approve\` archives unpinned when the loss is meant.
|
|
201
201
|
|
|
202
|
-
### The same pin on the contract axis
|
|
203
|
-
|
|
204
|
-
A feature's \`openapi.yaml\` is a
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
operation
|
|
210
|
-
|
|
211
|
-
|
|
202
|
+
### The same pin on the contract axis
|
|
203
|
+
|
|
204
|
+
A feature's \`openapi.yaml\` is a self-contained document holding ONLY what the
|
|
205
|
+
feature adds, changes or removes — not a copy of the living contract. The merge
|
|
206
|
+
walks only the paths and methods the delta spells: an operation you leave out
|
|
207
|
+
stays exactly as the living contract has it, a local \`$ref\`
|
|
208
|
+
(\`#/components/schemas/Order\`) resolves into the living document, and a new
|
|
209
|
+
operation has no living version to pin — an add-only delta needs no rebase.
|
|
210
|
+
|
|
211
|
+
What the merge does upsert is every operation the document DOES spell. So an
|
|
212
|
+
operation you quote without changing it — copied for context, or a whole
|
|
213
|
+
contract pasted in — is a collision waiting for someone else's edit: two
|
|
214
|
+
features over one service, each quoting the operation the other edits, destroy
|
|
215
|
+
each other, and the second to archive pushes its authoring-time copy back over
|
|
216
|
+
whatever landed in between. Quote nothing you do not change. Each quote costs a
|
|
217
|
+
pin, and a parallel feature that REMOVES the quoted operation makes it worse
|
|
218
|
+
either way: pinned, the quote is \`openapi.baseline-invalid\`, an error that
|
|
219
|
+
blocks your archive, and \`openapi.op-revived\` beside it; unpinned, it is
|
|
220
|
+
\`openapi.op-revived\` alone — a warning that gates, because the merge would
|
|
221
|
+
read the dead operation as a new one and put it back (\`asyncapi.slot-revived\`
|
|
222
|
+
is the same finding on a channel, operation or message slot). A parallel
|
|
223
|
+
feature that MOVES the operation to another address does the same to a quote
|
|
224
|
+
of the old one, which archived would answer the operationId at both. The fix
|
|
225
|
+
for both is to delete the quote. Keep it only when the feature brings a
|
|
226
|
+
retired operation back ON PURPOSE: then drop any pin, and a human archives
|
|
227
|
+
with \`--approve\`. \`loam rebase\` never pins a retired operation as new —
|
|
228
|
+
nothing lives at its slot, so there is no version to pin — and prints the same
|
|
229
|
+
finding (\`revived[]\` in \`--json\`), so the decision stays with you. It judges
|
|
230
|
+
the delta as it will write it, pins included: a quote of an address another
|
|
231
|
+
operation lives at now — one a move vacated, since re-occupied — IS repinned,
|
|
232
|
+
over the operation living there, which makes it an edit the merge would write;
|
|
233
|
+
the revival that edit makes is reported by that rebase (or its \`--dry-run\`),
|
|
234
|
+
not first by the next validate. The check reads the removal
|
|
235
|
+
markers of the features under \`features/archive/\` — a removal made by
|
|
236
|
+
hand-editing a living contract is invisible to it.
|
|
237
|
+
|
|
238
|
+
The guard against that collision, for every living operation the delta does
|
|
239
|
+
spell, is a pin — a vendor extension beside \`x-loam-remove\`:
|
|
212
240
|
|
|
213
241
|
\`\`\`yaml
|
|
214
242
|
paths:
|
|
@@ -218,8 +246,9 @@ paths:
|
|
|
218
246
|
x-loam-based-on: 86feabe621ed887d
|
|
219
247
|
\`\`\`
|
|
220
248
|
|
|
221
|
-
\`loam rebase\` writes one on
|
|
222
|
-
|
|
249
|
+
\`loam rebase\` writes one on every operation in the delta the living contract
|
|
250
|
+
already holds, pinned to the living version; what the feature adds gets none.
|
|
251
|
+
That is what lets the merge tell a QUOTE from an EDIT:
|
|
223
252
|
|
|
224
253
|
- pin equals the operation's own content → you quoted it → **the merge skips it
|
|
225
254
|
entirely** and the living contract keeps whatever it holds. Archive prints
|
|
@@ -228,12 +257,15 @@ version. That is what lets the merge tell a QUOTE from an EDIT:
|
|
|
228
257
|
- pin matches neither → you edited it AND somebody landed a change to it →
|
|
229
258
|
\`openapi.baseline-stale\`, refused.
|
|
230
259
|
|
|
231
|
-
|
|
232
|
-
\`servers\`) and
|
|
233
|
-
|
|
234
|
-
\`
|
|
235
|
-
|
|
236
|
-
|
|
260
|
+
The two surfaces an in-operation key cannot reach — path-level keys
|
|
261
|
+
(\`parameters\`, \`servers\`, \`summary\`, \`x-*\`) and every
|
|
262
|
+
\`components/<kind>/<name>\` — are pinned the same way through one root record,
|
|
263
|
+
\`x-loam-baselines\`, which \`loam rebase\` also writes and the merge strips: a
|
|
264
|
+
quoted key or component is skipped, an edited one is written and warned as
|
|
265
|
+
\`openapi.path-item-modified\` / \`openapi.component-modified\`. A new component
|
|
266
|
+
is copied in when something the merge writes references it; a living one you
|
|
267
|
+
only \`$ref\` needs no copy in the delta at all. Removal markers carry no pin;
|
|
268
|
+
\`openapi.remove-target-mismatch\` is what guards those.
|
|
237
269
|
|
|
238
270
|
## Drawing a shared broker
|
|
239
271
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spine.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/spine.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,mFAAmF;IAChG,0EAA0E;IAC1E,4EAA4E;IAC5E,yEAAyE;IACzE,YAAY,EAAE,EAAE;IAChB,OAAO,EACL,qNAAqN;IACvN,UAAU,EAAE,8BAA8B;IAC1C,YAAY,EAAE,EAAE;IAChB,KAAK,EAAE;QACL,cAAc;QACd,wCAAwC;QACxC,yBAAyB;QACzB,sCAAsC;KACvC;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4RP;CACA,CAAC","sourcesContent":["/**\n * The `loam-spine` reference page: how the artifacts JOIN.\n *\n * Operation and message ids, coverage and permission references, the\n * capability tree and its `Realizes:` join, the requirement baseline pins\n * (`Based-On:` / `x-loam-based-on`), how to draw a shared broker, and how to\n * declare a message produced outside the fleet.\n *\n * This is the grammar a reader consults while writing ONE document, which is\n * why it is a printed page rather than a section of AGENTS.md: it is consulted\n * at a moment you can name, and paying for it on every invocation of every\n * session bought nothing. `## Which element IS which service` deliberately\n * stayed behind in ../../agents-md/spine.ts — an agent cannot read the\n * landscape at all without it, so it is orientation rather than reference.\n *\n * The text is the AGENTS.md section verbatim; nothing was rewritten to move\n * it, so a cross-reference from the remaining file still names the same\n * headings. See ./reference.ts for why these four pages exist.\n */\nimport type { CommandContent } from \"../../contract.js\";\n\nexport const LOAM_SPINE: CommandContent = {\n name: \"loam-spine\",\n description: \"Reference: the ID spine — every join between the artifacts, and the baseline pins\",\n // A reference page takes no arguments: it is one document, printed whole.\n // The empty hint is what `loam instructions` prints beside the name, and an\n // invented one (\"<none>\") would read as an argument somebody could pass.\n argumentHint: \"\",\n purpose:\n \"The exact joins the artifacts are held together by — operationId, message name, `Covers:`, `Requires:`, `Capability:`/`Realizes:` — and the `Based-On:` pins that stop two features in flight reverting each other.\",\n invocation: \"loam instructions loam-spine\",\n placeholders: [],\n spine: [\n \"The ID spine\",\n \"The requirement baseline — `Based-On:`\",\n \"Drawing a shared broker\",\n \"A message produced outside the fleet\",\n ],\n body: `The ID spine: how the artifacts join, and what pins a change to the version it was written against.\n\n## The ID spine\n\nThe spine is a family of exact joins, not one magic id: feature tag; service\nbinding; synchronous operation; asynchronous message; architecture coverage;\nauthorization permission. Three artifacts describe the same synchronous call in\nthree languages. They are joined by the **operationId**, and that join is what\n\\`loam validate\\` checks:\n\n- architecture — a C4 edge names the operation it calls:\n \\`checkoutWeb -> paymentService 'Calls authorizePayment' { metadata { op 'authorizePayment' } }\\`\n- behaviour — a requirement declares the operations it governs:\n \\`Operations: authorizePayment\\`\n- contract — the provider's \\`openapi.yaml\\` defines it:\n \\`operationId: authorizePayment\\`\n\nSpell it identically in all three places. A mismatch is an error, not a warning:\nan edge calling an operation nobody defines is a broken contract between services.\n\nFeatures are joined the same way: a delta's new elements and edges carry the\nfeature id as a LikeC4 tag (\\`#FEAT-101\\`), and that tag is exactly what\n\\`loam archive\\` folds into the living landscape. Untagged elements in a delta are\ncontext for the diagram, not changes.\n\nThe other joins use the same discipline:\n\n- \\`metadata { publishes|consumes 'message' }\\` on an edge ↔\n \\`Publishes:\\`/\\`Consumes:\\` in a requirement ↔ the AsyncAPI 3 message name;\n- \\`Covers:\\` in an architecture requirement ↔ a C4 element/edge or\n \\`alert:<id>\\` / \\`sli:<id>\\` from health.yaml;\n- \\`Requires: <subject>/<permission>\\` in either requirement file ↔ the fleet\n declaration in \\`architecture/permissions.yaml\\`;\n- \\`Capability:\\` in either requirement file ↔ a declared id in\n \\`architecture/capabilities.yaml\\`. A LIST, comma-separated — the relation is\n many-to-many in both directions.\n\nThe permissions file is opt-in: a fleet with no \\`Requires:\\` line owes none.\nOnce a requirement names a permission, an undeclared pair is\n\\`permissions.unknown\\` (error). An unreadable vocabulary is\n\\`permissions.invalid\\`; a declaration nothing requires is\n\\`permissions.unenforced\\` (warn). The subject is what the permission is checked\nON (user, profile, service), not necessarily the caller. \\`owned_by\\` and\n\\`enforced_by\\` are explanatory and are not resolved against services; this is\na checked document join, not proof that the identity provider implements it.\n\nThe capability axis opts in the OTHER way: the FILES are the opt-in, not the\nline. A fleet with neither \\`architecture/capabilities.yaml\\` nor a\n\\`capabilities/\\` directory gets no capability findings at all, however many\n\\`Capability:\\` lines its requirements already carry.\n\nA capability may be declared on either side, and the vocabulary is the UNION.\nA name alone is a line in \\`architecture/capabilities.yaml\\` —\n\\`capabilities: {<id>: {description, owner}}\\`, nested ids such as\n\\`payments/refunds\\` kept as one flat key. A name with prose behind it gets a\ndocument instead: \\`capabilities/<id>/spec.md\\`, the AUTHORED business tree,\none directory per capability with nesting spelled by the tree\n(\\`capabilities/payments/refunds/spec.md\\`). The directory is the list — there\nis no manifest — and its existence is what opts the fleet in, so \\`loam init\\`\ndoes not scaffold it. A directory holding neither the document nor a\ncapability beneath it is \\`capability.doc-missing\\` (warn).\n\nThe document carries narrative and then \\`## Requirements\\`, in the same\ngrammar every spec.md uses, with two rules of its own. Every requirement needs\na \\`Requirement-ID:\\` — these documents outlive the services that realize them,\nso identity is the line and not the heading; without one it is\n\\`capability.requirement-unidentified\\` (error). And a capability requirement\nmust be observable OUTSIDE the fleet: \\`Operations:\\`, \\`Covers:\\`,\n\\`Publishes:\\` and \\`Consumes:\\` all resolve against one service's own contract,\nso carrying any of them is \\`capability.requirement-service-scoped\\` (error) —\nwrite it in that service's spec.md instead. Naming no service is an authoring\nrule that PR review holds, not one loam checks: matching service names in prose\nwould be a heuristic, and loam does not guess. The axis's own two joins are\nrefused here as well (\\`capability.requirement-inert-join\\`, error): they point\nINTO the tree, so nothing reads them written inside it.\n\nA FEATURE changes a capability document with a delta, not by editing it:\n\\`features/<FEAT>/capabilities/<id>/spec.md\\`, the same\n\\`## ADDED|MODIFIED|REMOVED Requirements\\` grammar the service deltas use, the\nsame nesting (\\`features/<FEAT>/capabilities/payments/refunds/\\`), the same\n\\`Based-On:\\` pins, and the same \\`delta.*\\` refusals with the capability id as\nthe subject. \\`scaffold.placeholder\\` reads it too, so an unedited template\nheading refuses the archive here as it does in a spec delta, and\n\\`capability.doc-missing\\` (warn) names a directory in it that holds no\n\\`spec.md\\` — an empty one is not a delta, so the merge would carry nothing.\n\\`loam archive\\` merges it into the living document in the same\ntransaction as the service merge, creating \\`capabilities/<id>/spec.md\\` — and\nso the whole tree — when this is the first feature to mention it; the heading\nis the id and the narrative above \\`## Requirements\\` is yours to write, because\nloam merges requirements and never prose. The document's three rules above are\ngraded on the delta BEFORE the merge, so a capability requirement filed at the\nwrong altitude is refused while you still hold it. Editing the living document\ndirectly is still legal; the delta is what makes two features touching one\npromise collide loudly instead of silently. \\`loam new <FEAT> --capability <id>\\`\nscaffolds that delta, and \\`loam show <FEAT>\\` and \\`loam delta <FEAT>\\` list the\npromises a feature changes — a capability delta names no service, so without\nthose two a business-only feature reads as a feature carrying nothing.\n\nA service requirement says which promise it serves with \\`Realizes:\\`, entries\nspelled \\`<capability-id>#<Requirement-ID>\\`. This is the join the tree exists\nfor, and it is NOT the same claim as \\`Capability:\\` beside it — that names a\ntheme, this names one promise, and a requirement commonly carries both. The\ncapability half of an entry is what makes the target addressable, since a\n\\`Requirement-ID\\` is unique only inside its own document; the separator is the\nLAST \\`#\\`, because the requirement half's grammar excludes one and the\ncapability half's does not. Written by whoever implements the requirement,\nnever by the analyst — a business document must not be edited every time the\nfleet rearranges which service carries which part.\n\nBoth directions are graded. An entry naming no capability requirement is\n\\`capability.realizes-unknown\\` (error; in a feature delta it gates\n\\`loam archive\\`, \\`--approve\\`-overridable), and its message says which of the\nfive failures happened — malformed entry, undeclared capability, declared but\nundocumented, document with no requirements, or an id that document does not\ndeclare. A capability requirement nothing keeps is\n\\`capability.requirement-unrealized\\` (warn, one per requirement): it never\ngates, because writing the business document ahead of the fleet is the intended\nuse. \\`loam list capabilities --json\\` carries the whole join — each capability's\nrequirements, and what realizes each one.\n\n\\`Realizes:\\` is not the only way to keep a promise, and for a criterion that\nCROSSES services it is not enough: \"I enter a login and a password and I am in\"\nbelongs to no single service's spec, because each promises only its own part. A\nUSE CASE can carry it, because it IS the hop sequence. So a \\`dynamic view\\`\nalready tagged \\`#cap-<slug>\\` may carry \\`#req-<slug>\\` as well, naming one of\nthat capability's requirements — the architect's answer to the analyst's\npromise, graded by the same four \\`usecase.*\\` codes down to the operation. The\nsecond tag is SCOPED by the first: a \\`Requirement-ID\\` is unique only inside its\nown document, so a \\`#req-\\` tag on a view that resolves no capability, or two, is\n\\`usecase.requirement-unresolved\\` (error) rather than a guess. Several \\`#req-\\`\ntags on one view are legal — a flow commonly keeps two promises. Both tags spell\ntheir id with every character outside \\`[A-Za-z0-9_-]\\` flattened to \\`-\\`,\nbecause that is all a LikeC4 tag name accepts.\n\nAgainst that vocabulary an undeclared name is \\`capability.unknown\\` (error; in\na feature delta it gates \\`loam archive\\`, \\`--approve\\`-overridable), an\nunreadable \\`capabilities.yaml\\` is \\`capability.invalid\\` exactly once per run\nwith every grade that resolves against the vocabulary suspended behind it, and\na declared capability no living requirement realizes is\n\\`capability.unrealized\\` (warn, one per capability). The fleet total is\nreadable: \\`loam list capabilities\\` reports each capability's realizing\nrequirements, their services and the draft/verified split, and\n\\`loam explore --capability <id>\\` seeds an exploration from the realizing\nservices.\n\n## The requirement baseline — \\`Based-On:\\`\n\nA MODIFIED requirement carries its FULL new text, not a diff, so archive does not\nmerge your wording into the living one — it REPLACES it. When two features rewrite\nthe same requirement, the loser loses everything they wrote, scenarios and all.\n\n\\`loam validate\\` names the other feature while both are in flight\n(\\`delta.modified-conflict\\`), but that warning cannot see the case that actually\nhappens: the first feature archives, stops being an active feature, the second\nrevalidates GREEN, and its archive lands text written against a document that no\nlonger exists — \\`+0 ~1 -0\\`, exit 0, nobody told.\n\nSo a MODIFIED or REMOVED requirement pins the living version it was written\nagainst, directly under its identity:\n\n\\`\\`\\`markdown\n### Requirement: Cancel an order\nRequirement-ID: REQ-042\nBased-On: 3bcc7a40f2b23ff3\n\\`\\`\\`\n\nYou do not compute that value — \\`loam rebase FEAT-101\\` writes it, for every\nMODIFIED/REMOVED requirement in the feature, from the living text as it stands\nright now. Run it when you finish authoring a delta, and again after you resolve\na collision. Archive refuses on a stale pin (\\`delta.baseline-stale\\`) and says\nboth digests.\n\n**Restamping is not resolving.** A pin claims \"I read this version\". If\n\\`loam rebase\\` reports a requirement as moved, re-read the living requirement and\nfold in what you still mean BEFORE you ship — otherwise you have re-pinned to a\ndocument you never read, and archive will merge your text over theirs with\nloam's blessing. There is no automatic three-way merge and there will not be one:\nrequirement prose does not merge, people do.\n\nA delta adopted from OpenSpec has no such line and gets a warning\n(\\`delta.baseline-missing\\`) — one that GATES the archive: unpinned is exactly\nthe shape that reverts someone else's landed change at exit 0. A migrated\ncorpus is not stranded by this: \\`loam rebase <FEAT>\\` pins every delta in one\ncommand, and a human's \\`--approve\\` archives unpinned when the loss is meant.\n\n### The same pin on the contract axis — and why it matters more there\n\nA feature's \\`openapi.yaml\\` is a COMPLETE document, not a patch: you restate the\nliving contract around the slot you are changing, and the merge upserts every\noperation the document spells. So the collision on this axis needs no overlap at\nall. Two features touching one service, editing DIFFERENT operations, destroy\neach other — the second to archive quietly pushes its authoring-time copy of the\noperation it never meant to touch back over whatever landed in between.\n\nThe same marker fixes it, as a vendor extension beside \\`x-loam-remove\\`:\n\n\\`\\`\\`yaml\npaths:\n /orders/{id}/cancel:\n post:\n operationId: cancelOrder\n x-loam-based-on: 86feabe621ed887d\n\\`\\`\\`\n\n\\`loam rebase\\` writes one on EVERY operation in the delta, pinned to the living\nversion. That is what lets the merge tell a QUOTE from an EDIT:\n\n- pin equals the operation's own content → you quoted it → **the merge skips it\n entirely** and the living contract keeps whatever it holds. Archive prints\n \\`· quotes …\\` so the plan never silently writes less than your delta spells.\n- pin equals the living operation → you edited it, nobody else did → merged.\n- pin matches neither → you edited it AND somebody landed a change to it →\n \\`openapi.baseline-stale\\`, refused.\n\nNot covered, deliberately, and worth knowing: path-level keys (\\`parameters\\`,\n\\`servers\\`) and \\`components/schemas\\` are still upserted wholesale, so a restated\nschema can still revert another feature's change to it —\n\\`openapi.path-item-modified\\` and \\`openapi.component-modified\\` are your only\nwarning there. Removal markers carry no pin either; \\`openapi.remove-target-mismatch\\`\nis what guards those.\n\n## Drawing a shared broker\n\nA broker drawn as ONE element is the node every service in the fleet points at,\nand any view over it is a star nobody can read. Nothing in loam catches this —\nloam parses the model and renders no view, so the map can be perfectly valid and\ncompletely illegible. Two habits keep it readable, and both are cheap only\nbefore the fleet is drawn:\n\n**Model the topic, not the broker.** Nest a topic per channel inside the broker's\nelement and point the edges at it — \\`paymentService -> kafka.paymentEvents\\`, not\n\\`paymentService -> kafka\\`. One hub of degree sixty becomes twelve of degree\nfive, and it is the truer model besides: what a producer and a consumer share is\nthe topic, never the broker. Every check joins the same way, because an edge into\na nested element resolves to the element that owns it. Declare the kind once —\n\n specification {\n element topic {\n style { shape queue }\n }\n }\n\nThe KIND is what does the work: since 2026-09-07 loam reads \\`database\\`, \\`queue\\`,\n\\`topic\\`, \\`cache\\` and \\`bucket\\` as infrastructure rather than as candidate\nservices, so a topic drawn this way is out of the service census and nobody is\nasked for a \\`services/payment.events/\\` (measured: the same element spelled\n\\`container\\` earns \\`landscape.service-undocumented\\`, spelled \\`topic\\` it earns\nnothing). This used to need \\`#external\\` ON THE KIND as a workaround, because\n**LikeC4 does not inherit tags** and a topic nested inside an \\`#external\\` broker\nis not external itself. That workaround is no longer owed. Tag the kind\n\\`#external\\` if you want the topics out of the fleet VIEW as well — that is what\nthe next paragraph's \\`exclude\\` reads, and it is a rendering choice now rather\nthan a way to silence a check.\n\n**Scope the views.** \\`exclude element.tag = #external\\` keeps the fleet overview to\nthe calls between our own services; a second view over the broker draws the event\nspine on its own. Views are LikeC4's and loam computes none, so this costs loam\nnothing and is worth doing anyway — an \\`include *\\` over the whole fleet map is\nalso the one view whose computation takes minutes rather than milliseconds. The\nscaffolded landscape applies the same pruning to ubiquitous infrastructure with\n\\`#platform\\`: the fleet view excludes it, and the platform view's\n\\`include * -> element.tag = #platform\\` (the relationship form — the obvious\nspelling draws boxes with no edges) keeps \"who depends on it\" answerable.\n\n## A message produced outside the fleet\n\nA platform service that is not ours — a config service, an auth broker —\nproduces messages our services consume, and no \\`services/<id>/\\` will ever hold\nits contract. Declare the production on the landscape, where the element\nalready says \\`#external\\`:\n\n externalConfig = softwareSystem 'external-config' {\n #external\n }\n externalConfig -> kafka.configTopic 'publishes config refreshes' {\n metadata { publishes 'config.ConfigRefreshed' }\n }\n\n\\`spine.message-unproduced\\` then stops firing — the message HAS a producer,\noutside the fleet — and the contract question moves to the one file that can\nanswer it: the consuming service's own asyncapi.yaml. While that file declares\nno shape for the message, \\`spine.message-external\\` (warn) says so; once the\nconsumer declares the payload, the axis is quiet. A truthful map and a green\nbuild no longer exclude each other, which is the exact choice this used to force.\n`,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"spine.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/spine.ts"],"names":[],"mappings":"AAsBA,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,mFAAmF;IAChG,0EAA0E;IAC1E,4EAA4E;IAC5E,yEAAyE;IACzE,YAAY,EAAE,EAAE;IAChB,OAAO,EACL,qNAAqN;IACvN,UAAU,EAAE,8BAA8B;IAC1C,YAAY,EAAE,EAAE;IAChB,KAAK,EAAE;QACL,cAAc;QACd,wCAAwC;QACxC,yBAAyB;QACzB,sCAAsC;KACvC;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4TP;CACA,CAAC","sourcesContent":["/**\n * The `loam-spine` reference page: how the artifacts JOIN.\n *\n * Operation and message ids, coverage and permission references, the\n * capability tree and its `Realizes:` join, the requirement baseline pins\n * (`Based-On:` / `x-loam-based-on`), how to draw a shared broker, and how to\n * declare a message produced outside the fleet.\n *\n * This is the grammar a reader consults while writing ONE document, which is\n * why it is a printed page rather than a section of AGENTS.md: it is consulted\n * at a moment you can name, and paying for it on every invocation of every\n * session bought nothing. Which element IS which service deliberately stayed\n * in AGENTS.md — today as ORIENTATION's `## The identity rule` in\n * ../../agents-md.ts — because an agent cannot read the landscape at all\n * without it, so it is orientation rather than reference.\n *\n * The text is the AGENTS.md section verbatim; nothing was rewritten to move\n * it, so a cross-reference from the remaining file still names the same\n * headings. See ./reference.ts for why these four pages exist.\n */\nimport type { CommandContent } from \"../../contract.js\";\n\nexport const LOAM_SPINE: CommandContent = {\n name: \"loam-spine\",\n description: \"Reference: the ID spine — every join between the artifacts, and the baseline pins\",\n // A reference page takes no arguments: it is one document, printed whole.\n // The empty hint is what `loam instructions` prints beside the name, and an\n // invented one (\"<none>\") would read as an argument somebody could pass.\n argumentHint: \"\",\n purpose:\n \"The exact joins the artifacts are held together by — operationId, message name, `Covers:`, `Requires:`, `Capability:`/`Realizes:` — and the `Based-On:` pins that stop two features in flight reverting each other.\",\n invocation: \"loam instructions loam-spine\",\n placeholders: [],\n spine: [\n \"The ID spine\",\n \"The requirement baseline — `Based-On:`\",\n \"Drawing a shared broker\",\n \"A message produced outside the fleet\",\n ],\n body: `The ID spine: how the artifacts join, and what pins a change to the version it was written against.\n\n## The ID spine\n\nThe spine is a family of exact joins, not one magic id: feature tag; service\nbinding; synchronous operation; asynchronous message; architecture coverage;\nauthorization permission. Three artifacts describe the same synchronous call in\nthree languages. They are joined by the **operationId**, and that join is what\n\\`loam validate\\` checks:\n\n- architecture — a C4 edge names the operation it calls:\n \\`checkoutWeb -> paymentService 'Calls authorizePayment' { metadata { op 'authorizePayment' } }\\`\n- behaviour — a requirement declares the operations it governs:\n \\`Operations: authorizePayment\\`\n- contract — the provider's \\`openapi.yaml\\` defines it:\n \\`operationId: authorizePayment\\`\n\nSpell it identically in all three places. A mismatch is an error, not a warning:\nan edge calling an operation nobody defines is a broken contract between services.\n\nFeatures are joined the same way: a delta's new elements and edges carry the\nfeature id as a LikeC4 tag (\\`#FEAT-101\\`), and that tag is exactly what\n\\`loam archive\\` folds into the living landscape. Untagged elements in a delta are\ncontext for the diagram, not changes.\n\nThe other joins use the same discipline:\n\n- \\`metadata { publishes|consumes 'message' }\\` on an edge ↔\n \\`Publishes:\\`/\\`Consumes:\\` in a requirement ↔ the AsyncAPI 3 message name;\n- \\`Covers:\\` in an architecture requirement ↔ a C4 element/edge or\n \\`alert:<id>\\` / \\`sli:<id>\\` from health.yaml;\n- \\`Requires: <subject>/<permission>\\` in either requirement file ↔ the fleet\n declaration in \\`architecture/permissions.yaml\\`;\n- \\`Capability:\\` in either requirement file ↔ a declared id in\n \\`architecture/capabilities.yaml\\`. A LIST, comma-separated — the relation is\n many-to-many in both directions.\n\nThe permissions file is opt-in: a fleet with no \\`Requires:\\` line owes none.\nOnce a requirement names a permission, an undeclared pair is\n\\`permissions.unknown\\` (error). An unreadable vocabulary is\n\\`permissions.invalid\\`; a declaration nothing requires is\n\\`permissions.unenforced\\` (warn). The subject is what the permission is checked\nON (user, profile, service), not necessarily the caller. \\`owned_by\\` and\n\\`enforced_by\\` are explanatory and are not resolved against services; this is\na checked document join, not proof that the identity provider implements it.\n\nThe capability axis opts in the OTHER way: the FILES are the opt-in, not the\nline. A fleet with neither \\`architecture/capabilities.yaml\\` nor a\n\\`capabilities/\\` directory gets no capability findings at all, however many\n\\`Capability:\\` lines its requirements already carry.\n\nA capability may be declared on either side, and the vocabulary is the UNION.\nA name alone is a line in \\`architecture/capabilities.yaml\\` —\n\\`capabilities: {<id>: {description, owner}}\\`, nested ids such as\n\\`payments/refunds\\` kept as one flat key. A name with prose behind it gets a\ndocument instead: \\`capabilities/<id>/spec.md\\`, the AUTHORED business tree,\none directory per capability with nesting spelled by the tree\n(\\`capabilities/payments/refunds/spec.md\\`). The directory is the list — there\nis no manifest — and its existence is what opts the fleet in, so \\`loam init\\`\ndoes not scaffold it. A directory holding neither the document nor a\ncapability beneath it is \\`capability.doc-missing\\` (warn).\n\nThe document carries narrative and then \\`## Requirements\\`, in the same\ngrammar every spec.md uses, with two rules of its own. Every requirement needs\na \\`Requirement-ID:\\` — these documents outlive the services that realize them,\nso identity is the line and not the heading; without one it is\n\\`capability.requirement-unidentified\\` (error). And a capability requirement\nmust be observable OUTSIDE the fleet: \\`Operations:\\`, \\`Covers:\\`,\n\\`Publishes:\\` and \\`Consumes:\\` all resolve against one service's own contract,\nso carrying any of them is \\`capability.requirement-service-scoped\\` (error) —\nwrite it in that service's spec.md instead. Naming no service is an authoring\nrule that PR review holds, not one loam checks: matching service names in prose\nwould be a heuristic, and loam does not guess. The axis's own two joins are\nrefused here as well (\\`capability.requirement-inert-join\\`, error): they point\nINTO the tree, so nothing reads them written inside it.\n\nA FEATURE changes a capability document with a delta, not by editing it:\n\\`features/<FEAT>/capabilities/<id>/spec.md\\`, the same\n\\`## ADDED|MODIFIED|REMOVED Requirements\\` grammar the service deltas use, the\nsame nesting (\\`features/<FEAT>/capabilities/payments/refunds/\\`), the same\n\\`Based-On:\\` pins, and the same \\`delta.*\\` refusals with the capability id as\nthe subject. \\`scaffold.placeholder\\` reads it too, so an unedited template\nheading refuses the archive here as it does in a spec delta, and\n\\`capability.doc-missing\\` (warn) names a directory in it that holds no\n\\`spec.md\\` — an empty one is not a delta, so the merge would carry nothing.\n\\`loam archive\\` merges it into the living document in the same\ntransaction as the service merge, creating \\`capabilities/<id>/spec.md\\` — and\nso the whole tree — when this is the first feature to mention it; the heading\nis the id and the narrative above \\`## Requirements\\` is yours to write, because\nloam merges requirements and never prose. The document's three rules above are\ngraded on the delta BEFORE the merge, so a capability requirement filed at the\nwrong altitude is refused while you still hold it. Editing the living document\ndirectly is still legal; the delta is what makes two features touching one\npromise collide loudly instead of silently. \\`loam new <FEAT> --capability <id>\\`\nscaffolds that delta, and \\`loam show <FEAT>\\` and \\`loam delta <FEAT>\\` list the\npromises a feature changes — a capability delta names no service, so without\nthose two a business-only feature reads as a feature carrying nothing.\n\nA service requirement says which promise it serves with \\`Realizes:\\`, entries\nspelled \\`<capability-id>#<Requirement-ID>\\`. This is the join the tree exists\nfor, and it is NOT the same claim as \\`Capability:\\` beside it — that names a\ntheme, this names one promise, and a requirement commonly carries both. The\ncapability half of an entry is what makes the target addressable, since a\n\\`Requirement-ID\\` is unique only inside its own document; the separator is the\nLAST \\`#\\`, because the requirement half's grammar excludes one and the\ncapability half's does not. Written by whoever implements the requirement,\nnever by the analyst — a business document must not be edited every time the\nfleet rearranges which service carries which part.\n\nBoth directions are graded. An entry naming no capability requirement is\n\\`capability.realizes-unknown\\` (error; in a feature delta it gates\n\\`loam archive\\`, \\`--approve\\`-overridable), and its message says which of the\nfive failures happened — malformed entry, undeclared capability, declared but\nundocumented, document with no requirements, or an id that document does not\ndeclare. A capability requirement nothing keeps is\n\\`capability.requirement-unrealized\\` (warn, one per requirement): it never\ngates, because writing the business document ahead of the fleet is the intended\nuse. \\`loam list capabilities --json\\` carries the whole join — each capability's\nrequirements, and what realizes each one.\n\n\\`Realizes:\\` is not the only way to keep a promise, and for a criterion that\nCROSSES services it is not enough: \"I enter a login and a password and I am in\"\nbelongs to no single service's spec, because each promises only its own part. A\nUSE CASE can carry it, because it IS the hop sequence. So a \\`dynamic view\\`\nalready tagged \\`#cap-<slug>\\` may carry \\`#req-<slug>\\` as well, naming one of\nthat capability's requirements — the architect's answer to the analyst's\npromise, graded by the same four \\`usecase.*\\` codes down to the operation. The\nsecond tag is SCOPED by the first: a \\`Requirement-ID\\` is unique only inside its\nown document, so a \\`#req-\\` tag on a view that resolves no capability, or two, is\n\\`usecase.requirement-unresolved\\` (error) rather than a guess. Several \\`#req-\\`\ntags on one view are legal — a flow commonly keeps two promises. Both tags spell\ntheir id with every character outside \\`[A-Za-z0-9_-]\\` flattened to \\`-\\`,\nbecause that is all a LikeC4 tag name accepts.\n\nAgainst that vocabulary an undeclared name is \\`capability.unknown\\` (error; in\na feature delta it gates \\`loam archive\\`, \\`--approve\\`-overridable), an\nunreadable \\`capabilities.yaml\\` is \\`capability.invalid\\` exactly once per run\nwith every grade that resolves against the vocabulary suspended behind it, and\na declared capability no living requirement realizes is\n\\`capability.unrealized\\` (warn, one per capability). The fleet total is\nreadable: \\`loam list capabilities\\` reports each capability's realizing\nrequirements, their services and the draft/verified split, and\n\\`loam explore --capability <id>\\` seeds an exploration from the realizing\nservices.\n\n## The requirement baseline — \\`Based-On:\\`\n\nA MODIFIED requirement carries its FULL new text, not a diff, so archive does not\nmerge your wording into the living one — it REPLACES it. When two features rewrite\nthe same requirement, the loser loses everything they wrote, scenarios and all.\n\n\\`loam validate\\` names the other feature while both are in flight\n(\\`delta.modified-conflict\\`), but that warning cannot see the case that actually\nhappens: the first feature archives, stops being an active feature, the second\nrevalidates GREEN, and its archive lands text written against a document that no\nlonger exists — \\`+0 ~1 -0\\`, exit 0, nobody told.\n\nSo a MODIFIED or REMOVED requirement pins the living version it was written\nagainst, directly under its identity:\n\n\\`\\`\\`markdown\n### Requirement: Cancel an order\nRequirement-ID: REQ-042\nBased-On: 3bcc7a40f2b23ff3\n\\`\\`\\`\n\nYou do not compute that value — \\`loam rebase FEAT-101\\` writes it, for every\nMODIFIED/REMOVED requirement in the feature, from the living text as it stands\nright now. Run it when you finish authoring a delta, and again after you resolve\na collision. Archive refuses on a stale pin (\\`delta.baseline-stale\\`) and says\nboth digests.\n\n**Restamping is not resolving.** A pin claims \"I read this version\". If\n\\`loam rebase\\` reports a requirement as moved, re-read the living requirement and\nfold in what you still mean BEFORE you ship — otherwise you have re-pinned to a\ndocument you never read, and archive will merge your text over theirs with\nloam's blessing. There is no automatic three-way merge and there will not be one:\nrequirement prose does not merge, people do.\n\nA delta adopted from OpenSpec has no such line and gets a warning\n(\\`delta.baseline-missing\\`) — one that GATES the archive: unpinned is exactly\nthe shape that reverts someone else's landed change at exit 0. A migrated\ncorpus is not stranded by this: \\`loam rebase <FEAT>\\` pins every delta in one\ncommand, and a human's \\`--approve\\` archives unpinned when the loss is meant.\n\n### The same pin on the contract axis\n\nA feature's \\`openapi.yaml\\` is a self-contained document holding ONLY what the\nfeature adds, changes or removes — not a copy of the living contract. The merge\nwalks only the paths and methods the delta spells: an operation you leave out\nstays exactly as the living contract has it, a local \\`$ref\\`\n(\\`#/components/schemas/Order\\`) resolves into the living document, and a new\noperation has no living version to pin — an add-only delta needs no rebase.\n\nWhat the merge does upsert is every operation the document DOES spell. So an\noperation you quote without changing it — copied for context, or a whole\ncontract pasted in — is a collision waiting for someone else's edit: two\nfeatures over one service, each quoting the operation the other edits, destroy\neach other, and the second to archive pushes its authoring-time copy back over\nwhatever landed in between. Quote nothing you do not change. Each quote costs a\npin, and a parallel feature that REMOVES the quoted operation makes it worse\neither way: pinned, the quote is \\`openapi.baseline-invalid\\`, an error that\nblocks your archive, and \\`openapi.op-revived\\` beside it; unpinned, it is\n\\`openapi.op-revived\\` alone — a warning that gates, because the merge would\nread the dead operation as a new one and put it back (\\`asyncapi.slot-revived\\`\nis the same finding on a channel, operation or message slot). A parallel\nfeature that MOVES the operation to another address does the same to a quote\nof the old one, which archived would answer the operationId at both. The fix\nfor both is to delete the quote. Keep it only when the feature brings a\nretired operation back ON PURPOSE: then drop any pin, and a human archives\nwith \\`--approve\\`. \\`loam rebase\\` never pins a retired operation as new —\nnothing lives at its slot, so there is no version to pin — and prints the same\nfinding (\\`revived[]\\` in \\`--json\\`), so the decision stays with you. It judges\nthe delta as it will write it, pins included: a quote of an address another\noperation lives at now — one a move vacated, since re-occupied — IS repinned,\nover the operation living there, which makes it an edit the merge would write;\nthe revival that edit makes is reported by that rebase (or its \\`--dry-run\\`),\nnot first by the next validate. The check reads the removal\nmarkers of the features under \\`features/archive/\\` — a removal made by\nhand-editing a living contract is invisible to it.\n\nThe guard against that collision, for every living operation the delta does\nspell, is a pin — a vendor extension beside \\`x-loam-remove\\`:\n\n\\`\\`\\`yaml\npaths:\n /orders/{id}/cancel:\n post:\n operationId: cancelOrder\n x-loam-based-on: 86feabe621ed887d\n\\`\\`\\`\n\n\\`loam rebase\\` writes one on every operation in the delta the living contract\nalready holds, pinned to the living version; what the feature adds gets none.\nThat is what lets the merge tell a QUOTE from an EDIT:\n\n- pin equals the operation's own content → you quoted it → **the merge skips it\n entirely** and the living contract keeps whatever it holds. Archive prints\n \\`· quotes …\\` so the plan never silently writes less than your delta spells.\n- pin equals the living operation → you edited it, nobody else did → merged.\n- pin matches neither → you edited it AND somebody landed a change to it →\n \\`openapi.baseline-stale\\`, refused.\n\nThe two surfaces an in-operation key cannot reach — path-level keys\n(\\`parameters\\`, \\`servers\\`, \\`summary\\`, \\`x-*\\`) and every\n\\`components/<kind>/<name>\\` — are pinned the same way through one root record,\n\\`x-loam-baselines\\`, which \\`loam rebase\\` also writes and the merge strips: a\nquoted key or component is skipped, an edited one is written and warned as\n\\`openapi.path-item-modified\\` / \\`openapi.component-modified\\`. A new component\nis copied in when something the merge writes references it; a living one you\nonly \\`$ref\\` needs no copy in the delta at all. Removal markers carry no pin;\n\\`openapi.remove-target-mismatch\\` is what guards those.\n\n## Drawing a shared broker\n\nA broker drawn as ONE element is the node every service in the fleet points at,\nand any view over it is a star nobody can read. Nothing in loam catches this —\nloam parses the model and renders no view, so the map can be perfectly valid and\ncompletely illegible. Two habits keep it readable, and both are cheap only\nbefore the fleet is drawn:\n\n**Model the topic, not the broker.** Nest a topic per channel inside the broker's\nelement and point the edges at it — \\`paymentService -> kafka.paymentEvents\\`, not\n\\`paymentService -> kafka\\`. One hub of degree sixty becomes twelve of degree\nfive, and it is the truer model besides: what a producer and a consumer share is\nthe topic, never the broker. Every check joins the same way, because an edge into\na nested element resolves to the element that owns it. Declare the kind once —\n\n specification {\n element topic {\n style { shape queue }\n }\n }\n\nThe KIND is what does the work: since 2026-09-07 loam reads \\`database\\`, \\`queue\\`,\n\\`topic\\`, \\`cache\\` and \\`bucket\\` as infrastructure rather than as candidate\nservices, so a topic drawn this way is out of the service census and nobody is\nasked for a \\`services/payment.events/\\` (measured: the same element spelled\n\\`container\\` earns \\`landscape.service-undocumented\\`, spelled \\`topic\\` it earns\nnothing). This used to need \\`#external\\` ON THE KIND as a workaround, because\n**LikeC4 does not inherit tags** and a topic nested inside an \\`#external\\` broker\nis not external itself. That workaround is no longer owed. Tag the kind\n\\`#external\\` if you want the topics out of the fleet VIEW as well — that is what\nthe next paragraph's \\`exclude\\` reads, and it is a rendering choice now rather\nthan a way to silence a check.\n\n**Scope the views.** \\`exclude element.tag = #external\\` keeps the fleet overview to\nthe calls between our own services; a second view over the broker draws the event\nspine on its own. Views are LikeC4's and loam computes none, so this costs loam\nnothing and is worth doing anyway — an \\`include *\\` over the whole fleet map is\nalso the one view whose computation takes minutes rather than milliseconds. The\nscaffolded landscape applies the same pruning to ubiquitous infrastructure with\n\\`#platform\\`: the fleet view excludes it, and the platform view's\n\\`include * -> element.tag = #platform\\` (the relationship form — the obvious\nspelling draws boxes with no edges) keeps \"who depends on it\" answerable.\n\n## A message produced outside the fleet\n\nA platform service that is not ours — a config service, an auth broker —\nproduces messages our services consume, and no \\`services/<id>/\\` will ever hold\nits contract. Declare the production on the landscape, where the element\nalready says \\`#external\\`:\n\n externalConfig = softwareSystem 'external-config' {\n #external\n }\n externalConfig -> kafka.configTopic 'publishes config refreshes' {\n metadata { publishes 'config.ConfigRefreshed' }\n }\n\n\\`spine.message-unproduced\\` then stops firing — the message HAS a producer,\noutside the fleet — and the contract question moves to the one file that can\nanswer it: the consuming service's own asyncapi.yaml. While that file declares\nno shape for the message, \\`spine.message-external\\` (warn) says so; once the\nconsumer declares the payload, the axis is quiet. A truthful map and a green\nbuild no longer exclude each other, which is the exact choice this used to force.\n`,\n};\n"]}
|