@ybotok/loam 0.2.0-alpha.5 → 0.2.0-alpha.7
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 +1063 -0
- package/MIGRATING-from-OpenSpec.md +7 -4
- package/QUICKSTART.md +8 -3
- package/README.md +13 -7
- package/ROADMAP.md +285 -11
- package/SCHEMA.md +566 -45
- package/WORKFLOW.md +23 -10
- package/dist/commands/adopt/render.js +47 -8
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +45 -1
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/run.js +53 -23
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/context/print.js +11 -1
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/doctor.js +46 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explore.js +12 -2
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/list/views.js +5 -1
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/seed/commit.js +34 -1
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +158 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +7 -2
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +2 -1
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/marks.js +3 -5
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +39 -18
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/subsystem/edit.js +4 -2
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +55 -17
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/sync.js +238 -87
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +126 -22
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +159 -19
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +16 -9
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +30 -77
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/vocabulary.js +4 -19
- package/dist/commands/validate/checks/vocabulary.js.map +1 -1
- package/dist/commands/validate/feature.js +1 -1
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +131 -120
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +102 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +109 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +219 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +56 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/steps.js +49 -17
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +45 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +258 -70
- package/dist/commands/validate/fleet/views/projects.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +99 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +32 -15
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/service/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +110 -34
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +39 -5
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +41 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +10 -24
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/core/agent/agents-md/artifacts.js +1 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +19 -5
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/cycle.js +24 -9
- package/dist/core/agent/agents-md/cycle.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/subsystem.js +70 -17
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +45 -3
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +5 -3
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +26 -7
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/support/report.js +39 -5
- package/dist/core/agent/support/report.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +123 -14
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +34 -20
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +3 -1
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +12 -6
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/brief/brief.js +88 -19
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +133 -7
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +212 -129
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +81 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +284 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +94 -29
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +13 -3
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/likec4.js +23 -1
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/parsed/deployment.js +5 -3
- package/dist/core/c4/parsed/deployment.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +142 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +77 -7
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +12 -6
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +85 -42
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +53 -15
- package/dist/core/c4/project/staged.js.map +1 -1
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/service.js +8 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +18 -0
- package/dist/core/c4/resolve/steps.js.map +1 -1
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +126 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-scan.js +26 -0
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +60 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +17 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +154 -100
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +140 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +14 -4
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +2 -1
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/coherence/coherence.js +7 -0
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/delta/delta.js +9 -0
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +16 -10
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-state.js +21 -5
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/victims.js +10 -1
- package/dist/core/diff/victims.js.map +1 -1
- package/dist/core/docs.js +29 -24
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +36 -8
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/explain/refusals.js +1 -1
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explore/explore.js +44 -9
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +47 -0
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +5 -1
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +65 -26
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/pack/living.js +47 -13
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +9 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/repo/paths.js +45 -38
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +45 -6
- package/dist/core/repo/tree/render/body.js.map +1 -1
- package/dist/core/repo/tree/render/projects.js +93 -35
- package/dist/core/repo/tree/render/projects.js.map +1 -1
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +20 -4
- package/dist/core/repo/tree/render/stale.js.map +1 -1
- package/dist/core/repo/tree/render/views.js +149 -32
- package/dist/core/repo/tree/render/views.js.map +1 -1
- package/dist/core/repo/tree/walk.js +45 -2
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/scaffold/landscape.js +219 -9
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/usecases/delta/claims.js +1 -0
- package/dist/core/usecases/delta/claims.js.map +1 -1
- package/dist/core/usecases/delta/overlay.js +4 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +18 -6
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +7 -0
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +161 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/verify/checklist.js +42 -10
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +51 -1
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +30 -8
- package/examples/docs/architecture/landscape.likec4 +47 -0
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +3 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +5 -2
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/model.likec4 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -39
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/examples/docs/services/checkout-web/likec4.config.json +0 -4
- package/examples/docs/services/order-service/likec4.config.json +0 -4
- package/examples/docs/services/payment-service/likec4.config.json +0 -4
- package/examples/docs/services/platform/identity-service/likec4.config.json +0 -4
- package/examples/docs/services/platform/notification-service/likec4.config.json +0 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explore.js","sourceRoot":"","sources":["../../src/commands/explore.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"../core/envelope/config.js\";\nimport { explore, type Exploration, type ExploreService } from \"../core/explore/explore.js\";\nimport { FleetContext } from \"../core/fleet-context.js\";\nimport { emitJson, fail, reportNoConfig } from \"../core/envelope/json.js\";\nimport { featureIdProblem } from \"../core/kernel/ids/feature.js\";\nimport { parseServiceIds } from \"../core/kernel/ids/service.js\";\nimport { DocsRepoUnavailableError } from \"../core/repo/state.js\";\nimport { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from \"./policy/gate.js\";\nimport { plural } from \"./policy/format.js\";\n\n/**\n * The feature id in the suggested command line when `--as` did not say.\n *\n * It has to satisfy the feature-id grammar, because `scaffold` is documented as\n * the literal `loam new` line and AGENTS.md's rule is that an instruction loam\n * prints and loam refuses is a defect. `FEAT-NNN` — the obvious placeholder —\n * is exactly that: `loam new FEAT-NNN` comes back `invalid-option`, so the last\n * line of every default run was a command that could not be run.\n *\n * `FEAT-000` parses and is still unmistakably a placeholder, which buys the\n * property the refusal was standing in for: an agent that pastes it unedited\n * gets a feature called FEAT-000, visible in `loam list` within seconds and\n * deletable with `git rm -r`. A plausible id (FEAT-1) would not be.\n */\nconst PLACEHOLDER = \"FEAT-000\";\n\ninterface ExploreOptions {\n json?: boolean;\n op?: string[];\n capability?: string[];\n as?: string;\n}\n\nexport function registerExplore(program: Command): void {\n program\n .command(\"explore\")\n .argument(\"[service...]\", \"service ids the change starts from\")\n .description(\"Read the fleet around a change nobody has written down yet — writes nothing\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\n \"--op <operationId>\",\n \"seed from an operation instead of a service; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\n \"--capability <id>\",\n \"seed from a declared capability's realizing services; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\"--as <FEAT>\", `feature id for the suggested command line (default: ${PLACEHOLDER})`)\n .action(async (serviceArgs: string[], opts: ExploreOptions) => {\n const json = opts.json === true;\n const services = serviceArgs ?? [];\n const operations = opts.op ?? [];\n const capabilities = opts.capability ?? [];\n\n // `--as` is interpolated into the `loam new` line this command prints\n // under `next:` and returns as `scaffold`, and `loam-feature` teaches an\n // agent to pass `$1` straight into it. Unchecked, `explore` hands back a\n // command `loam new` then refuses — loam instructing an agent to run\n // something loam rejects, which is the one thing the printed-command\n // guard exists to prevent and the one shape it cannot see, because it\n // reads literal source strings and this line is assembled from argv.\n // Same grammar as `new`'s, from core/kernel/ids/feature.ts, so the two cannot disagree.\n if (opts.as !== undefined) {\n const problem = featureIdProblem(opts.as, \"feature id for --as\");\n if (problem !== null) {\n fail(json, \"invalid-option\", problem);\n return;\n }\n }\n if (services.length === 0 && operations.length === 0 && capabilities.length === 0) {\n fail(\n json,\n \"invalid-option\",\n \"Nothing to explore from. Name at least one service, or pass --op <operationId> or --capability <id>. \" +\n \"`loam list services` is the fleet; `loam show <service>` is one of them; \" +\n \"`loam list capabilities` is what the fleet promises.\",\n );\n return;\n }\n\n // Rule 6 in docs/DESIGN.md: a raw argv string that reaches a path join is\n // guarded at the command boundary. Every id here reaches the service path builders.\n // Before `loadConfig`, exactly as `loam new` guards its `--touches` list:\n // the grammar is a fact about the argument, not about the repository, so\n // an unusable id should answer the same whether or not you are standing\n // in a wired repo.\n //\n // A seed naming a service that does not EXIST is fine and reported as\n // `unknown` — the feature may be introducing it — but one that could not\n // be a service directory at all is a typo, not an intention.\n const seeds = parseServiceIds(services, \"service\");\n if (!seeds.ok) {\n fail(json, \"invalid-option\", seeds.problem);\n return;\n }\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const { docsDir } = config;\n // `services`, not `docs`: every answer here is a statement about the\n // fleet, and an empty ring read out of a directory that is not a docs\n // repo says \"nothing calls this service\" about a repo nobody looked in.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n try {\n const result = await explore({\n docsDir,\n services: seeds.ids,\n operations,\n capabilities,\n featureId: opts.as ?? PLACEHOLDER,\n context: new FleetContext(),\n });\n if (json) {\n emitJson({ command: \"explore\", docsDir, ...result });\n return;\n }\n print(result);\n } catch (err) {\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // The scan is all-or-nothing — it reads every seed's contract to build\n // one table — so the honest answer to an unreadable file is a refusal\n // naming the path, not a ring with a silent hole in it.\n reportRepositoryUnavailable(json, err, \"the exploration would be missing a service\", docsDir);\n }\n });\n}\n\n/* ------------------------------------------------------------------ */\n\nfunction print(r: Exploration): void {\n if (!r.landscape.present || !r.landscape.parses) {\n // Said first and without hedging: with no readable map there IS no ring,\n // and a reader who takes the empty neighbour list at face value concludes\n // the change is contained when nothing checked that.\n const why = r.landscape.present ? \"does not parse\" : \"does not exist\";\n console.log(\n `! architecture/landscape.likec4 ${why} — no neighbour below is derived, and none is missing either\\n`,\n );\n }\n\n for (const s of r.services) {\n console.log(`${s.id} ${label(s)}`);\n if (!s.known) {\n // Two different facts wear the same absence. A SEED nobody typed by\n // accident is a service the feature introduces; one typed by accident is\n // a typo, and the near-miss list is what separates them. A neighbour is\n // neither — the fleet map drew an edge to something with no\n // `services/<id>/`, which is usually an external system and occasionally\n // a service nobody has adopted. Saying \"typo\" about that one sends a\n // reader looking for a mistake they did not make.\n const near = r.unknown.find((u) => u.id === s.id)?.nearest ?? [];\n const why =\n s.reason === \"seed\"\n ? \"the feature introduces it, or the id is a typo\"\n : \"an external system, or a service nobody has adopted\";\n console.log(\n ` no services/${s.id}/ — ${why}` +\n (near.length > 0 ? `; closest existing: ${near.join(\", \")}` : \"\"),\n );\n } else {\n if (s.missing.length > 0) console.log(` missing: ${s.missing.join(\", \")}`);\n if (!s.modelled) console.log(` not in the fleet map — no edge into it can be checked`);\n // Said before `exposes`, and said at all: an unreadable contract exposes\n // an empty list, and printing nothing there reads as \"this service offers\n // no endpoints\" — the answer that turns a MODIFIED requirement into an\n // ADDED one.\n if (s.openapi.unreadable) {\n console.log(` ! openapi.yaml does not parse — nothing here lists what ${s.id} exposes`);\n if (s.openapi.error !== undefined) console.log(` ${s.openapi.error}`);\n } else if (s.operations.length > 0) {\n console.log(` exposes: ${s.operations.join(\", \")}`);\n }\n }\n for (const e of s.inbound) console.log(` ← ${e.service}${edgeNote(e.op, e.title)}`);\n for (const e of s.outbound) console.log(` → ${e.service}${edgeNote(e.op, e.title)}`);\n console.log(\"\");\n }\n\n if (r.unresolvedOperations.length > 0) {\n console.log(\n `! no living contract defines ${r.unresolvedOperations.join(\", \")} — ` +\n `the feature is adding ${r.unresolvedOperations.length === 1 ? \"it\" : \"them\"}, or the id is wrong\\n`,\n );\n }\n\n if (r.unresolvedCapabilities.length > 0) {\n console.log(\n `! no realizing service seeds ${r.unresolvedCapabilities.join(\", \")} — ` +\n `not declared in architecture/capabilities.yaml, or declared and realized by nothing\\n`,\n );\n }\n\n if (r.neighbours.length > 0) {\n // The whole point of the command, so it is stated as a question rather than\n // a list: these are candidates for --touches, and loam does not know which\n // of them this change actually touches.\n console.log(\n `${plural(r.neighbours.length, \"neighbour\")} one hop out: ${r.neighbours.join(\", \")}`,\n );\n console.log(\" weigh each one — loam knows they are connected, not whether you change them\\n\");\n }\n\n if (r.overlaps.length > 0) {\n console.log(\"already in flight over this ground\");\n for (const o of r.overlaps) console.log(` ${o.feature} ${o.services.join(\", \")}`);\n console.log(\" `loam dependencies` says which must archive first, and what no ordering fixes\\n\");\n }\n\n console.log(`next: ${r.scaffold}`);\n}\n\nfunction label(s: ExploreService): string {\n const where = s.reason === \"seed\" ? \"seed\" : s.reason === \"calls-seed\" ? \"calls a seed\" : \"called by a seed\";\n // `known`, matching the branch in `print` — not `maturity === null`. The two\n // are set together and cannot disagree today, but discriminating on different\n // fields for the same question is how they start to: a reader fixing one\n // branch has no reason to look at the other.\n return s.known ? `${where} · ${s.maturity}` : `${where} · not in the docs repo`;\n}\n\nfunction edgeNote(op: string | null, title: string | null): string {\n if (op !== null) return ` ${op}`;\n return title !== null ? ` \"${title}\"` : \"\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"explore.js","sourceRoot":"","sources":["../../src/commands/explore.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"../core/envelope/config.js\";\nimport { explore, type Exploration, type ExploreService } from \"../core/explore/explore.js\";\nimport { FleetContext } from \"../core/fleet-context.js\";\nimport { emitJson, fail, reportNoConfig } from \"../core/envelope/json.js\";\nimport { featureIdProblem } from \"../core/kernel/ids/feature.js\";\nimport { parseServiceIds } from \"../core/kernel/ids/service.js\";\nimport { DocsRepoUnavailableError } from \"../core/repo/state.js\";\nimport { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from \"./policy/gate.js\";\nimport { plural } from \"./policy/format.js\";\n\n/**\n * The feature id in the suggested command line when `--as` did not say.\n *\n * It has to satisfy the feature-id grammar, because `scaffold` is documented as\n * the literal `loam new` line and AGENTS.md's rule is that an instruction loam\n * prints and loam refuses is a defect. `FEAT-NNN` — the obvious placeholder —\n * is exactly that: `loam new FEAT-NNN` comes back `invalid-option`, so the last\n * line of every default run was a command that could not be run.\n *\n * `FEAT-000` parses and is still unmistakably a placeholder, which buys the\n * property the refusal was standing in for: an agent that pastes it unedited\n * gets a feature called FEAT-000, visible in `loam list` within seconds and\n * deletable with `git rm -r`. A plausible id (FEAT-1) would not be.\n */\nconst PLACEHOLDER = \"FEAT-000\";\n\ninterface ExploreOptions {\n json?: boolean;\n op?: string[];\n capability?: string[];\n as?: string;\n}\n\nexport function registerExplore(program: Command): void {\n program\n .command(\"explore\")\n .argument(\"[service...]\", \"service ids the change starts from\")\n .description(\"Read the fleet around a change nobody has written down yet — writes nothing\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\n \"--op <operationId>\",\n \"seed from an operation instead of a service; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\n \"--capability <id>\",\n \"seed from a declared capability's realizing services; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\"--as <FEAT>\", `feature id for the suggested command line (default: ${PLACEHOLDER})`)\n .action(async (serviceArgs: string[], opts: ExploreOptions) => {\n const json = opts.json === true;\n const services = serviceArgs ?? [];\n const operations = opts.op ?? [];\n const capabilities = opts.capability ?? [];\n\n // `--as` is interpolated into the `loam new` line this command prints\n // under `next:` and returns as `scaffold`, and `loam-feature` teaches an\n // agent to pass `$1` straight into it. Unchecked, `explore` hands back a\n // command `loam new` then refuses — loam instructing an agent to run\n // something loam rejects, which is the one thing the printed-command\n // guard exists to prevent and the one shape it cannot see, because it\n // reads literal source strings and this line is assembled from argv.\n // Same grammar as `new`'s, from core/kernel/ids/feature.ts, so the two cannot disagree.\n if (opts.as !== undefined) {\n const problem = featureIdProblem(opts.as, \"feature id for --as\");\n if (problem !== null) {\n fail(json, \"invalid-option\", problem);\n return;\n }\n }\n if (services.length === 0 && operations.length === 0 && capabilities.length === 0) {\n fail(\n json,\n \"invalid-option\",\n \"Nothing to explore from. Name at least one service, or pass --op <operationId> or --capability <id>. \" +\n \"`loam list services` is the fleet; `loam show <service>` is one of them; \" +\n \"`loam list capabilities` is what the fleet promises.\",\n );\n return;\n }\n\n // Rule 6 in docs/DESIGN.md: a raw argv string that reaches a path join is\n // guarded at the command boundary. Every id here reaches the service path builders.\n // Before `loadConfig`, exactly as `loam new` guards its `--touches` list:\n // the grammar is a fact about the argument, not about the repository, so\n // an unusable id should answer the same whether or not you are standing\n // in a wired repo.\n //\n // A seed naming a service that does not EXIST is fine and reported as\n // `unknown` — the feature may be introducing it — but one that could not\n // be a service directory at all is a typo, not an intention.\n const seeds = parseServiceIds(services, \"service\");\n if (!seeds.ok) {\n fail(json, \"invalid-option\", seeds.problem);\n return;\n }\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const { docsDir } = config;\n // `services`, not `docs`: every answer here is a statement about the\n // fleet, and an empty ring read out of a directory that is not a docs\n // repo says \"nothing calls this service\" about a repo nobody looked in.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n try {\n const result = await explore({\n docsDir,\n services: seeds.ids,\n operations,\n capabilities,\n featureId: opts.as ?? PLACEHOLDER,\n context: new FleetContext(),\n });\n if (json) {\n emitJson({ command: \"explore\", docsDir, ...result });\n return;\n }\n print(result);\n } catch (err) {\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // The scan is all-or-nothing — it reads every seed's contract to build\n // one table — so the honest answer to an unreadable file is a refusal\n // naming the path, not a ring with a silent hole in it.\n reportRepositoryUnavailable(json, err, \"the exploration would be missing a service\", docsDir);\n }\n });\n}\n\n/* ------------------------------------------------------------------ */\n\nfunction print(r: Exploration): void {\n if (!r.landscape.present || !r.landscape.parses) {\n // Said first and without hedging: with no readable map there IS no ring,\n // and a reader who takes the empty neighbour list at face value concludes\n // the change is contained when nothing checked that.\n // Same rule as `context`'s printer: name the document that actually failed.\n // `parses` answers for the `architecture/` PROJECT, so the document at fault\n // is very often a SIBLING beside a landscape that reads perfectly, and\n // naming the landscape was a claim about bytes loam read fine (verification\n // 2026-09-04). `present` is still about the FILE — it is the write target\n // every brief asks for edits to — so the absent arm keeps the file name.\n const named = !r.landscape.present\n ? \"architecture/landscape.likec4 does not exist\"\n : r.landscape.broken.length === 0\n ? \"the fleet map (architecture/) does not parse\"\n : `${r.landscape.broken.join(\", \")} ${r.landscape.broken.length > 1 ? \"do\" : \"does\"} not parse`;\n console.log(`! ${named} — no neighbour below is derived, and none is missing either\\n`);\n }\n\n for (const s of r.services) {\n console.log(`${s.id} ${label(s)}`);\n if (!s.known) {\n // Two different facts wear the same absence. A SEED nobody typed by\n // accident is a service the feature introduces; one typed by accident is\n // a typo, and the near-miss list is what separates them. A neighbour is\n // neither — the fleet map drew an edge to something with no\n // `services/<id>/`, which is usually an external system and occasionally\n // a service nobody has adopted. Saying \"typo\" about that one sends a\n // reader looking for a mistake they did not make.\n const near = r.unknown.find((u) => u.id === s.id)?.nearest ?? [];\n const why =\n s.reason === \"seed\"\n ? \"the feature introduces it, or the id is a typo\"\n : \"an external system, or a service nobody has adopted\";\n console.log(\n ` no services/${s.id}/ — ${why}` +\n (near.length > 0 ? `; closest existing: ${near.join(\", \")}` : \"\"),\n );\n } else {\n if (s.missing.length > 0) console.log(` missing: ${s.missing.join(\", \")}`);\n if (!s.modelled) console.log(` not in the fleet map — no edge into it can be checked`);\n // Said before `exposes`, and said at all: an unreadable contract exposes\n // an empty list, and printing nothing there reads as \"this service offers\n // no endpoints\" — the answer that turns a MODIFIED requirement into an\n // ADDED one.\n if (s.openapi.unreadable) {\n console.log(` ! openapi.yaml does not parse — nothing here lists what ${s.id} exposes`);\n if (s.openapi.error !== undefined) console.log(` ${s.openapi.error}`);\n } else if (s.operations.length > 0) {\n console.log(` exposes: ${s.operations.join(\", \")}`);\n }\n }\n for (const e of s.inbound) console.log(` ← ${e.service}${edgeNote(e.op, e.title)}`);\n for (const e of s.outbound) console.log(` → ${e.service}${edgeNote(e.op, e.title)}`);\n console.log(\"\");\n }\n\n if (r.unresolvedOperations.length > 0) {\n console.log(\n `! no living contract defines ${r.unresolvedOperations.join(\", \")} — ` +\n `the feature is adding ${r.unresolvedOperations.length === 1 ? \"it\" : \"them\"}, or the id is wrong\\n`,\n );\n }\n\n if (r.unresolvedCapabilities.length > 0) {\n console.log(\n `! no realizing service seeds ${r.unresolvedCapabilities.join(\", \")} — ` +\n `not declared in architecture/capabilities.yaml, or declared and realized by nothing\\n`,\n );\n }\n\n if (r.neighbours.length > 0) {\n // The whole point of the command, so it is stated as a question rather than\n // a list: these are candidates for --touches, and loam does not know which\n // of them this change actually touches.\n console.log(\n `${plural(r.neighbours.length, \"neighbour\")} one hop out: ${r.neighbours.join(\", \")}`,\n );\n console.log(\" weigh each one — loam knows they are connected, not whether you change them\\n\");\n }\n\n if (r.overlaps.length > 0) {\n console.log(\"already in flight over this ground\");\n for (const o of r.overlaps) console.log(` ${o.feature} ${o.services.join(\", \")}`);\n console.log(\" `loam dependencies` says which must archive first, and what no ordering fixes\\n\");\n }\n\n console.log(`next: ${r.scaffold}`);\n}\n\nfunction label(s: ExploreService): string {\n const where = s.reason === \"seed\" ? \"seed\" : s.reason === \"calls-seed\" ? \"calls a seed\" : \"called by a seed\";\n // `known`, matching the branch in `print` — not `maturity === null`. The two\n // are set together and cannot disagree today, but discriminating on different\n // fields for the same question is how they start to: a reader fixing one\n // branch has no reason to look at the other.\n return s.known ? `${where} · ${s.maturity}` : `${where} · not in the docs repo`;\n}\n\nfunction edgeNote(op: string | null, title: string | null): string {\n if (op !== null) return ` ${op}`;\n return title !== null ? ` \"${title}\"` : \"\";\n}\n"]}
|
|
@@ -48,7 +48,11 @@ export async function serviceViews(docsDir, services, boundService, preloaded) {
|
|
|
48
48
|
if (proven) {
|
|
49
49
|
const svcOf = serviceResolver(land.elements, known);
|
|
50
50
|
for (const r of land.relationships) {
|
|
51
|
-
|
|
51
|
+
// …including its self-edge rule: an edge whose two endpoints resolve to
|
|
52
|
+
// one service is that service's own internal wiring, and counting it made
|
|
53
|
+
// a service nobody calls owe an OpenAPI contract — `partial` here while
|
|
54
|
+
// `loam context` (which reads the shared derivation) said `documented`.
|
|
55
|
+
if (r.op !== undefined && svcOf(r.source) !== svcOf(r.target))
|
|
52
56
|
called.add(svcOf(r.target));
|
|
53
57
|
}
|
|
54
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"views.js","sourceRoot":"","sources":["../../../src/commands/list/views.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the list actually reads: one row per service, one verification cell per\n * feature.\n *\n * Both are gathered before anything is printed or serialized, because the same\n * numbers appear in `--json` and in the worklist footer — a footer computed\n * from a second read is a footer that can contradict the rows above it.\n */\nimport { existsSync } from \"node:fs\";\nimport { loadFile, type LoadedDoc } from \"../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../core/c4/resolve/service.js\";\nimport {\n maturityGaps,\n serviceMaturity,\n type Maturity,\n type MaturityInput,\n} from \"../../core/vocabulary/maturity.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"../../core/repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../../core/repo/paths.js\";\nimport { featureChecklist } from \"../../core/verify/checklist.js\";\nimport { readVerification } from \"../../core/verify/file.js\";\nimport { type AnsweredBy } from \"../../core/verify/answers.js\";\nimport {\n confirmedProvenance,\n tallyRecord,\n type VerificationVerdict,\n verificationVerdict,\n} from \"../../core/verify/record.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\nexport interface ServiceView extends MaturityInput {\n /** True when this service's `sources` can only be resolved from its own repo. */\n unverifiableFromHere: boolean;\n maturity: Maturity;\n /** What stands between this service and the next rung, in fix order. */\n missing: string[];\n}\n\n/**\n * Read the living landscape ONCE and decide, per service, whether an API is\n * expected of it. `serviceResolver` is handed the real service ids so an edge\n * drawn into a modelled container (`paymentService.api`) counts for the service\n * that owns it — the same resolution `validate` uses, so the two commands\n * cannot disagree about who is called.\n */\nexport async function serviceViews(\n docsDir: DocsDir,\n services: ServiceEntry[],\n boundService: string | undefined,\n preloaded?: LoadedDoc | null,\n): Promise<ServiceView[]> {\n const known = new Set(services.map((s) => s.id));\n const lp = landscapePath(docsDir);\n // `preloaded` is the landscape a caller already holds — `undefined` means\n // \"load it if it exists\", null \"there is none\", the convention\n // validateService's own `preloaded` follows. The fleet scorecard hands in\n // the batch-parsed doc because a healthy `validate --all` must spin ZERO\n // per-document workspaces (test/validate-batch-fallback.test.ts counts\n // them), and a second parse of a document that run already holds was the\n // exact per-path cost the batch loader exists to retire.\n const land = preloaded !== undefined ? preloaded : existsSync(lp) ? await loadFile(lp) : null;\n const called = new Set<string>();\n // Positive evidence only: with no landscape, or one that does not parse, we\n // know nothing about who is called and every service is assumed to have an\n // API — the conservative direction, because claiming a service needs no\n // contract is how a real one goes undocumented. This is the SAME rule\n // `landscapeEvidence` (core/vocabulary/maturity.ts) spells per service for\n // explore and the context pack — derived fleet-wide here in one pass (one\n // `called` set for every service) because calling the per-service partition\n // from this loop would re-walk the whole relationship list once per\n // service. If the rule moves, both spellings move.\n const proven = land !== null && land.errors.length === 0;\n if (proven) {\n const svcOf = serviceResolver(land.elements, known);\n for (const r of land.relationships) {\n if (r.op !== undefined) called.add(svcOf(r.target));\n }\n }\n\n return services.map((entry) => {\n const archSpec = existsSync(servicePathsAt(entry.dir).archSpec);\n const apiExpected = !proven || called.has(entry.id);\n const view = {\n entry,\n archSpec,\n apiExpected,\n unverifiableFromHere: entry.sources.declared && boundService !== entry.id,\n };\n const missing = maturityGaps(view);\n return { ...view, maturity: serviceMaturity(view), missing };\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* JSON */\n/* ------------------------------------------------------------------ */\n\nexport interface VerificationCell {\n state: \"recorded\" | \"stale\";\n /** The day the record was written. */\n recorded: string;\n /**\n * `verify --json`'s own three-valued verdict, through `verificationVerdict` —\n * not a fourth opinion computed here. `attested` is the one this column got\n * wrong: a scenario confirmed on an agent's word printed as `11/11` under a\n * heading that said `verified`, and `list --json` is how the fleet is graded.\n */\n verdict: VerificationVerdict;\n /** Both counts are `tallyRecord`'s — the record's `claims[]`, never its `summary:` block. */\n confirmed: number;\n claims: number;\n /** Of the confirmed, the scenario claims on an agent's word (`attestedClaims`). */\n attested: number;\n /**\n * Of the confirmed, one count per `answered_by` value (`confirmedProvenance`), every key\n * present. A different question from `attested` above, which is why both are here: `attested`\n * asks only about `scenario.tested` because that is what the verdict turns on, while this asks\n * who answered EVERY kind — and reading the first as if it were the second is how the fleet\n * scorecard credited agent-answered permission and operation claims to a test runner.\n */\n answered: Record<AnsweredBy, number>;\n}\n\n/**\n * The verification column. `readVerification` is one file read; the checklist\n * is derived only when there is a record to judge — on a fleet-sized repo most\n * features have none, and deriving N checklists to print N dashes is the cost\n * that would get this column dropped.\n *\n * An archived feature's record is frozen history (see `core/verify/record.ts`): archive\n * merged the feature's operations into the living openapi, so a re-derived\n * checklist can only mismatch — the record is reported as recorded, never\n * judged stale.\n */\nexport async function featureVerification(\n docsDir: DocsDir,\n f: FeatureEntry,\n): Promise<VerificationCell | null> {\n const v = await readVerification(f.dir);\n if (v === null) return null;\n const stale = f.archived\n ? false\n : (await featureChecklist(docsDir, f.dir, f.id)).digest !== v.checklist;\n const tally = tallyRecord(v);\n return {\n state: stale ? \"stale\" : \"recorded\",\n recorded: v.recorded,\n verdict: verificationVerdict(tally, stale),\n confirmed: tally.confirmed,\n claims: tally.claims,\n attested: tally.attested,\n answered: confirmedProvenance(v.claims),\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Text */\n/* ------------------------------------------------------------------ */\n\n/**\n * Fixed-width presence flags. `a` and `A` are deliberately the same letter in\n * two cases: the arch spec and the API are the two things a reader most often\n * mistakes for each other, and one is lowercase because it is optional.\n */\n"]}
|
|
1
|
+
{"version":3,"file":"views.js","sourceRoot":"","sources":["../../../src/commands/list/views.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAkB,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EACL,YAAY,EACZ,eAAe,GAGhB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAwC,MAAM,4BAA4B,CAAC;AAClF,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAmB,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EACL,mBAAmB,EACnB,WAAW,EAEX,mBAAmB,GACpB,MAAM,6BAA6B,CAAC;AAWrC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAgB,EAChB,QAAwB,EACxB,YAAgC,EAChC,SAA4B;IAE5B,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,MAAM,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAClC,0EAA0E;IAC1E,+DAA+D;IAC/D,0EAA0E;IAC1E,yEAAyE;IACzE,uEAAuE;IACvE,yEAAyE;IACzE,yDAAyD;IACzD,MAAM,IAAI,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9F,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,4EAA4E;IAC5E,2EAA2E;IAC3E,wEAAwE;IACxE,sEAAsE;IACtE,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,oEAAoE;IACpE,mDAAmD;IACnD,MAAM,MAAM,GAAG,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;IACzD,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACpD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACnC,wEAAwE;YACxE,0EAA0E;YAC1E,wEAAwE;YACxE,wEAAwE;YACxE,IAAI,CAAC,CAAC,EAAE,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7F,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5B,MAAM,QAAQ,GAAG,UAAU,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QAChE,MAAM,WAAW,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACpD,MAAM,IAAI,GAAG;YACX,KAAK;YACL,QAAQ;YACR,WAAW;YACX,oBAAoB,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,IAAI,YAAY,KAAK,KAAK,CAAC,EAAE;SAC1E,CAAC;QACF,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC;AAgCD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAgB,EAChB,CAAe;IAEf,MAAM,CAAC,GAAG,MAAM,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ;QACtB,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,CAAC,MAAM,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,SAAS,CAAC;IAC1E,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7B,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU;QACnC,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,OAAO,EAAE,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC;QAC1C,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,MAAM,CAAC;KACxC,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE;;;;GAIG","sourcesContent":["/**\n * What the list actually reads: one row per service, one verification cell per\n * feature.\n *\n * Both are gathered before anything is printed or serialized, because the same\n * numbers appear in `--json` and in the worklist footer — a footer computed\n * from a second read is a footer that can contradict the rows above it.\n */\nimport { existsSync } from \"node:fs\";\nimport { loadFile, type LoadedDoc } from \"../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../core/c4/resolve/service.js\";\nimport {\n maturityGaps,\n serviceMaturity,\n type Maturity,\n type MaturityInput,\n} from \"../../core/vocabulary/maturity.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"../../core/repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../../core/repo/paths.js\";\nimport { featureChecklist } from \"../../core/verify/checklist.js\";\nimport { readVerification } from \"../../core/verify/file.js\";\nimport { type AnsweredBy } from \"../../core/verify/answers.js\";\nimport {\n confirmedProvenance,\n tallyRecord,\n type VerificationVerdict,\n verificationVerdict,\n} from \"../../core/verify/record.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\nexport interface ServiceView extends MaturityInput {\n /** True when this service's `sources` can only be resolved from its own repo. */\n unverifiableFromHere: boolean;\n maturity: Maturity;\n /** What stands between this service and the next rung, in fix order. */\n missing: string[];\n}\n\n/**\n * Read the living landscape ONCE and decide, per service, whether an API is\n * expected of it. `serviceResolver` is handed the real service ids so an edge\n * drawn into a modelled container (`paymentService.api`) counts for the service\n * that owns it — the same resolution `validate` uses, so the two commands\n * cannot disagree about who is called.\n */\nexport async function serviceViews(\n docsDir: DocsDir,\n services: ServiceEntry[],\n boundService: string | undefined,\n preloaded?: LoadedDoc | null,\n): Promise<ServiceView[]> {\n const known = new Set(services.map((s) => s.id));\n const lp = landscapePath(docsDir);\n // `preloaded` is the landscape a caller already holds — `undefined` means\n // \"load it if it exists\", null \"there is none\", the convention\n // validateService's own `preloaded` follows. The fleet scorecard hands in\n // the batch-parsed doc because a healthy `validate --all` must spin ZERO\n // per-document workspaces (test/validate-batch-fallback.test.ts counts\n // them), and a second parse of a document that run already holds was the\n // exact per-path cost the batch loader exists to retire.\n const land = preloaded !== undefined ? preloaded : existsSync(lp) ? await loadFile(lp) : null;\n const called = new Set<string>();\n // Positive evidence only: with no landscape, or one that does not parse, we\n // know nothing about who is called and every service is assumed to have an\n // API — the conservative direction, because claiming a service needs no\n // contract is how a real one goes undocumented. This is the SAME rule\n // `landscapeEvidence` (core/vocabulary/maturity.ts) spells per service for\n // explore and the context pack — derived fleet-wide here in one pass (one\n // `called` set for every service) because calling the per-service partition\n // from this loop would re-walk the whole relationship list once per\n // service. If the rule moves, both spellings move.\n const proven = land !== null && land.errors.length === 0;\n if (proven) {\n const svcOf = serviceResolver(land.elements, known);\n for (const r of land.relationships) {\n // …including its self-edge rule: an edge whose two endpoints resolve to\n // one service is that service's own internal wiring, and counting it made\n // a service nobody calls owe an OpenAPI contract — `partial` here while\n // `loam context` (which reads the shared derivation) said `documented`.\n if (r.op !== undefined && svcOf(r.source) !== svcOf(r.target)) called.add(svcOf(r.target));\n }\n }\n\n return services.map((entry) => {\n const archSpec = existsSync(servicePathsAt(entry.dir).archSpec);\n const apiExpected = !proven || called.has(entry.id);\n const view = {\n entry,\n archSpec,\n apiExpected,\n unverifiableFromHere: entry.sources.declared && boundService !== entry.id,\n };\n const missing = maturityGaps(view);\n return { ...view, maturity: serviceMaturity(view), missing };\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* JSON */\n/* ------------------------------------------------------------------ */\n\nexport interface VerificationCell {\n state: \"recorded\" | \"stale\";\n /** The day the record was written. */\n recorded: string;\n /**\n * `verify --json`'s own three-valued verdict, through `verificationVerdict` —\n * not a fourth opinion computed here. `attested` is the one this column got\n * wrong: a scenario confirmed on an agent's word printed as `11/11` under a\n * heading that said `verified`, and `list --json` is how the fleet is graded.\n */\n verdict: VerificationVerdict;\n /** Both counts are `tallyRecord`'s — the record's `claims[]`, never its `summary:` block. */\n confirmed: number;\n claims: number;\n /** Of the confirmed, the scenario claims on an agent's word (`attestedClaims`). */\n attested: number;\n /**\n * Of the confirmed, one count per `answered_by` value (`confirmedProvenance`), every key\n * present. A different question from `attested` above, which is why both are here: `attested`\n * asks only about `scenario.tested` because that is what the verdict turns on, while this asks\n * who answered EVERY kind — and reading the first as if it were the second is how the fleet\n * scorecard credited agent-answered permission and operation claims to a test runner.\n */\n answered: Record<AnsweredBy, number>;\n}\n\n/**\n * The verification column. `readVerification` is one file read; the checklist\n * is derived only when there is a record to judge — on a fleet-sized repo most\n * features have none, and deriving N checklists to print N dashes is the cost\n * that would get this column dropped.\n *\n * An archived feature's record is frozen history (see `core/verify/record.ts`): archive\n * merged the feature's operations into the living openapi, so a re-derived\n * checklist can only mismatch — the record is reported as recorded, never\n * judged stale.\n */\nexport async function featureVerification(\n docsDir: DocsDir,\n f: FeatureEntry,\n): Promise<VerificationCell | null> {\n const v = await readVerification(f.dir);\n if (v === null) return null;\n const stale = f.archived\n ? false\n : (await featureChecklist(docsDir, f.dir, f.id)).digest !== v.checklist;\n const tally = tallyRecord(v);\n return {\n state: stale ? \"stale\" : \"recorded\",\n recorded: v.recorded,\n verdict: verificationVerdict(tally, stale),\n confirmed: tally.confirmed,\n claims: tally.claims,\n attested: tally.attested,\n answered: confirmedProvenance(v.claims),\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Text */\n/* ------------------------------------------------------------------ */\n\n/**\n * Fixed-width presence flags. `a` and `A` are deliberately the same letter in\n * two cases: the arch spec and the API are the two things a reader most often\n * mistakes for each other, and one is lowercase because it is optional.\n */\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* the docs lock, nothing outside it does, and the module prints nothing —
|
|
6
6
|
* the caller renders every arm.
|
|
7
7
|
*
|
|
8
|
-
* The
|
|
8
|
+
* The three refusals live UNDER the lock on purpose: the never-overwrite promise is a
|
|
9
9
|
* statement about the landscape as it is at the moment of the write, and an
|
|
10
10
|
* unlocked classification would be a TOCTOU against every other docs writer.
|
|
11
11
|
*/
|
|
@@ -16,6 +16,7 @@ import { isLandscapeStub } from "../../core/scaffold/landscape.js";
|
|
|
16
16
|
import { parseServiceId } from "../../core/kernel/ids/service.js";
|
|
17
17
|
import { landscapePath } from "../../core/repo/paths.js";
|
|
18
18
|
import { listFleetTree } from "../../core/repo/repo.js";
|
|
19
|
+
import { authoredRefusal } from "../../core/repo/tree/render/service/header.js";
|
|
19
20
|
import { DocsRepoUnavailableError } from "../../core/repo/state.js";
|
|
20
21
|
import { stageWrites } from "../../core/staging/commit.js";
|
|
21
22
|
import { InterruptedCommitError } from "../../core/staging/interrupted.js";
|
|
@@ -63,6 +64,38 @@ export async function commitSeed(req) {
|
|
|
63
64
|
if (inconsistency !== null)
|
|
64
65
|
return inconsistency;
|
|
65
66
|
const plan = await planSeedWrites(req, tree);
|
|
67
|
+
// THE OTHER never-overwrite promise, and the third refusal this window owns.
|
|
68
|
+
//
|
|
69
|
+
// `classifyLandscape` above keeps it for `architecture/landscape.likec4`;
|
|
70
|
+
// this keeps it for the two files seed does not share with the team at all —
|
|
71
|
+
// `architecture/subsystems.likec4` and `architecture/services.likec4` are
|
|
72
|
+
// filenames loam RESERVES inside a directory the team owns, and seed writes
|
|
73
|
+
// them whole. Until this line existed a `loam seed` into a repository that
|
|
74
|
+
// kept a hand-authored diagram at one of those paths destroyed it in
|
|
75
|
+
// silence: the same clobber reproduced through `loam subsystem new`, and the
|
|
76
|
+
// guard that caught it lived in `subsystem sync` alone.
|
|
77
|
+
//
|
|
78
|
+
// THE WHOLE SEED IS REFUSED, exactly as an edited landscape refuses it, and
|
|
79
|
+
// for the same reason: seed regenerates every file it owns wholesale from
|
|
80
|
+
// fleet.yaml, so landing the landscape and the markers while declining a
|
|
81
|
+
// views file would leave a tree whose very next `validate --all` — seed's
|
|
82
|
+
// own `next` command — reports `subsystem.views-stale` over a file no
|
|
83
|
+
// command could ever repair. `planSeedWrites` has staged nothing; the
|
|
84
|
+
// transaction below is the first byte, and it does not run.
|
|
85
|
+
if (plan.authored.length > 0) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
// `already-exists` for the reason commands/subsystem/txn/txn.ts gives at
|
|
89
|
+
// its own copy of this refusal: it is what that code has always meant —
|
|
90
|
+
// refusing to create over something that already exists, because
|
|
91
|
+
// overwriting would destroy work nobody asked to lose.
|
|
92
|
+
code: "already-exists",
|
|
93
|
+
message: plan.authored.map((file) => authoredRefusal(file)).join(" ") +
|
|
94
|
+
" NOTHING was written: not the landscape, not a subsystem marker, not a `.gitkeep`. Seed regenerates " +
|
|
95
|
+
"every file it owns wholesale, so it never proceeds past human work standing at one of its paths — the " +
|
|
96
|
+
"same promise `seed-landscape-edited` makes one file over.",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
66
99
|
const staged = await stageWrites(plan.writes);
|
|
67
100
|
const committed = await commitStaged({ root: docsDir, command: "seed", rerun: req.rerun, target: "landscape" }, staged, "seeded");
|
|
68
101
|
if (!committed.ok) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/commands/seed/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Seed's commit window: lock, recover, the under-lock provenance and\n * fleet-consistency checks, one journaled transaction. Modeled line for line\n * on commands/new/commit.ts and on the same seam: everything in here holds\n * the docs lock, nothing outside it does, and the module prints nothing —\n * the caller renders every arm.\n *\n * The checks live UNDER the lock on purpose: the never-overwrite promise is a\n * statement about the landscape as it is at the moment of the write, and an\n * unlocked classification would be a TOCTOU against every other docs writer.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport type { FleetSeed } from \"../../core/c4/seed/fleet-file.js\";\nimport { landscapeProvenance } from \"../../core/c4/seed/stamp.js\";\nimport { isLandscapeStub } from \"../../core/scaffold/landscape.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { parseServiceId } from \"../../core/kernel/ids/service.js\";\nimport { landscapePath } from \"../../core/repo/paths.js\";\nimport { listFleetTree } from \"../../core/repo/repo.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport type { FleetTree } from \"../../core/repo/tree/walk.js\";\nimport { stageWrites } from \"../../core/staging/commit.js\";\nimport { type CommitRecovery, InterruptedCommitError } from \"../../core/staging/interrupted.js\";\nimport { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from \"../../core/staging/lock.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport { planSeedWrites, type SeedPlanInput } from \"./plan.js\";\n\n/** The plan's input, plus the one fact only the transaction needs. */\nexport interface SeedCommitRequest extends SeedPlanInput {\n /** How a human re-runs this command — the transaction journal's `rerun`. */\n rerun: string;\n}\n\n/** What happened to architecture/landscape.likec4 — the three writes seed may prove safe. */\nexport type LandscapeDisposition = \"created\" | \"replaced-stub\" | \"regenerated\";\n\nexport type SeedCommit =\n | {\n ok: true;\n landscape: LandscapeDisposition;\n /** Absolute path of every file the transaction wrote, in write order. */\n written: string[];\n /**\n * Absolute path of every file the transaction REMOVED — only ever the\n * generated views file, when the resulting tree has no subsystems left\n * to view. Separate from `written` because \"+ path\" is a lie about a\n * delete.\n */\n removed: string[];\n /** Service ids split by whether this run created the directory. */\n services: { created: string[]; existing: string[] };\n recovered: CommitRecovery | null;\n }\n | {\n ok: false;\n code:\n | \"docs-busy\"\n | \"commit-interrupted\"\n | \"merge-failed\"\n | \"rollback-incomplete\"\n | \"seed-landscape-edited\";\n message: string;\n }\n /** The under-lock fleet-consistency preflight refusing; see `missingServices`. */\n | { ok: false; code: \"seed-file-invalid\"; message: string; missingServices: string[] }\n /** The docs repo went away between the command's gate and the locked read — new/commit.ts says why this is not a `code:` arm. */\n | { ok: false; repoGone: DocsRepoUnavailableError };\n\nexport async function commitSeed(req: SeedCommitRequest): Promise<SeedCommit> {\n const { docsDir } = req;\n let releaseLock: () => Promise<void>;\n try {\n releaseLock = await acquireDocsLockWaiting(docsDir, LOCK_WAIT_MS);\n } catch (err) {\n if (err instanceof DocsBusyError) return { ok: false, code: \"docs-busy\", message: err.message };\n throw err;\n }\n try {\n let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(docsDir);\n } catch (err) {\n if (!(err instanceof InterruptedCommitError)) throw err;\n return { ok: false, code: \"commit-interrupted\", message: err.message };\n }\n\n // Provenance first, then the tree: both read the repo as this run will\n // leave it, and both refusals must prove nothing was written.\n const landscape = await classifyLandscape(docsDir);\n if (\"refusal\" in landscape) {\n return { ok: false, code: \"seed-landscape-edited\", message: landscape.refusal };\n }\n\n let tree: FleetTree;\n try {\n tree = await listFleetTree(docsDir);\n } catch (err) {\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return { ok: false, repoGone: err };\n }\n const inconsistency = preflight(tree, req.seed);\n if (inconsistency !== null) return inconsistency;\n\n const plan = await planSeedWrites(req, tree);\n const staged = await stageWrites(plan.writes);\n const committed = await commitStaged(\n { root: docsDir, command: \"seed\", rerun: req.rerun, target: \"landscape\" },\n staged,\n \"seeded\",\n );\n if (!committed.ok) {\n // `committed.raced` is deliberately NOT remapped: the lock serialises\n // loam writers, so a fired CAS or a lost exclusive create here means an\n // out-of-band editor, and seed has no softer race answer the way `new`\n // has already-exists — merge-failed's \"nothing was written, re-run\" is\n // the honest sentence either way.\n return { ok: false, code: committed.code, message: committed.message };\n }\n return {\n ok: true,\n landscape: landscape.disposition,\n written: plan.writes.filter((w) => w.content !== null).map((w) => w.path),\n removed: plan.writes.filter((w) => w.content === null).map((w) => w.path),\n services: plan.services,\n recovered,\n };\n } finally {\n await releaseLock();\n }\n}\n\n/**\n * May seed write the landscape? Absent and untouched-scaffold-stub are the\n * two states with provably no human work in them; a matching stamp digest is\n * the third (`core/c4/seed/stamp.ts`). Everything else refuses.\n */\nasync function classifyLandscape(\n docsDir: DocsDir,\n): Promise<{ disposition: LandscapeDisposition } | { refusal: string }> {\n const path = landscapePath(docsDir);\n if (!existsSync(path)) return { disposition: \"created\" };\n const text = (await readFile(path)).toString(\"utf8\");\n // `isLandscapeStub`, never a byte-compare of our own: that function IS the\n // repo's answer to \"is this still the scaffold's untouched map\", it is what\n // `loam status` reads before telling the reader to run seed, and it is\n // suffix-matched and CRLF-normalised for two reasons seed shares —\n // `migrate-openspec` prepends a preamble to the identical stub, and a\n // checkout under `core.autocrlf` (Git for Windows' installer default)\n // rewrites every line without touching a fact. A stricter compare here made\n // status and seed contradict each other about the same file.\n if (isLandscapeStub(text)) return { disposition: \"replaced-stub\" };\n const provenance = landscapeProvenance(text);\n if (provenance === \"seed-stamped\") return { disposition: \"regenerated\" };\n return {\n refusal:\n `architecture/landscape.likec4 ${\n provenance === \"seed-edited\"\n ? \"was seeded but has been hand-edited since — the line-1 stamp's digest no longer matches the content\"\n : \"was not written by `loam seed`: it carries no line-1 stamp and is not the scaffold's untouched stub\"\n }. Seed never overwrites human work; nothing was written. Either fold the edits into ` +\n `fleet.yaml and delete the file, then re-run — or keep the hand-authored map and stop ` +\n `using seed here.`,\n };\n}\n\n/**\n * The fleet-consistency preflight. Seed regenerates the landscape WHOLESALE —\n * that is the only posture the stamp can prove safe — so every existing\n * services/<id>/ must be named in fleet.yaml, or the regenerate would orphan\n * it into `landscape.service-unmodelled`. And the file's names must not\n * collide with what the tree already holds under a different kind: one flat\n * namespace, the same rule the walk enforces.\n */\nfunction preflight(tree: FleetTree, seed: FleetSeed): SeedCommit | null {\n const treeSubs = new Set(tree.subsystems.map((s) => s.name));\n const treeIds = new Set(tree.services.map((s) => s.id as string));\n\n // The collision scan runs BEFORE the missing-service scan, and the order is\n // load-bearing. A name the file declares as a subsystem or an external while\n // the tree holds it as a service DIRECTORY is both facts at once: it is a\n // namespace collision, and it is also an existing service the file does not\n // name. With the missing scan first, the caller was told to paste that name\n // into `services:` — advice that walks straight into `seed-duplicate-service`\n // on the next run, because the file already declares it as something else.\n const collisions: string[] = [];\n for (const sub of seed.subsystems) {\n if (treeIds.has(sub as string)) collisions.push(`subsystem '${sub}' is an existing service directory`);\n }\n for (const s of seed.services) {\n if (treeSubs.has(s.id as string)) collisions.push(`service '${s.id}' is an existing subsystem directory`);\n }\n for (const x of seed.externals) {\n if (treeIds.has(x)) collisions.push(`external '${x}' is an existing service directory`);\n if (treeSubs.has(x)) collisions.push(`external '${x}' is an existing subsystem directory`);\n }\n if (collisions.length > 0) {\n return {\n ok: false,\n code: \"seed-file-invalid\",\n message:\n `fleet.yaml collides with the services/ tree — service ids, subsystem names and externals ` +\n `share one flat namespace: ${collisions.join(\"; \")}. Rename the fleet.yaml side (or the ` +\n `directory, with \\`loam subsystem rename <from> <to>\\`), then re-run. Nothing was written.`,\n missingServices: [],\n };\n }\n\n const ids = new Set(seed.services.map((s) => s.id as string));\n const missing = tree.services.map((s) => s.id as string).filter((id) => !ids.has(id)).sort();\n if (missing.length === 0) return null;\n // The ids are partitioned because one half cannot take the other's advice.\n // `tree.services` carries RAW ids — a directory whose name fails the service\n // grammar is still a service to the walk, by design — and \"add it to\n // services: exactly as spelled\" is impossible for one of those: the fleet\n // file would refuse the entry. Without this split, seed was simply unusable\n // on such a repo and never said so.\n const legal = missing.filter((id) => parseServiceId(id, \"services entry\").ok);\n const illegal = missing.filter((id) => !parseServiceId(id, \"services entry\").ok);\n const fixes: string[] = [];\n if (legal.length > 0) fixes.push(`add ${legal.join(\", \")} to services:, exactly as spelled`);\n if (illegal.length > 0) {\n fixes.push(\n `rename the ${illegal.length === 1 ? \"directory\" : \"directories\"} ${illegal.join(\", \")} ` +\n `first — ${illegal.length === 1 ? \"that name is not a legal service id\" : \"those names are not legal service ids\"}, ` +\n `so no services: entry can name ${illegal.length === 1 ? \"it\" : \"them\"}`,\n );\n }\n return {\n ok: false,\n code: \"seed-file-invalid\",\n message:\n `fleet.yaml must name every existing service — seed regenerates the landscape wholesale, ` +\n `and these services/ directories are not in it: ${missing.join(\", \")}. ` +\n `To fix: ${fixes.join(\"; and \")}. Then re-run. Nothing was written.`,\n missingServices: missing,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/commands/seed/commit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Seed's commit window: lock, recover, the under-lock provenance and\n * fleet-consistency checks, one journaled transaction. Modeled line for line\n * on commands/new/commit.ts and on the same seam: everything in here holds\n * the docs lock, nothing outside it does, and the module prints nothing —\n * the caller renders every arm.\n *\n * The three refusals live UNDER the lock on purpose: the never-overwrite promise is a\n * statement about the landscape as it is at the moment of the write, and an\n * unlocked classification would be a TOCTOU against every other docs writer.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport type { FleetSeed } from \"../../core/c4/seed/fleet-file.js\";\nimport { landscapeProvenance } from \"../../core/c4/seed/stamp.js\";\nimport { isLandscapeStub } from \"../../core/scaffold/landscape.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { parseServiceId } from \"../../core/kernel/ids/service.js\";\nimport { landscapePath } from \"../../core/repo/paths.js\";\nimport { listFleetTree } from \"../../core/repo/repo.js\";\nimport { authoredRefusal } from \"../../core/repo/tree/render/service/header.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport type { FleetTree } from \"../../core/repo/tree/walk.js\";\nimport { stageWrites } from \"../../core/staging/commit.js\";\nimport { type CommitRecovery, InterruptedCommitError } from \"../../core/staging/interrupted.js\";\nimport { acquireDocsLockWaiting, DocsBusyError, LOCK_WAIT_MS } from \"../../core/staging/lock.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../core/staging/txn/transaction.js\";\nimport { planSeedWrites, type SeedPlanInput } from \"./plan.js\";\n\n/** The plan's input, plus the one fact only the transaction needs. */\nexport interface SeedCommitRequest extends SeedPlanInput {\n /** How a human re-runs this command — the transaction journal's `rerun`. */\n rerun: string;\n}\n\n/** What happened to architecture/landscape.likec4 — the three writes seed may prove safe. */\nexport type LandscapeDisposition = \"created\" | \"replaced-stub\" | \"regenerated\";\n\nexport type SeedCommit =\n | {\n ok: true;\n landscape: LandscapeDisposition;\n /** Absolute path of every file the transaction wrote, in write order. */\n written: string[];\n /**\n * Absolute path of every file the transaction REMOVED — only ever the\n * generated views file, when the resulting tree has no subsystems left\n * to view. Separate from `written` because \"+ path\" is a lie about a\n * delete.\n */\n removed: string[];\n /** Service ids split by whether this run created the directory. */\n services: { created: string[]; existing: string[] };\n recovered: CommitRecovery | null;\n }\n | {\n ok: false;\n code:\n | \"docs-busy\"\n | \"commit-interrupted\"\n | \"merge-failed\"\n | \"rollback-incomplete\"\n | \"seed-landscape-edited\"\n /** A hand-authored document standing at one of the two generated views paths — see the refusal in `commitSeed`. */\n | \"already-exists\";\n message: string;\n }\n /** The under-lock fleet-consistency preflight refusing; see `missingServices`. */\n | { ok: false; code: \"seed-file-invalid\"; message: string; missingServices: string[] }\n /** The docs repo went away between the command's gate and the locked read — new/commit.ts says why this is not a `code:` arm. */\n | { ok: false; repoGone: DocsRepoUnavailableError };\n\nexport async function commitSeed(req: SeedCommitRequest): Promise<SeedCommit> {\n const { docsDir } = req;\n let releaseLock: () => Promise<void>;\n try {\n releaseLock = await acquireDocsLockWaiting(docsDir, LOCK_WAIT_MS);\n } catch (err) {\n if (err instanceof DocsBusyError) return { ok: false, code: \"docs-busy\", message: err.message };\n throw err;\n }\n try {\n let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(docsDir);\n } catch (err) {\n if (!(err instanceof InterruptedCommitError)) throw err;\n return { ok: false, code: \"commit-interrupted\", message: err.message };\n }\n\n // Provenance first, then the tree: both read the repo as this run will\n // leave it, and both refusals must prove nothing was written.\n const landscape = await classifyLandscape(docsDir);\n if (\"refusal\" in landscape) {\n return { ok: false, code: \"seed-landscape-edited\", message: landscape.refusal };\n }\n\n let tree: FleetTree;\n try {\n tree = await listFleetTree(docsDir);\n } catch (err) {\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return { ok: false, repoGone: err };\n }\n const inconsistency = preflight(tree, req.seed);\n if (inconsistency !== null) return inconsistency;\n\n const plan = await planSeedWrites(req, tree);\n // THE OTHER never-overwrite promise, and the third refusal this window owns.\n //\n // `classifyLandscape` above keeps it for `architecture/landscape.likec4`;\n // this keeps it for the two files seed does not share with the team at all —\n // `architecture/subsystems.likec4` and `architecture/services.likec4` are\n // filenames loam RESERVES inside a directory the team owns, and seed writes\n // them whole. Until this line existed a `loam seed` into a repository that\n // kept a hand-authored diagram at one of those paths destroyed it in\n // silence: the same clobber reproduced through `loam subsystem new`, and the\n // guard that caught it lived in `subsystem sync` alone.\n //\n // THE WHOLE SEED IS REFUSED, exactly as an edited landscape refuses it, and\n // for the same reason: seed regenerates every file it owns wholesale from\n // fleet.yaml, so landing the landscape and the markers while declining a\n // views file would leave a tree whose very next `validate --all` — seed's\n // own `next` command — reports `subsystem.views-stale` over a file no\n // command could ever repair. `planSeedWrites` has staged nothing; the\n // transaction below is the first byte, and it does not run.\n if (plan.authored.length > 0) {\n return {\n ok: false,\n // `already-exists` for the reason commands/subsystem/txn/txn.ts gives at\n // its own copy of this refusal: it is what that code has always meant —\n // refusing to create over something that already exists, because\n // overwriting would destroy work nobody asked to lose.\n code: \"already-exists\",\n message:\n plan.authored.map((file) => authoredRefusal(file)).join(\" \") +\n \" NOTHING was written: not the landscape, not a subsystem marker, not a `.gitkeep`. Seed regenerates \" +\n \"every file it owns wholesale, so it never proceeds past human work standing at one of its paths — the \" +\n \"same promise `seed-landscape-edited` makes one file over.\",\n };\n }\n const staged = await stageWrites(plan.writes);\n const committed = await commitStaged(\n { root: docsDir, command: \"seed\", rerun: req.rerun, target: \"landscape\" },\n staged,\n \"seeded\",\n );\n if (!committed.ok) {\n // `committed.raced` is deliberately NOT remapped: the lock serialises\n // loam writers, so a fired CAS or a lost exclusive create here means an\n // out-of-band editor, and seed has no softer race answer the way `new`\n // has already-exists — merge-failed's \"nothing was written, re-run\" is\n // the honest sentence either way.\n return { ok: false, code: committed.code, message: committed.message };\n }\n return {\n ok: true,\n landscape: landscape.disposition,\n written: plan.writes.filter((w) => w.content !== null).map((w) => w.path),\n removed: plan.writes.filter((w) => w.content === null).map((w) => w.path),\n services: plan.services,\n recovered,\n };\n } finally {\n await releaseLock();\n }\n}\n\n/**\n * May seed write the landscape? Absent and untouched-scaffold-stub are the\n * two states with provably no human work in them; a matching stamp digest is\n * the third (`core/c4/seed/stamp.ts`). Everything else refuses.\n */\nasync function classifyLandscape(\n docsDir: DocsDir,\n): Promise<{ disposition: LandscapeDisposition } | { refusal: string }> {\n const path = landscapePath(docsDir);\n if (!existsSync(path)) return { disposition: \"created\" };\n const text = (await readFile(path)).toString(\"utf8\");\n // `isLandscapeStub`, never a byte-compare of our own: that function IS the\n // repo's answer to \"is this still the scaffold's untouched map\", it is what\n // `loam status` reads before telling the reader to run seed, and it is\n // suffix-matched and CRLF-normalised for two reasons seed shares —\n // `migrate-openspec` prepends a preamble to the identical stub, and a\n // checkout under `core.autocrlf` (Git for Windows' installer default)\n // rewrites every line without touching a fact. A stricter compare here made\n // status and seed contradict each other about the same file.\n if (isLandscapeStub(text)) return { disposition: \"replaced-stub\" };\n const provenance = landscapeProvenance(text);\n if (provenance === \"seed-stamped\") return { disposition: \"regenerated\" };\n return {\n refusal:\n `architecture/landscape.likec4 ${\n provenance === \"seed-edited\"\n ? \"was seeded but has been hand-edited since — the line-1 stamp's digest no longer matches the content\"\n : \"was not written by `loam seed`: it carries no line-1 stamp and is not the scaffold's untouched stub\"\n }. Seed never overwrites human work; nothing was written. Either fold the edits into ` +\n `fleet.yaml and delete the file, then re-run — or keep the hand-authored map and stop ` +\n `using seed here.`,\n };\n}\n\n/**\n * The fleet-consistency preflight. Seed regenerates the landscape WHOLESALE —\n * that is the only posture the stamp can prove safe — so every existing\n * services/<id>/ must be named in fleet.yaml, or the regenerate would orphan\n * it into `landscape.service-unmodelled`. And the file's names must not\n * collide with what the tree already holds under a different kind: one flat\n * namespace, the same rule the walk enforces.\n */\nfunction preflight(tree: FleetTree, seed: FleetSeed): SeedCommit | null {\n const treeSubs = new Set(tree.subsystems.map((s) => s.name));\n const treeIds = new Set(tree.services.map((s) => s.id as string));\n\n // The collision scan runs BEFORE the missing-service scan, and the order is\n // load-bearing. A name the file declares as a subsystem or an external while\n // the tree holds it as a service DIRECTORY is both facts at once: it is a\n // namespace collision, and it is also an existing service the file does not\n // name. With the missing scan first, the caller was told to paste that name\n // into `services:` — advice that walks straight into `seed-duplicate-service`\n // on the next run, because the file already declares it as something else.\n const collisions: string[] = [];\n for (const sub of seed.subsystems) {\n if (treeIds.has(sub as string)) collisions.push(`subsystem '${sub}' is an existing service directory`);\n }\n for (const s of seed.services) {\n if (treeSubs.has(s.id as string)) collisions.push(`service '${s.id}' is an existing subsystem directory`);\n }\n for (const x of seed.externals) {\n if (treeIds.has(x)) collisions.push(`external '${x}' is an existing service directory`);\n if (treeSubs.has(x)) collisions.push(`external '${x}' is an existing subsystem directory`);\n }\n if (collisions.length > 0) {\n return {\n ok: false,\n code: \"seed-file-invalid\",\n message:\n `fleet.yaml collides with the services/ tree — service ids, subsystem names and externals ` +\n `share one flat namespace: ${collisions.join(\"; \")}. Rename the fleet.yaml side (or the ` +\n `directory, with \\`loam subsystem rename <from> <to>\\`), then re-run. Nothing was written.`,\n missingServices: [],\n };\n }\n\n const ids = new Set(seed.services.map((s) => s.id as string));\n const missing = tree.services.map((s) => s.id as string).filter((id) => !ids.has(id)).sort();\n if (missing.length === 0) return null;\n // The ids are partitioned because one half cannot take the other's advice.\n // `tree.services` carries RAW ids — a directory whose name fails the service\n // grammar is still a service to the walk, by design — and \"add it to\n // services: exactly as spelled\" is impossible for one of those: the fleet\n // file would refuse the entry. Without this split, seed was simply unusable\n // on such a repo and never said so.\n const legal = missing.filter((id) => parseServiceId(id, \"services entry\").ok);\n const illegal = missing.filter((id) => !parseServiceId(id, \"services entry\").ok);\n const fixes: string[] = [];\n if (legal.length > 0) fixes.push(`add ${legal.join(\", \")} to services:, exactly as spelled`);\n if (illegal.length > 0) {\n fixes.push(\n `rename the ${illegal.length === 1 ? \"directory\" : \"directories\"} ${illegal.join(\", \")} ` +\n `first — ${illegal.length === 1 ? \"that name is not a legal service id\" : \"those names are not legal service ids\"}, ` +\n `so no services: entry can name ${illegal.length === 1 ? \"it\" : \"them\"}`,\n );\n }\n return {\n ok: false,\n code: \"seed-file-invalid\",\n message:\n `fleet.yaml must name every existing service — seed regenerates the landscape wholesale, ` +\n `and these services/ directories are not in it: ${missing.join(\", \")}. ` +\n `To fix: ${fixes.join(\"; and \")}. Then re-run. Nothing was written.`,\n missingServices: missing,\n };\n}\n"]}
|
|
@@ -1,21 +1,125 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a seed run will write, decided in full before a byte lands. Split from
|
|
3
3
|
* `./commit.ts` on the phase seam: that module owns the WINDOW — the lock, the
|
|
4
|
-
* interrupted-commit recovery, the
|
|
4
|
+
* interrupted-commit recovery, the refusals and the transaction — while
|
|
5
5
|
* this one answers the single question "given the tree as it is and the fleet
|
|
6
6
|
* file as it reads, which files change and how". Nothing here acquires
|
|
7
7
|
* anything, and nothing here writes: it is called under the lock and hands
|
|
8
8
|
* back a plan the transaction executes or rolls back whole.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { join } from "node:path";
|
|
10
|
+
import { copyFile, mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
13
|
+
import { architectureDir, architectureProjectDocuments } from "../../core/c4/project/architecture.js";
|
|
14
|
+
import { loadProject } from "../../core/c4/project/load.js";
|
|
15
|
+
import { repoPath } from "../../core/envelope/json.js";
|
|
13
16
|
import { serviceDirOf } from "../../core/kernel/ids/dirs.js";
|
|
14
|
-
import { landscapePath, servicePathsUnder, servicesDir, subsystemPathUnder, subsystemViewsPath, unfiledServicePaths, } from "../../core/repo/paths.js";
|
|
17
|
+
import { landscapePath, servicePathsUnder, servicesDir, serviceViewsPath, subsystemPathUnder, subsystemViewsPath, unfiledServicePaths, } from "../../core/repo/paths.js";
|
|
15
18
|
import { SUBSYSTEM_MARKER } from "../../core/repo/tree/marker.js";
|
|
16
|
-
import {
|
|
19
|
+
import { readGeneratedFile } from "../../core/repo/tree/render/service/header.js";
|
|
20
|
+
import { renderServiceOverviews, renderSubsystemViews, viewsAgree, } from "../../core/repo/tree/render/views.js";
|
|
17
21
|
import { TEMP_FILE_RE } from "../../core/staging/commit.js";
|
|
18
22
|
import { planWrite } from "../../core/staging/writes.js";
|
|
23
|
+
/**
|
|
24
|
+
* The facts the generated views are rendered from: the `architecture/` PROJECT
|
|
25
|
+
* as this commit leaves it — the sealed landscape standing in for the file it is
|
|
26
|
+
* about to become, beside every other `.likec4` the directory already holds —
|
|
27
|
+
* and never the sealed landscape alone.
|
|
28
|
+
*
|
|
29
|
+
* WHY THE PROJECT. `subsystem.views-stale` (`validate --all`) and `loam
|
|
30
|
+
* subsystem sync` both recompute the expected bytes from `loadArchitecture` —
|
|
31
|
+
* the landscape merged with every other `architecture/**` document, the
|
|
32
|
+
* generated file excluded — and the render asks that document set which global
|
|
33
|
+
* style ids it declares. A repository's `global { styleGroup subsystems { … } }`
|
|
34
|
+
* naturally lives in a sibling such as `architecture/usecases/palette.likec4`,
|
|
35
|
+
* since seed regenerates the landscape wholesale, so it was in the grader's
|
|
36
|
+
* census and not in seed's: a seed into such a repo wrote a views file the very
|
|
37
|
+
* next `validate --all` — seed's own first `next` command — graded stale, and
|
|
38
|
+
* re-seeding wrote the same bytes again, the one loop no `next` could leave.
|
|
39
|
+
* Reading the same documents the grader reads is what makes the shared render
|
|
40
|
+
* shared in fact rather than in name (commands/subsystem/txn/views.ts's banner).
|
|
41
|
+
*
|
|
42
|
+
* NO SIBLING, NO WORKSPACE: the landscape's own facts, already in hand from the
|
|
43
|
+
* self-check, ARE the project's, and the common case pays one readdir.
|
|
44
|
+
*
|
|
45
|
+
* STAGED, for `core/c4/project/staged.ts`'s reason: `loadProject` copies
|
|
46
|
+
* documents from disk keyed on their path relative to a base, and the landscape
|
|
47
|
+
* here exists only in memory. That module's staging is bound to a feature's
|
|
48
|
+
* merge preview, so the same handful of copies is made here once more — the
|
|
49
|
+
* temp tree is gone before this returns, and nothing under the docs repo is
|
|
50
|
+
* touched.
|
|
51
|
+
*
|
|
52
|
+
* FAIL CLOSED to the landscape's own facts. A project that does not parse (a
|
|
53
|
+
* sibling carrying an error of its own, or a copy the temp directory refused)
|
|
54
|
+
* declares nothing loam can rely on, so the render carries exactly what it
|
|
55
|
+
* carried before the project was asked, and the seed still lands: that sibling
|
|
56
|
+
* is `landscape.invalid`'s to name on the next `validate --all`, and a seed
|
|
57
|
+
* refusing over it would make seed the first command to grade a file it does
|
|
58
|
+
* not own. Nothing is hidden by the fallback: the grader compares the views
|
|
59
|
+
* file only once the project has parsed (`validate/fleet/landscape.ts`), so
|
|
60
|
+
* the repo sees the one finding that names the sibling, never a stale-views
|
|
61
|
+
* finding over a file no command could have rendered differently.
|
|
62
|
+
*/
|
|
63
|
+
async function projectFacts(req) {
|
|
64
|
+
const arch = architectureDir(req.docsDir);
|
|
65
|
+
const landscape = resolve(landscapePath(req.docsDir));
|
|
66
|
+
// The PROJECT's documents, root `exclude` applied — `loadArchitecture`'s own
|
|
67
|
+
// list minus the landscape, which is staged from memory just below. Walking
|
|
68
|
+
// `architecture/` directly skipped that filter, and here that is a WRONG
|
|
69
|
+
// answer rather than an over-scan: a `palette.likec4` under an excluded path
|
|
70
|
+
// is a document the renderer never loads, so its `global styleGroup` id put
|
|
71
|
+
// `global style subsystems` into the generated views file — a name the fleet
|
|
72
|
+
// project cannot resolve — and the very next `validate --all` (seed's own
|
|
73
|
+
// `next` command) graded that file against the same set THIS one now reads.
|
|
74
|
+
// The W5b defect, one command over.
|
|
75
|
+
const siblings = (await architectureProjectDocuments(req.docsDir)).filter((p) => resolve(p) !== landscape);
|
|
76
|
+
if (siblings.length === 0)
|
|
77
|
+
return req.map;
|
|
78
|
+
// The temp directory is made INSIDE the try, not one line above it: a full
|
|
79
|
+
// disk, a read-only temp root, or a TMPDIR naming a directory that no longer
|
|
80
|
+
// exists rejects `mkdtemp`, and outside the try that rejection left `seed` as
|
|
81
|
+
// the generic `internal` refusal on a repository where nothing is wrong — the
|
|
82
|
+
// defect docs/CODE-STYLE.md's "read inside the try that handles the read" rule
|
|
83
|
+
// was written from.
|
|
84
|
+
let root = null;
|
|
85
|
+
try {
|
|
86
|
+
root = await mkdtemp(join(tmpdir(), "loam-seed-"));
|
|
87
|
+
const staged = [];
|
|
88
|
+
for (const path of [landscape, ...siblings]) {
|
|
89
|
+
const dest = join(root, relative(arch, path));
|
|
90
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
91
|
+
// The landscape is staged from the bytes this commit writes, never from
|
|
92
|
+
// the file on disk — which may be the scaffold's stub, a stamped
|
|
93
|
+
// predecessor, or absent — so the project parsed is the one the grader
|
|
94
|
+
// will read after the commit.
|
|
95
|
+
if (path === landscape)
|
|
96
|
+
await writeFile(dest, req.landscape, "utf8");
|
|
97
|
+
else
|
|
98
|
+
await copyFile(path, dest);
|
|
99
|
+
staged.push(dest);
|
|
100
|
+
}
|
|
101
|
+
const project = await loadProject(root, staged);
|
|
102
|
+
// RELATIONSHIPS travel with the elements, and dropping them is silent: they
|
|
103
|
+
// are what each per-service overview computes its peer set from, so a facts
|
|
104
|
+
// record without them renders every service view peerless — the service and
|
|
105
|
+
// its own interior, and nothing it touches — with no error anywhere
|
|
106
|
+
// (`core/repo/tree/render/views.ts` states the consequence beside the
|
|
107
|
+
// optional field). The fallback `req.map` is the whole self-check document
|
|
108
|
+
// and already carries them.
|
|
109
|
+
return project.clean
|
|
110
|
+
? { elements: project.elements, globalStyles: project.globalStyles, relationships: project.relationships }
|
|
111
|
+
: req.map;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// `loadProject` throws on an error it cannot attribute to a document, and
|
|
115
|
+
// a full disk fails the staging above; both are the fail-closed arm.
|
|
116
|
+
return req.map;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
if (root !== null)
|
|
120
|
+
await rm(root, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
19
123
|
/**
|
|
20
124
|
* Does this existing service directory hold nothing git would keep? An empty
|
|
21
125
|
* directory is a service to the walk but nothing at all to git, so a landscape
|
|
@@ -122,12 +226,53 @@ export async function planSeedWrites(req, tree) {
|
|
|
122
226
|
// to disk that writes.ts forbids; a views file whose bytes are not UTF-8
|
|
123
227
|
// therefore fails the compare and is regenerated, which is the right answer
|
|
124
228
|
// for it anyway.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
229
|
+
//
|
|
230
|
+
// BOTH generated files, from ONE read of the project: `architecture/services.likec4`
|
|
231
|
+
// is the same contract one file over — one overview view per service the map
|
|
232
|
+
// draws, absent when it draws none — and a seed that landed only the subsystem
|
|
233
|
+
// views would send its caller, through its own `next` list, into a
|
|
234
|
+
// `subsystem.views-stale` no `seed` could clear. This is where the seeded
|
|
235
|
+
// services first exist AND the sealed landscape first says what they are, so
|
|
236
|
+
// it is the only moment the pair can be rendered together.
|
|
237
|
+
const facts = await projectFacts(req);
|
|
238
|
+
const authored = [
|
|
239
|
+
await stageGenerated(writes, subsystemViewsPath(docsDir), renderSubsystemViews(plannedTree, facts)),
|
|
240
|
+
await stageGenerated(writes, serviceViewsPath(docsDir), renderServiceOverviews(plannedTree, facts)),
|
|
241
|
+
].filter((path) => path !== null);
|
|
242
|
+
return {
|
|
243
|
+
writes,
|
|
244
|
+
services: { created: created.sort(), existing: existing.sort() },
|
|
245
|
+
authored: authored.map((path) => repoPath(docsDir, path)),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Append the write for ONE generated file, or nothing when the file already
|
|
250
|
+
* says it — the comparison the block above argues for, spelled once so the two
|
|
251
|
+
* generated files cannot drift into two rules. Answers the path when the file
|
|
252
|
+
* standing there is SOMEBODY ELSE'S, and stages nothing for it.
|
|
253
|
+
*
|
|
254
|
+
* OWNERSHIP FIRST, before the compare, exactly as `commands/subsystem/sync.ts`
|
|
255
|
+
* asks it — through the same reader, from beneath every writer
|
|
256
|
+
* (`core/repo/tree/render/service/header.ts`). Both of these are filenames loam
|
|
257
|
+
* RESERVES inside `architecture/`, a directory the team owns, and seed rewrites
|
|
258
|
+
* them whole; until this line existed a `loam seed` into a repository that kept
|
|
259
|
+
* a hand-authored diagram at one of those paths destroyed it without a word.
|
|
260
|
+
* That is the same promise `seed-landscape-edited` makes one file over — seed
|
|
261
|
+
* never overwrites human work — and it was missing for the two files seed
|
|
262
|
+
* reserves rather than shares.
|
|
263
|
+
*
|
|
264
|
+
* It is checked before the compare and not after, so that "the file already says
|
|
265
|
+
* this" can never vouch for a file loam did not write. An authored file cannot
|
|
266
|
+
* in fact agree with a render that opens with loam's mark, but a reader must not
|
|
267
|
+
* have to prove that to know the guard holds.
|
|
268
|
+
*/
|
|
269
|
+
async function stageGenerated(writes, path, content) {
|
|
270
|
+
const { text: onDisk, authored } = await readGeneratedFile(path);
|
|
271
|
+
if (authored)
|
|
272
|
+
return path;
|
|
273
|
+
if (viewsAgree(onDisk, content))
|
|
274
|
+
return null;
|
|
275
|
+
writes.push({ path, content: content === null ? null : Buffer.from(content, "utf8") });
|
|
276
|
+
return null;
|
|
132
277
|
}
|
|
133
278
|
//# sourceMappingURL=plan.js.map
|