@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
|
@@ -15,49 +15,74 @@
|
|
|
15
15
|
import { existsSync } from "node:fs";
|
|
16
16
|
import {} from "../c4/likec4.js";
|
|
17
17
|
import { serviceResolver } from "../c4/resolve/service.js";
|
|
18
|
-
import { ACTOR_KINDS, EXTERNAL_TAG, landscapeEvidence, maturityGaps, serviceMaturity, MATURITY_LADDER, } from "../vocabulary/maturity.js";
|
|
18
|
+
import { ACTOR_KINDS, EXTERNAL_TAG, INFRA_KINDS, landscapeEvidence, maturityGaps, serviceMaturity, MATURITY_LADDER, } from "../vocabulary/maturity.js";
|
|
19
19
|
import { compareIds } from "../repo/entries.js";
|
|
20
20
|
import { landscapePath, servicePathsAt } from "../repo/paths.js";
|
|
21
21
|
import { enumeratedServices } from "../repo/service-target.js";
|
|
22
|
+
import { ancestorIds } from "../kernel/ids/fqn/ancestors.js";
|
|
22
23
|
/** Is this rung at or above `documented` — the fixed threshold check 1 grades against? */
|
|
23
24
|
export function atLeastDocumented(m) {
|
|
24
25
|
return MATURITY_LADDER.indexOf(m) >= MATURITY_LADDER.indexOf("documented");
|
|
25
26
|
}
|
|
26
|
-
/** The id itself, then each dotted ancestor — the same walk `serviceResolver` resolves through. */
|
|
27
|
-
function selfAndAncestors(id) {
|
|
28
|
-
const out = [id];
|
|
29
|
-
for (let dot = id.lastIndexOf("."); dot !== -1; dot = id.lastIndexOf(".", dot - 1)) {
|
|
30
|
-
out.push(id.slice(0, dot));
|
|
31
|
-
}
|
|
32
|
-
return out;
|
|
33
|
-
}
|
|
34
27
|
/**
|
|
35
|
-
* What the map says about one edge END: does it stand for a person,
|
|
36
|
-
* tagged `#external`? Walked up the ancestor chain
|
|
37
|
-
* usually written on the containing system while
|
|
38
|
-
* nested element — but the walk STOPS at the first
|
|
39
|
-
* service (an explicit binding or a title naming a
|
|
40
|
-
* `serviceResolver`'s own first two rungs, checked after that
|
|
41
|
-
* facts are read). What sits above the service boundary is a
|
|
42
|
-
* grouping's `#external` is the grouping's claim, not the
|
|
43
|
-
* the stop, an adopted service drawn inside an `#external`
|
|
44
|
-
* from `gate.partner-undocumented` and its real rung hidden.
|
|
28
|
+
* What the map says about one edge END: does it stand for a person, is it
|
|
29
|
+
* INFRASTRUCTURE, and is it tagged `#external`? Walked up the ancestor chain
|
|
30
|
+
* because all three facts are usually written on the containing system while
|
|
31
|
+
* the edge is drawn from a nested element — but the walk STOPS at the first
|
|
32
|
+
* element that stands for a service (an explicit binding or a title naming a
|
|
33
|
+
* real directory, `serviceResolver`'s own first two rungs, checked after that
|
|
34
|
+
* element's own facts are read). What sits above the service boundary is a
|
|
35
|
+
* grouping, and a grouping's `#external` is the grouping's claim, not the
|
|
36
|
+
* service's: without the stop, an adopted service drawn inside an `#external`
|
|
37
|
+
* zone was exempted from `gate.partner-undocumented` and its real rung hidden.
|
|
38
|
+
*
|
|
39
|
+
* `infra` is read only off an element that stands for NO service AND sits
|
|
40
|
+
* inside none, which is the asymmetry the `standsFor` local exists for. That is
|
|
41
|
+
* `commands/validate/fleet/census.ts` in two steps and in its order:
|
|
42
|
+
* `serviceLevelElements` drops everything drawn inside a service BEFORE
|
|
43
|
+
* `drawnSystems` ever applies the kind test, so a service's own interior never
|
|
44
|
+
* reaches it. Both halves are load-bearing. A `database` bound with
|
|
45
|
+
* `metadata { service '<id>' }` is an adopted service that happens to be drawn
|
|
46
|
+
* as a store — its side of the join IS recorded, and dropping it would hide a
|
|
47
|
+
* real partner's rung. And a store drawn INSIDE its owner — `orderService.db`,
|
|
48
|
+
* the ordinary way a team draws a private datastore — is that service's
|
|
49
|
+
* interior: reading the kind off it convicted the WHOLE endpoint as
|
|
50
|
+
* infrastructure, and `partnerScan` then dropped order-service from the partner
|
|
51
|
+
* set, so a service this gate exists to grade vanished because of how somebody
|
|
52
|
+
* drew its storage. The kind is the fallback for an element nothing else
|
|
53
|
+
* answers for, never the override.
|
|
45
54
|
*/
|
|
46
55
|
function endFacts(byId, known, id) {
|
|
47
56
|
let actor = false;
|
|
57
|
+
let infra = false;
|
|
48
58
|
let external = false;
|
|
49
|
-
|
|
59
|
+
// The id itself, then each dotted ancestor — the same walk `serviceResolver`
|
|
60
|
+
// resolves through, out of the one module that spells it
|
|
61
|
+
// (`core/kernel/ids/fqn/ancestors.ts`).
|
|
62
|
+
for (const candidate of ancestorIds(id)) {
|
|
50
63
|
const e = byId.get(candidate);
|
|
51
64
|
if (e === undefined)
|
|
52
65
|
continue;
|
|
66
|
+
const standsFor = e.service !== undefined || known.has(e.title);
|
|
53
67
|
if (ACTOR_KINDS.has(e.kind.toLowerCase()))
|
|
54
68
|
actor = true;
|
|
69
|
+
if (!standsFor && INFRA_KINDS.has(e.kind.toLowerCase()))
|
|
70
|
+
infra = true;
|
|
55
71
|
if (e.tags.includes(EXTERNAL_TAG))
|
|
56
72
|
external = true;
|
|
57
|
-
if (
|
|
73
|
+
if (standsFor) {
|
|
74
|
+
// Everything the walk has visited so far was drawn INSIDE this service,
|
|
75
|
+
// so nothing visited so far was service-LEVEL and no kind read off it
|
|
76
|
+
// answers for a service — `serviceLevelElements` filtering ahead of the
|
|
77
|
+
// kind test, spelled as a walk. `actor` and `external` are deliberately
|
|
78
|
+
// left standing: a person or an `#external` tag written on a container is
|
|
79
|
+
// still a claim about this endpoint, while the store shape is a claim
|
|
80
|
+
// about the owner's interior.
|
|
81
|
+
infra = false;
|
|
58
82
|
break;
|
|
83
|
+
}
|
|
59
84
|
}
|
|
60
|
-
return { actor, external };
|
|
85
|
+
return { actor, infra, external };
|
|
61
86
|
}
|
|
62
87
|
/** How one edge joins the two ends, in the words the finding and the payload both use. */
|
|
63
88
|
function viaOf(r) {
|
|
@@ -162,14 +187,28 @@ export async function partnerScan(req) {
|
|
|
162
187
|
continue;
|
|
163
188
|
if (target === service) {
|
|
164
189
|
const facts = endFacts(byId, known, r.source);
|
|
165
|
-
// A person is never a deploy participant
|
|
166
|
-
// landscape census applies
|
|
167
|
-
|
|
190
|
+
// A person is never a deploy participant, and neither is the fleet's own
|
|
191
|
+
// infrastructure: the two kind exemptions every landscape census applies
|
|
192
|
+
// (drawnSystems, commands/validate/fleet/census.ts), spelled here off the
|
|
193
|
+
// same two sets so a gate and a validate run cannot disagree about what a
|
|
194
|
+
// partner IS.
|
|
195
|
+
//
|
|
196
|
+
// The infra half is the newer one, and it had a measured cost. A
|
|
197
|
+
// `database` the fleet owns and draws at fleet level joined every service
|
|
198
|
+
// that reads it, with `maturity: null` because there is no
|
|
199
|
+
// `services/<title>/` — so `gate.partner-undocumented` told the deployer
|
|
200
|
+
// to "adopt it, bind it … or tag it #external", three remedies none of
|
|
201
|
+
// which is true of a Postgres (measured on a one-service fixture,
|
|
202
|
+
// 2026-09-07). It is dropped from the partner SET rather than exempted
|
|
203
|
+
// from the finding, which is the actor precedent exactly: `partners` is
|
|
204
|
+
// the list of counterparties whose DOCS the gate grades, and a store has
|
|
205
|
+
// none to grade.
|
|
206
|
+
if (!facts.actor && !facts.infra)
|
|
168
207
|
join(source, "consumer", r, facts.external);
|
|
169
208
|
}
|
|
170
209
|
else if (source === service) {
|
|
171
210
|
const facts = endFacts(byId, known, r.target);
|
|
172
|
-
if (!facts.actor)
|
|
211
|
+
if (!facts.actor && !facts.infra)
|
|
173
212
|
join(target, "provider", r, facts.external);
|
|
174
213
|
}
|
|
175
214
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"partners.js","sourceRoot":"","sources":["../../../src/core/gate/partners.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The direct join partners of one service, derived from the LIVING landscape,\n * and the maturity grading of everything on either end of a join.\n *\n * The landscape is the only place the fleet's joins are drawn, so the partner\n * set is exactly the edges whose resolved source or target is the gated\n * service — resolved through `serviceResolver` with the ENUMERATED fleet\n * riding in, the same ride that keeps container-modelling landscapes attached\n * everywhere else (an edge into `paymentService.api` is an edge into the\n * service that owns it). A landscape that is absent or does not parse yields\n * NO partner set, never an empty one: `landscape` says which, and the check\n * layer turns \"could not look\" into `gate.partners-unknown` rather than\n * letting it read as \"no partners\".\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem, type Rel } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport {\n ACTOR_KINDS,\n EXTERNAL_TAG,\n landscapeEvidence,\n maturityGaps,\n serviceMaturity,\n MATURITY_LADDER,\n type Maturity,\n} from \"../vocabulary/maturity.js\";\nimport { compareIds, type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../repo/paths.js\";\nimport { enumeratedServices } from \"../repo/service-target.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport type { GatePartner, LandscapeRead, PartnerRole } from \"./report.js\";\n\nexport interface PartnerScanRequest {\n docsDir: DocsDir;\n /** The gated service — a name the enumeration approved (`resolveServiceTarget`). */\n service: RawServiceId;\n fleet: FleetContext;\n}\n\n/** One service graded on the adoption ladder, with what the next rung wants. */\nexport interface GradedService {\n maturity: Maturity;\n gaps: string[];\n}\n\nexport interface PartnerScan {\n landscape: LandscapeRead;\n /**\n * Why the landscape graded `invalid` — the parse-error tally or the read\n * failure's own words. Null on `read` and `absent`; the finding that fails\n * the gate must say what broke, not merely that something did.\n */\n landscapeProblem: string | null;\n /**\n * The gated service's own rung, graded under the landscape's evidence —\n * `gate.service-undocumented`'s subject. Fail-closed on the api question:\n * with no readable map, `landscapeEvidence` assumes an API is expected.\n */\n target: GradedService;\n /**\n * The same service graded with NO api expected of it — the reading that\n * does not depend on the landscape at all. The check layer answers from\n * THIS one when the map is absent or broken: the api question is then\n * unanswerable, and one unanswerable fact must not convict twice (the\n * landscape already carries its own finding).\n */\n targetWithoutApi: GradedService;\n /**\n * The gated service's own enumerated entry — carried so a finding can name\n * the directory it actually occupies. `services/<id>/` is right only for an\n * unfiled fleet, and a check that sends a reader to a directory they cannot\n * open has answered worse than not at all.\n */\n targetEntry: ServiceEntry;\n partners: GatePartner[];\n /**\n * The enumerated entries behind the partners that HAVE a `services/`\n * directory, in `partners` order — what the freshness check walks. Kept as\n * entries rather than names so the ids that travel on carry the\n * enumeration's provenance (`RawServiceId`), never a document's.\n */\n partnerEntries: ServiceEntry[];\n}\n\n/** Is this rung at or above `documented` — the fixed threshold check 1 grades against? */\nexport function atLeastDocumented(m: Maturity): boolean {\n return MATURITY_LADDER.indexOf(m) >= MATURITY_LADDER.indexOf(\"documented\");\n}\n\n/** The id itself, then each dotted ancestor — the same walk `serviceResolver` resolves through. */\nfunction selfAndAncestors(id: string): string[] {\n const out = [id];\n for (let dot = id.lastIndexOf(\".\"); dot !== -1; dot = id.lastIndexOf(\".\", dot - 1)) {\n out.push(id.slice(0, dot));\n }\n return out;\n}\n\n/**\n * What the map says about one edge END: does it stand for a person, and is it\n * tagged `#external`? Walked up the ancestor chain because both facts are\n * usually written on the containing system while the edge is drawn from a\n * nested element — but the walk STOPS at the first element that stands for a\n * service (an explicit binding or a title naming a real directory,\n * `serviceResolver`'s own first two rungs, checked after that element's own\n * facts are read). What sits above the service boundary is a grouping, and a\n * grouping's `#external` is the grouping's claim, not the service's: without\n * the stop, an adopted service drawn inside an `#external` zone was exempted\n * from `gate.partner-undocumented` and its real rung hidden.\n */\nfunction endFacts(\n byId: Map<string, Elem>,\n known: ReadonlySet<string>,\n id: string,\n): { actor: boolean; external: boolean } {\n let actor = false;\n let external = false;\n for (const candidate of selfAndAncestors(id)) {\n const e = byId.get(candidate);\n if (e === undefined) continue;\n if (ACTOR_KINDS.has(e.kind.toLowerCase())) actor = true;\n if (e.tags.includes(EXTERNAL_TAG)) external = true;\n if (e.service !== undefined || known.has(e.title)) break;\n }\n return { actor, external };\n}\n\n/** How one edge joins the two ends, in the words the finding and the payload both use. */\nfunction viaOf(r: Rel): string[] {\n const typed = [\n ...(r.op === undefined ? [] : [`operation ${r.op}`]),\n ...(r.publishes === undefined ? [] : [`message ${r.publishes}`]),\n ...(r.consumes === undefined ? [] : [`message ${r.consumes}`]),\n ];\n if (typed.length > 0) return typed;\n return [r.title === undefined ? \"untyped edge\" : `edge '${r.title}'`];\n}\n\ninterface PartnerAccumulator {\n consumer: boolean;\n provider: boolean;\n external: boolean;\n via: Set<string>;\n}\n\nexport async function partnerScan(req: PartnerScanRequest): Promise<PartnerScan> {\n const { docsDir, service, fleet } = req;\n // The same memoized enumeration the command's resolveServiceTarget already\n // performed on this FleetContext — one walk answers both questions.\n const entries = await enumeratedServices(docsDir, fleet);\n // Keyed as plain strings on purpose: the lookups below arrive from the\n // resolver, whose names are document-asserted, and only a hit proves a\n // directory. The VALUES keep the enumeration's provenance.\n const entryById = new Map<string, ServiceEntry>(entries.map((e) => [e.id, e]));\n const known: ReadonlySet<string> = new Set(entries.map((e) => e.id));\n\n const lp = landscapePath(docsDir);\n let landscape: LandscapeRead = \"absent\";\n let landscapeProblem: string | null = null;\n let elements: Elem[] = [];\n let relationships: Rel[] = [];\n if (existsSync(lp)) {\n try {\n const doc = await fleet.loadLikeC4(lp);\n if (doc.errors.length > 0) {\n landscape = \"invalid\";\n const first = doc.errors[0];\n landscapeProblem =\n `${doc.errors.length} parse error(s)` +\n (first === undefined ? \"\" : ` — first: ${first.message}`);\n } else {\n landscape = \"read\";\n elements = doc.elements;\n relationships = doc.relationships;\n }\n } catch (err) {\n // A landscape that could not be READ, graded as one that did not PARSE —\n // commands/validate/fleet/load.ts's containment doctrine, spelled here\n // because core cannot import it: \"could not be read\" and \"did not parse\"\n // have the same consequence (nothing may be concluded from this file).\n // The reason travels into the finding, message-preserving as\n // `unreadableLandscape` is; the read error must not kill the run — the\n // other three checks still have answers.\n landscape = \"invalid\";\n landscapeProblem = err instanceof Error ? err.message : String(err);\n }\n }\n const parses = landscape === \"read\";\n const svcOf: (id: string) => string = parses\n ? serviceResolver(elements, known)\n : (id: string) => id;\n const elementIds = elements.map((e) => e.id);\n\n // One service's ladder rung. `landscapeEvidence` is the ONE shared spelling\n // of the positive-evidence apiExpected rule (core/vocabulary/maturity.ts);\n // its own docblock warns against calling it per service inside a FLEET loop,\n // and this loop is not one — it runs over the gated service and its direct\n // partners, a handful, never the enumeration.\n const gradeWith = (entry: ServiceEntry, apiExpected: boolean): GradedService => {\n const input = {\n entry,\n archSpec: existsSync(servicePathsAt(entry.dir).archSpec),\n apiExpected,\n };\n return { maturity: serviceMaturity(input), gaps: maturityGaps(input) };\n };\n const grade = (entry: ServiceEntry): GradedService =>\n gradeWith(\n entry,\n landscapeEvidence({ id: entry.id, parses, relationships, elementIds, svcOf }).apiExpected,\n );\n\n // Unreachable by construction: the command layer resolved `service` against\n // the same memoized enumeration this scan just read, and refused\n // `unknown-service` when no directory answered. Failing closed here beats\n // grading a service nobody enumerated as if its absence were a rung.\n const targetEntry = entryById.get(service);\n if (targetEntry === undefined) {\n throw new Error(`gate: '${service}' vanished from the enumeration between resolve and scan`);\n }\n\n const byId = new Map(elements.map((e) => [e.id, e]));\n const acc = new Map<string, PartnerAccumulator>();\n const join = (name: string, role: PartnerRole, r: Rel, external: boolean): void => {\n const slot = acc.get(name) ?? { consumer: false, provider: false, external: false, via: new Set<string>() };\n if (role === \"consumer\") slot.consumer = true;\n else slot.provider = true;\n slot.external = slot.external || external;\n for (const v of viaOf(r)) slot.via.add(v);\n acc.set(name, slot);\n };\n for (const r of relationships) {\n const source = svcOf(r.source);\n const target = svcOf(r.target);\n // A self-edge (both ends resolve to the gated service — its own containers\n // talking to each other) names no partner.\n if (source === target) continue;\n if (target === service) {\n const facts = endFacts(byId, known, r.source);\n // A person is never a deploy participant: the actor exemption every\n // landscape census applies (drawnSystems, commands/validate/fleet/census.ts).\n if (!facts.actor) join(source, \"consumer\", r, facts.external);\n } else if (source === service) {\n const facts = endFacts(byId, known, r.target);\n if (!facts.actor) join(target, \"provider\", r, facts.external);\n }\n }\n\n // Sorted by id so identical state yields identical bytes — the accumulator's\n // order is the landscape's edge order, which a reformat may shuffle.\n const joined = [...acc.entries()].sort((a, b) => compareIds(a[0], b[0]));\n const partners: GatePartner[] = joined.map(([name, slot]) => {\n const entry = entryById.get(name);\n return {\n service: name,\n maturity: entry === undefined ? null : grade(entry).maturity,\n role: slot.consumer && slot.provider ? \"both\" : slot.consumer ? \"consumer\" : \"provider\",\n via: [...slot.via].sort(),\n // An adopted service is OURS whatever a zone's tag says: `external` and\n // a maturity rung together must be unrepresentable, or the rung the\n // payload carries is one the exemption hides and the human view drops.\n external: entry === undefined && slot.external,\n };\n });\n\n return {\n landscape,\n landscapeProblem,\n targetEntry,\n target: grade(targetEntry),\n targetWithoutApi: gradeWith(targetEntry, false),\n partners,\n partnerEntries: joined.flatMap(([name]) => {\n const entry = entryById.get(name);\n return entry === undefined ? [] : [entry];\n }),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"partners.js","sourceRoot":"","sources":["../../../src/core/gate/partners.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The direct join partners of one service, derived from the LIVING landscape,\n * and the maturity grading of everything on either end of a join.\n *\n * The landscape is the only place the fleet's joins are drawn, so the partner\n * set is exactly the edges whose resolved source or target is the gated\n * service — resolved through `serviceResolver` with the ENUMERATED fleet\n * riding in, the same ride that keeps container-modelling landscapes attached\n * everywhere else (an edge into `paymentService.api` is an edge into the\n * service that owns it). A landscape that is absent or does not parse yields\n * NO partner set, never an empty one: `landscape` says which, and the check\n * layer turns \"could not look\" into `gate.partners-unknown` rather than\n * letting it read as \"no partners\".\n */\nimport { existsSync } from \"node:fs\";\nimport { type Elem, type Rel } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport {\n ACTOR_KINDS,\n EXTERNAL_TAG,\n INFRA_KINDS,\n landscapeEvidence,\n maturityGaps,\n serviceMaturity,\n MATURITY_LADDER,\n type Maturity,\n} from \"../vocabulary/maturity.js\";\nimport { compareIds, type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../repo/paths.js\";\nimport { enumeratedServices } from \"../repo/service-target.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport { ancestorIds } from \"../kernel/ids/fqn/ancestors.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport type { GatePartner, LandscapeRead, PartnerRole } from \"./report.js\";\n\nexport interface PartnerScanRequest {\n docsDir: DocsDir;\n /** The gated service — a name the enumeration approved (`resolveServiceTarget`). */\n service: RawServiceId;\n fleet: FleetContext;\n}\n\n/** One service graded on the adoption ladder, with what the next rung wants. */\nexport interface GradedService {\n maturity: Maturity;\n gaps: string[];\n}\n\nexport interface PartnerScan {\n landscape: LandscapeRead;\n /**\n * Why the landscape graded `invalid` — the parse-error tally or the read\n * failure's own words. Null on `read` and `absent`; the finding that fails\n * the gate must say what broke, not merely that something did.\n */\n landscapeProblem: string | null;\n /**\n * The gated service's own rung, graded under the landscape's evidence —\n * `gate.service-undocumented`'s subject. Fail-closed on the api question:\n * with no readable map, `landscapeEvidence` assumes an API is expected.\n */\n target: GradedService;\n /**\n * The same service graded with NO api expected of it — the reading that\n * does not depend on the landscape at all. The check layer answers from\n * THIS one when the map is absent or broken: the api question is then\n * unanswerable, and one unanswerable fact must not convict twice (the\n * landscape already carries its own finding).\n */\n targetWithoutApi: GradedService;\n /**\n * The gated service's own enumerated entry — carried so a finding can name\n * the directory it actually occupies. `services/<id>/` is right only for an\n * unfiled fleet, and a check that sends a reader to a directory they cannot\n * open has answered worse than not at all.\n */\n targetEntry: ServiceEntry;\n partners: GatePartner[];\n /**\n * The enumerated entries behind the partners that HAVE a `services/`\n * directory, in `partners` order — what the freshness check walks. Kept as\n * entries rather than names so the ids that travel on carry the\n * enumeration's provenance (`RawServiceId`), never a document's.\n */\n partnerEntries: ServiceEntry[];\n}\n\n/** Is this rung at or above `documented` — the fixed threshold check 1 grades against? */\nexport function atLeastDocumented(m: Maturity): boolean {\n return MATURITY_LADDER.indexOf(m) >= MATURITY_LADDER.indexOf(\"documented\");\n}\n\n/**\n * What the map says about one edge END: does it stand for a person, is it\n * INFRASTRUCTURE, and is it tagged `#external`? Walked up the ancestor chain\n * because all three facts are usually written on the containing system while\n * the edge is drawn from a nested element — but the walk STOPS at the first\n * element that stands for a service (an explicit binding or a title naming a\n * real directory, `serviceResolver`'s own first two rungs, checked after that\n * element's own facts are read). What sits above the service boundary is a\n * grouping, and a grouping's `#external` is the grouping's claim, not the\n * service's: without the stop, an adopted service drawn inside an `#external`\n * zone was exempted from `gate.partner-undocumented` and its real rung hidden.\n *\n * `infra` is read only off an element that stands for NO service AND sits\n * inside none, which is the asymmetry the `standsFor` local exists for. That is\n * `commands/validate/fleet/census.ts` in two steps and in its order:\n * `serviceLevelElements` drops everything drawn inside a service BEFORE\n * `drawnSystems` ever applies the kind test, so a service's own interior never\n * reaches it. Both halves are load-bearing. A `database` bound with\n * `metadata { service '<id>' }` is an adopted service that happens to be drawn\n * as a store — its side of the join IS recorded, and dropping it would hide a\n * real partner's rung. And a store drawn INSIDE its owner — `orderService.db`,\n * the ordinary way a team draws a private datastore — is that service's\n * interior: reading the kind off it convicted the WHOLE endpoint as\n * infrastructure, and `partnerScan` then dropped order-service from the partner\n * set, so a service this gate exists to grade vanished because of how somebody\n * drew its storage. The kind is the fallback for an element nothing else\n * answers for, never the override.\n */\nfunction endFacts(\n byId: Map<string, Elem>,\n known: ReadonlySet<string>,\n id: string,\n): { actor: boolean; infra: boolean; external: boolean } {\n let actor = false;\n let infra = false;\n let external = false;\n // The id itself, then each dotted ancestor — the same walk `serviceResolver`\n // resolves through, out of the one module that spells it\n // (`core/kernel/ids/fqn/ancestors.ts`).\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e === undefined) continue;\n const standsFor = e.service !== undefined || known.has(e.title);\n if (ACTOR_KINDS.has(e.kind.toLowerCase())) actor = true;\n if (!standsFor && INFRA_KINDS.has(e.kind.toLowerCase())) infra = true;\n if (e.tags.includes(EXTERNAL_TAG)) external = true;\n if (standsFor) {\n // Everything the walk has visited so far was drawn INSIDE this service,\n // so nothing visited so far was service-LEVEL and no kind read off it\n // answers for a service — `serviceLevelElements` filtering ahead of the\n // kind test, spelled as a walk. `actor` and `external` are deliberately\n // left standing: a person or an `#external` tag written on a container is\n // still a claim about this endpoint, while the store shape is a claim\n // about the owner's interior.\n infra = false;\n break;\n }\n }\n return { actor, infra, external };\n}\n\n/** How one edge joins the two ends, in the words the finding and the payload both use. */\nfunction viaOf(r: Rel): string[] {\n const typed = [\n ...(r.op === undefined ? [] : [`operation ${r.op}`]),\n ...(r.publishes === undefined ? [] : [`message ${r.publishes}`]),\n ...(r.consumes === undefined ? [] : [`message ${r.consumes}`]),\n ];\n if (typed.length > 0) return typed;\n return [r.title === undefined ? \"untyped edge\" : `edge '${r.title}'`];\n}\n\ninterface PartnerAccumulator {\n consumer: boolean;\n provider: boolean;\n external: boolean;\n via: Set<string>;\n}\n\nexport async function partnerScan(req: PartnerScanRequest): Promise<PartnerScan> {\n const { docsDir, service, fleet } = req;\n // The same memoized enumeration the command's resolveServiceTarget already\n // performed on this FleetContext — one walk answers both questions.\n const entries = await enumeratedServices(docsDir, fleet);\n // Keyed as plain strings on purpose: the lookups below arrive from the\n // resolver, whose names are document-asserted, and only a hit proves a\n // directory. The VALUES keep the enumeration's provenance.\n const entryById = new Map<string, ServiceEntry>(entries.map((e) => [e.id, e]));\n const known: ReadonlySet<string> = new Set(entries.map((e) => e.id));\n\n const lp = landscapePath(docsDir);\n let landscape: LandscapeRead = \"absent\";\n let landscapeProblem: string | null = null;\n let elements: Elem[] = [];\n let relationships: Rel[] = [];\n if (existsSync(lp)) {\n try {\n const doc = await fleet.loadLikeC4(lp);\n if (doc.errors.length > 0) {\n landscape = \"invalid\";\n const first = doc.errors[0];\n landscapeProblem =\n `${doc.errors.length} parse error(s)` +\n (first === undefined ? \"\" : ` — first: ${first.message}`);\n } else {\n landscape = \"read\";\n elements = doc.elements;\n relationships = doc.relationships;\n }\n } catch (err) {\n // A landscape that could not be READ, graded as one that did not PARSE —\n // commands/validate/fleet/load.ts's containment doctrine, spelled here\n // because core cannot import it: \"could not be read\" and \"did not parse\"\n // have the same consequence (nothing may be concluded from this file).\n // The reason travels into the finding, message-preserving as\n // `unreadableLandscape` is; the read error must not kill the run — the\n // other three checks still have answers.\n landscape = \"invalid\";\n landscapeProblem = err instanceof Error ? err.message : String(err);\n }\n }\n const parses = landscape === \"read\";\n const svcOf: (id: string) => string = parses\n ? serviceResolver(elements, known)\n : (id: string) => id;\n const elementIds = elements.map((e) => e.id);\n\n // One service's ladder rung. `landscapeEvidence` is the ONE shared spelling\n // of the positive-evidence apiExpected rule (core/vocabulary/maturity.ts);\n // its own docblock warns against calling it per service inside a FLEET loop,\n // and this loop is not one — it runs over the gated service and its direct\n // partners, a handful, never the enumeration.\n const gradeWith = (entry: ServiceEntry, apiExpected: boolean): GradedService => {\n const input = {\n entry,\n archSpec: existsSync(servicePathsAt(entry.dir).archSpec),\n apiExpected,\n };\n return { maturity: serviceMaturity(input), gaps: maturityGaps(input) };\n };\n const grade = (entry: ServiceEntry): GradedService =>\n gradeWith(\n entry,\n landscapeEvidence({ id: entry.id, parses, relationships, elementIds, svcOf }).apiExpected,\n );\n\n // Unreachable by construction: the command layer resolved `service` against\n // the same memoized enumeration this scan just read, and refused\n // `unknown-service` when no directory answered. Failing closed here beats\n // grading a service nobody enumerated as if its absence were a rung.\n const targetEntry = entryById.get(service);\n if (targetEntry === undefined) {\n throw new Error(`gate: '${service}' vanished from the enumeration between resolve and scan`);\n }\n\n const byId = new Map(elements.map((e) => [e.id, e]));\n const acc = new Map<string, PartnerAccumulator>();\n const join = (name: string, role: PartnerRole, r: Rel, external: boolean): void => {\n const slot = acc.get(name) ?? { consumer: false, provider: false, external: false, via: new Set<string>() };\n if (role === \"consumer\") slot.consumer = true;\n else slot.provider = true;\n slot.external = slot.external || external;\n for (const v of viaOf(r)) slot.via.add(v);\n acc.set(name, slot);\n };\n for (const r of relationships) {\n const source = svcOf(r.source);\n const target = svcOf(r.target);\n // A self-edge (both ends resolve to the gated service — its own containers\n // talking to each other) names no partner.\n if (source === target) continue;\n if (target === service) {\n const facts = endFacts(byId, known, r.source);\n // A person is never a deploy participant, and neither is the fleet's own\n // infrastructure: the two kind exemptions every landscape census applies\n // (drawnSystems, commands/validate/fleet/census.ts), spelled here off the\n // same two sets so a gate and a validate run cannot disagree about what a\n // partner IS.\n //\n // The infra half is the newer one, and it had a measured cost. A\n // `database` the fleet owns and draws at fleet level joined every service\n // that reads it, with `maturity: null` because there is no\n // `services/<title>/` — so `gate.partner-undocumented` told the deployer\n // to \"adopt it, bind it … or tag it #external\", three remedies none of\n // which is true of a Postgres (measured on a one-service fixture,\n // 2026-09-07). It is dropped from the partner SET rather than exempted\n // from the finding, which is the actor precedent exactly: `partners` is\n // the list of counterparties whose DOCS the gate grades, and a store has\n // none to grade.\n if (!facts.actor && !facts.infra) join(source, \"consumer\", r, facts.external);\n } else if (source === service) {\n const facts = endFacts(byId, known, r.target);\n if (!facts.actor && !facts.infra) join(target, \"provider\", r, facts.external);\n }\n }\n\n // Sorted by id so identical state yields identical bytes — the accumulator's\n // order is the landscape's edge order, which a reformat may shuffle.\n const joined = [...acc.entries()].sort((a, b) => compareIds(a[0], b[0]));\n const partners: GatePartner[] = joined.map(([name, slot]) => {\n const entry = entryById.get(name);\n return {\n service: name,\n maturity: entry === undefined ? null : grade(entry).maturity,\n role: slot.consumer && slot.provider ? \"both\" : slot.consumer ? \"consumer\" : \"provider\",\n via: [...slot.via].sort(),\n // An adopted service is OURS whatever a zone's tag says: `external` and\n // a maturity rung together must be unrepresentable, or the rung the\n // payload carries is one the exemption hides and the human view drops.\n external: entry === undefined && slot.external,\n };\n });\n\n return {\n landscape,\n landscapeProblem,\n targetEntry,\n target: grade(targetEntry),\n targetWithoutApi: gradeWith(targetEntry, false),\n partners,\n partnerEntries: joined.flatMap(([name]) => {\n const entry = entryById.get(name);\n return entry === undefined ? [] : [entry];\n }),\n };\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ancestor chain of a dotted element id — `a.b.c` yields `a.b`, then `a`.
|
|
3
|
+
*
|
|
4
|
+
* A leaf of its own because five modules were each walking it privately, in two
|
|
5
|
+
* spellings, and the walk is not incidental to any of them: it is the rule that
|
|
6
|
+
* a landscape does not have to draw a service as one opaque box. The moment
|
|
7
|
+
* somebody models its containers — `paymentService.api`, `paymentService.worker`
|
|
8
|
+
* — an edge drawn INTO a container is still an edge into the service, and every
|
|
9
|
+
* join that groups by service has to know that. Resolving only the exact id is
|
|
10
|
+
* what made those edges invisible: the spine check silently skipped them, and
|
|
11
|
+
* the no-openapi grace treated a service with a dozen inbound container calls as
|
|
12
|
+
* one nobody calls at all.
|
|
13
|
+
*
|
|
14
|
+
* The same walk answers a second question one altitude down, which is why a
|
|
15
|
+
* fifth copy appeared. A service model draws an edge at `kafka.orderEvents`
|
|
16
|
+
* while the fleet's health.yaml names the dependency `kafka`, so the slice of a
|
|
17
|
+
* per-service project has to carry the ANCESTOR of a partner endpoint as well as
|
|
18
|
+
* the endpoint (`core/c4/service-model/slice.ts`). Dropping it would turn a
|
|
19
|
+
* fleet's dependency declarations into `health.dependency-unmodelled` findings
|
|
20
|
+
* on the day it migrated its models, with nothing in the docs having changed.
|
|
21
|
+
*
|
|
22
|
+
* TWO SPELLINGS, both exported, because both are real and picking one would make
|
|
23
|
+
* every second caller write `[id, ...properAncestorIds(id)]` or `.slice(1)` — a
|
|
24
|
+
* per-call-site adjustment is exactly how a shared walk drifts back into five.
|
|
25
|
+
* A resolver that also looks the element itself up wants `ancestorIds`; a walk
|
|
26
|
+
* that has already handled the element wants `properAncestorIds`.
|
|
27
|
+
*
|
|
28
|
+
* No brand and no validation: this is string arithmetic over an id a DOCUMENT
|
|
29
|
+
* spells, and `core/kernel/ids/service.ts`'s provenance rules are about names
|
|
30
|
+
* that have to answer for a directory. An id with no dot in it has no ancestors
|
|
31
|
+
* and yields the empty list, which is the correct answer rather than an edge
|
|
32
|
+
* case — a top-level element belongs to nothing above it.
|
|
33
|
+
*/
|
|
34
|
+
/** Every dotted ancestor of an id, NEAREST FIRST and excluding the id itself. */
|
|
35
|
+
export function properAncestorIds(id) {
|
|
36
|
+
const out = [];
|
|
37
|
+
// Walking right-to-left from the last dot rather than splitting and rejoining:
|
|
38
|
+
// the ids are already strings, the answer is a set of prefixes of one of them,
|
|
39
|
+
// and a split/join round trip would invent a rule about what a segment may
|
|
40
|
+
// contain that nothing else here has.
|
|
41
|
+
for (let dot = id.lastIndexOf("."); dot !== -1; dot = id.lastIndexOf(".", dot - 1)) {
|
|
42
|
+
out.push(id.slice(0, dot));
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/** The id itself, then every dotted ancestor of it, nearest first. */
|
|
47
|
+
export function ancestorIds(id) {
|
|
48
|
+
return [id, ...properAncestorIds(id)];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The id a declaration nests directly under — `""` for a top-level one, which
|
|
52
|
+
* nests under nothing.
|
|
53
|
+
*
|
|
54
|
+
* The nearest ancestor, named because that is the question the callers ask:
|
|
55
|
+
* `properAncestorIds(id)[0] ?? ""` is the same walk spelled as an array index,
|
|
56
|
+
* and every reader has to re-derive that the first entry is the parent. It is
|
|
57
|
+
* here rather than beside its callers for the reason the file exists at all —
|
|
58
|
+
* the archive's two merges had each grown a private copy, and a third was about
|
|
59
|
+
* to appear the moment the routing gained a module.
|
|
60
|
+
*/
|
|
61
|
+
export function parentIdOf(id) {
|
|
62
|
+
const dot = id.lastIndexOf(".");
|
|
63
|
+
return dot === -1 ? "" : id.slice(0, dot);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=ancestors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ancestors.js","sourceRoot":"","sources":["../../../../../src/core/kernel/ids/fqn/ancestors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,+EAA+E;IAC/E,+EAA+E;IAC/E,2EAA2E;IAC3E,sCAAsC;IACtC,KAAK,IAAI,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;QACnF,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,CAAC,EAAE,EAAE,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * The ancestor chain of a dotted element id — `a.b.c` yields `a.b`, then `a`.\n *\n * A leaf of its own because five modules were each walking it privately, in two\n * spellings, and the walk is not incidental to any of them: it is the rule that\n * a landscape does not have to draw a service as one opaque box. The moment\n * somebody models its containers — `paymentService.api`, `paymentService.worker`\n * — an edge drawn INTO a container is still an edge into the service, and every\n * join that groups by service has to know that. Resolving only the exact id is\n * what made those edges invisible: the spine check silently skipped them, and\n * the no-openapi grace treated a service with a dozen inbound container calls as\n * one nobody calls at all.\n *\n * The same walk answers a second question one altitude down, which is why a\n * fifth copy appeared. A service model draws an edge at `kafka.orderEvents`\n * while the fleet's health.yaml names the dependency `kafka`, so the slice of a\n * per-service project has to carry the ANCESTOR of a partner endpoint as well as\n * the endpoint (`core/c4/service-model/slice.ts`). Dropping it would turn a\n * fleet's dependency declarations into `health.dependency-unmodelled` findings\n * on the day it migrated its models, with nothing in the docs having changed.\n *\n * TWO SPELLINGS, both exported, because both are real and picking one would make\n * every second caller write `[id, ...properAncestorIds(id)]` or `.slice(1)` — a\n * per-call-site adjustment is exactly how a shared walk drifts back into five.\n * A resolver that also looks the element itself up wants `ancestorIds`; a walk\n * that has already handled the element wants `properAncestorIds`.\n *\n * No brand and no validation: this is string arithmetic over an id a DOCUMENT\n * spells, and `core/kernel/ids/service.ts`'s provenance rules are about names\n * that have to answer for a directory. An id with no dot in it has no ancestors\n * and yields the empty list, which is the correct answer rather than an edge\n * case — a top-level element belongs to nothing above it.\n */\n\n/** Every dotted ancestor of an id, NEAREST FIRST and excluding the id itself. */\nexport function properAncestorIds(id: string): string[] {\n const out: string[] = [];\n // Walking right-to-left from the last dot rather than splitting and rejoining:\n // the ids are already strings, the answer is a set of prefixes of one of them,\n // and a split/join round trip would invent a rule about what a segment may\n // contain that nothing else here has.\n for (let dot = id.lastIndexOf(\".\"); dot !== -1; dot = id.lastIndexOf(\".\", dot - 1)) {\n out.push(id.slice(0, dot));\n }\n return out;\n}\n\n/** The id itself, then every dotted ancestor of it, nearest first. */\nexport function ancestorIds(id: string): string[] {\n return [id, ...properAncestorIds(id)];\n}\n\n/**\n * The id a declaration nests directly under — `\"\"` for a top-level one, which\n * nests under nothing.\n *\n * The nearest ancestor, named because that is the question the callers ask:\n * `properAncestorIds(id)[0] ?? \"\"` is the same walk spelled as an array index,\n * and every reader has to re-derive that the first entry is the parent. It is\n * here rather than beside its callers for the reason the file exists at all —\n * the archive's two merges had each grown a private copy, and a third was about\n * to appear the moment the routing gained a module.\n */\nexport function parentIdOf(id: string): string {\n const dot = id.lastIndexOf(\".\");\n return dot === -1 ? \"\" : id.slice(0, dot);\n}\n"]}
|
package/dist/core/pack/living.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* briefing, and the line limit is what asked the question.
|
|
10
10
|
*/
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
|
+
import { relative } from "node:path";
|
|
12
13
|
import { FleetContext } from "../fleet-context.js";
|
|
14
|
+
import {} from "../c4/likec4.js";
|
|
13
15
|
import { serviceResolver } from "../c4/resolve/service.js";
|
|
14
16
|
import { repoPath } from "../envelope/json.js";
|
|
15
17
|
import { listField, parseFrontmatter, stringField } from "../document/frontmatter.js";
|
|
@@ -18,8 +20,7 @@ import {} from "../document/spec.js";
|
|
|
18
20
|
import { landscapeEvidence, maturityGaps, serviceMaturity, } from "../vocabulary/maturity.js";
|
|
19
21
|
import {} from "../repo/entries.js";
|
|
20
22
|
import { landscapePath, servicePathsAt } from "../repo/paths.js";
|
|
21
|
-
import {
|
|
22
|
-
import { loadArchitecture } from "../c4/project/architecture.js";
|
|
23
|
+
import { NO_DEPLOYMENT } from "../c4/parsed/deployment.js";
|
|
23
24
|
import { serviceTopology } from "../deployment/objects.js";
|
|
24
25
|
export function packRequirement(r) {
|
|
25
26
|
return {
|
|
@@ -50,7 +51,21 @@ export async function buildLiving(req) {
|
|
|
50
51
|
// Both headers, exactly as `show` reads both (commands/show/service.ts):
|
|
51
52
|
// a memo hit on the same read the requirements above already paid for.
|
|
52
53
|
existsSync(paths.archSpec) ? context.readText(paths.archSpec) : null,
|
|
53
|
-
|
|
54
|
+
// The `architecture/` PROJECT, never `architecture/landscape.likec4` alone.
|
|
55
|
+
// The map is a project — the landscape merged with every use case, palette
|
|
56
|
+
// and second `model { }` block beside it — and reading one file of it made
|
|
57
|
+
// this pack answer differently from `loam adopt` about the same tree
|
|
58
|
+
// (review finding F26, reproduced: with a broken `architecture/palette.likec4`
|
|
59
|
+
// beside a landscape that parses, `adopt --service order-service` reported
|
|
60
|
+
// `parses: false` and refused to say anything about the map, while `context
|
|
61
|
+
// order-service` reported `parses: true` and listed its edges). The brief's
|
|
62
|
+
// own banner (core/brief/landscape.ts) already named this module as the
|
|
63
|
+
// reader that disagreed. Whichever answer is right, an agent handed both
|
|
64
|
+
// has no way to tell — and the renderer's answer is the project's.
|
|
65
|
+
//
|
|
66
|
+
// `present` stays a fact about the FILE (`existsSync` below), so a repo
|
|
67
|
+
// with no landscape is still "absent" rather than "empty project".
|
|
68
|
+
existsSync(landscapePath(docsDir)) ? context.architecture(docsDir) : null,
|
|
54
69
|
]);
|
|
55
70
|
const present = land !== null;
|
|
56
71
|
const parses = land !== null && land.errors.length === 0;
|
|
@@ -62,8 +77,12 @@ export async function buildLiving(req) {
|
|
|
62
77
|
const svcOf = serviceResolver(elements, known);
|
|
63
78
|
// The partition, the modelled probe and the apiExpected rule are ONE shared
|
|
64
79
|
// derivation (core/vocabulary/maturity.ts `landscapeEvidence`) — the same
|
|
65
|
-
// one explore's describe uses, so the pack and explore cannot
|
|
66
|
-
// same
|
|
80
|
+
// one explore's describe uses, so the pack and explore cannot spell the same
|
|
81
|
+
// rule two ways. They are handed the same DOCUMENTS as well: `explore` reads
|
|
82
|
+
// the `architecture/` project too (core/explore/explore.ts), which it did not
|
|
83
|
+
// when this slice moved off the single file — for a while the two differed by
|
|
84
|
+
// input on a fleet whose sibling document is broken, which is a disagreement
|
|
85
|
+
// no reader of both could resolve.
|
|
67
86
|
const { inbound, outbound, modelled, apiExpected } = landscapeEvidence({
|
|
68
87
|
id: entry.id,
|
|
69
88
|
parses,
|
|
@@ -78,14 +97,16 @@ export async function buildLiving(req) {
|
|
|
78
97
|
// map nobody had read. Empty for every fleet that draws none, which is the
|
|
79
98
|
// axis's opt-in and not a finding.
|
|
80
99
|
//
|
|
81
|
-
// Read
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
100
|
+
// Read off the PROJECT, for the reason `validate/service/specs.ts` pays the
|
|
101
|
+
// same load: `architecture/` is one LikeC4 project and a fleet's
|
|
102
|
+
// `deployment { }` block is ordinarily a file beside the landscape, not
|
|
103
|
+
// inside it. A single-file read would report "this service runs nowhere" over
|
|
104
|
+
// a repo that draws it, which is the one answer a briefing must never give.
|
|
105
|
+
// This used to be a SECOND load, made only when the landscape file itself
|
|
106
|
+
// declared no topology; `land` is that project now, so the fallback would be
|
|
107
|
+
// the same document parsed twice.
|
|
87
108
|
const topology = parses ? (land.deployment ?? NO_DEPLOYMENT) : NO_DEPLOYMENT;
|
|
88
|
-
const deployment = serviceTopology(
|
|
109
|
+
const deployment = serviceTopology(topology, elements, known, entry.id);
|
|
89
110
|
const input = { entry, archSpec: existsSync(paths.archSpec), apiExpected };
|
|
90
111
|
const header = (text) => text === null ? { present: false, malformed: false, data: {}, body: "" } : parseFrontmatter(text);
|
|
91
112
|
const fm = header(specText);
|
|
@@ -106,7 +127,7 @@ export async function buildLiving(req) {
|
|
|
106
127
|
sources: listField(fm, "sources"),
|
|
107
128
|
},
|
|
108
129
|
archSpec: { vouch_scope: scopeText(header(archText)) },
|
|
109
|
-
landscape: { present, parses, modelled, inbound, outbound },
|
|
130
|
+
landscape: { present, parses, broken: brokenDocuments(docsDir, land), modelled, inbound, outbound },
|
|
110
131
|
deployment,
|
|
111
132
|
requirements: reqs.map(packRequirement),
|
|
112
133
|
archRequirements: archReqs.map(packRequirement),
|
|
@@ -134,4 +155,17 @@ export async function buildLiving(req) {
|
|
|
134
155
|
},
|
|
135
156
|
};
|
|
136
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* The `architecture/` documents that failed, docs-relative POSIX, deduped and
|
|
160
|
+
* sorted — the same spelling `commands/validate/fleet/landscape.ts` uses for
|
|
161
|
+
* `landscape.invalid` and `core/brief/landscape.ts` for the brief's unparseable
|
|
162
|
+
* arm, so the three surfaces name one file one way.
|
|
163
|
+
*/
|
|
164
|
+
function brokenDocuments(docsDir, land) {
|
|
165
|
+
if (land === null)
|
|
166
|
+
return [];
|
|
167
|
+
return [...new Set(land.errors.map((e) => e.sourceFsPath).filter((p) => p !== undefined))]
|
|
168
|
+
.map((abs) => relative(docsDir, abs).split(/[\\/]/).join("/"))
|
|
169
|
+
.sort();
|
|
170
|
+
}
|
|
137
171
|
//# sourceMappingURL=living.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"living.js","sourceRoot":"","sources":["../../../src/core/pack/living.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The living slice of one service's context pack: its requirements verbatim,\n * its two contracts, its provenance, its maturity, and the fleet edges around\n * it — everything the docs repo says about the service TODAY, before any\n * feature in flight is consulted.\n *\n * Split from `./pack.ts` because it answers about the LIVING documents while\n * `./features.ts` answers about the deltas over them — two phases of one\n * briefing, and the line limit is what asked the question.\n */\nimport { existsSync } from \"node:fs\";\nimport { FleetContext } from \"../fleet-context.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport { repoPath } from \"../envelope/json.js\";\nimport { listField, parseFrontmatter, stringField, type Frontmatter } from \"../document/frontmatter.js\";\nimport { scopeText } from \"../provenance/sample/scope.js\";\nimport { type Requirement } from \"../document/spec.js\";\nimport {\n landscapeEvidence,\n maturityGaps,\n serviceMaturity,\n type LandscapeEdge,\n type Maturity,\n} from \"../vocabulary/maturity.js\";\nimport { type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../repo/paths.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport { hasDeployment, NO_DEPLOYMENT } from \"../c4/parsed/deployment.js\";\nimport { loadArchitecture } from \"../c4/project/architecture.js\";\nimport { serviceTopology, type ServiceTopology } from \"../deployment/objects.js\";\n\n/**\n * A requirement as the pack carries it: the delta brief's `reqJson` shape plus\n * the four join lines (`Requires:`, `Capability:`, `Publishes:`, `Consumes:`)\n * the brief never needed. Verbatim on purpose — the pack is the briefing, and\n * a heading without its body is a table of contents for a file the reader then\n * has to open.\n */\nexport interface PackRequirement {\n kind: Requirement[\"kind\"];\n id?: string;\n name: string;\n text: string;\n operations: string[];\n covers: string[];\n requires: string[];\n capabilities: string[];\n publishes: string[];\n consumes: string[];\n /** Verbatim: the acceptance criteria, and the source for the tests whoever picks this up writes. */\n scenarios: Array<{ name: string; lines: string[] }>;\n}\n\nexport function packRequirement(r: Requirement): PackRequirement {\n return {\n kind: r.kind,\n ...(r.id === undefined ? {} : { id: r.id }),\n name: r.name,\n text: r.text.join(\"\\n\").trim(),\n operations: r.operations,\n covers: r.covers,\n requires: r.requires,\n capabilities: r.capabilities,\n publishes: r.publishes,\n consumes: r.consumes,\n scenarios: r.scenarios.map((s) => ({ name: s.name, lines: s.lines })),\n };\n}\n\nexport interface LivingSlice {\n maturity: Maturity;\n /** What stands between the service and `vouched` — `maturityGaps`, so it matches `list --needs-work`. */\n missing: string[];\n /**\n * `vouch_scope` rides beside `status` because this pack is read by an agent\n * about to work on the service, and a bare `status: verified` over a vouch\n * that read a recorded sample of the document would be read as the whole of\n * it having been checked. Verbatim, null when there is none.\n *\n * spec.md's header ALONE — arch.spec.md carries its own, below.\n */\n frontmatter: {\n status: string | null;\n owner: string | null;\n last_verified: string | null;\n vouch_scope: string | null;\n sources: string[];\n };\n /**\n * arch.spec.md's own `vouch_scope`, under `show --json`'s spelling\n * (`archSpec.vouch_scope`) so the two surfaces name one fact one way.\n *\n * The sample is per FILE: one `loam vouch --sample` run reads a short\n * spec.md in full and stamps a long arch.spec.md sampled, and the pack read\n * only the first header. Every other trust surface — list, show,\n * `vouch --pack`, `validate --all`, status — reported that service as\n * sampled; context printed `verified`, `vouch_scope: null`, and then all of\n * arch.spec.md's requirements verbatim, of which nobody had read half. It is\n * the one surface whose reader is an agent that will act on the licence, and\n * `loam_context` exposes it as an MCP tool besides.\n */\n archSpec: { vouch_scope: string | null };\n /**\n * `present`/`parses` ride beside the edges (explore's shape) so silence is\n * evidence, not absence: an empty edge list under `parses: false` says\n * \"nobody could look\", not \"nobody calls this\".\n */\n landscape: {\n present: boolean;\n parses: boolean;\n modelled: boolean;\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n };\n /**\n * Where this service runs, off the landscape's `deployment { }` model, and\n * the deployment edges touching it — replication, failover, a network hop.\n *\n * Both ends, not only the outbound one: a replication edge is as often read\n * from the standby's side as from the primary's. Empty for a fleet that draws\n * no topology, and empty is not a finding here any more than it is anywhere\n * else on this axis.\n *\n * WHAT IT IS NOT. A green deployment axis means the documents agree with each\n * other — never that the second cluster exists, is reachable, or holds the\n * data a requirement claims. `core/brief/unchecked.ts` says so beside the\n * sixteen statements that say the same about the rest of the map, and an\n * agent reading this slice is exactly the reader that sentence is for.\n */\n deployment: ServiceTopology;\n requirements: PackRequirement[];\n archRequirements: PackRequirement[];\n /** Whether the living contract could be read at all — the vacuously-green guard every sibling carries. */\n openapi: { unreadable: boolean; error?: string };\n operations: Array<{ id: string; method: string; path: string; governedBy: string[] }>;\n asyncapi: { unreadable: boolean; error?: string };\n messages: Array<{ name: string; slot: string; direction: \"send\" | \"receive\" | null }>;\n /** Free-form prose stays a pointer: unbounded documents do not belong in a bounded pack. */\n pointers: {\n runbook: { present: boolean; path: string };\n health: { present: boolean; path: string };\n adrs: { count: number; path: string };\n };\n}\n\nexport interface LivingRequest {\n docsDir: DocsDir;\n entry: ServiceEntry;\n context: FleetContext;\n}\n\nexport async function buildLiving(req: LivingRequest): Promise<LivingSlice> {\n const { docsDir, entry, context } = req;\n const paths = servicePathsAt(entry.dir);\n\n // Independent reads, fanned out together; each is memoized on the shared\n // context, so anything `./joins.ts` or `./features.ts` re-asks is free.\n const [reqs, archReqs, api, events, specText, archText, land] = await Promise.all([\n entry.has.spec ? context.readRequirements(paths.spec) : [],\n existsSync(paths.archSpec) ? context.readRequirements(paths.archSpec) : [],\n context.readOpenapi(paths.openapi),\n context.readAsyncapi(paths.asyncapi),\n entry.has.spec ? context.readText(paths.spec) : null,\n // Both headers, exactly as `show` reads both (commands/show/service.ts):\n // a memo hit on the same read the requirements above already paid for.\n existsSync(paths.archSpec) ? context.readText(paths.archSpec) : null,\n existsSync(landscapePath(docsDir)) ? context.loadLikeC4(landscapePath(docsDir)) : null,\n ]);\n\n const present = land !== null;\n const parses = land !== null && land.errors.length === 0;\n const elements = parses ? land.elements : [];\n // Edges are filed under the service an element is BOUND to, with the\n // enumerated fleet riding in — the same resolution `show`, `explore` and\n // `validate` use, so no two commands can disagree about who calls whom.\n const known = new Set((await context.listServices(docsDir)).map((s) => s.id));\n const svcOf = serviceResolver(elements, known);\n // The partition, the modelled probe and the apiExpected rule are ONE shared\n // derivation (core/vocabulary/maturity.ts `landscapeEvidence`) — the same\n // one explore's describe uses, so the pack and explore cannot grade the\n // same service differently.\n const { inbound, outbound, modelled, apiExpected } = landscapeEvidence({\n id: entry.id,\n parses,\n relationships: parses ? land.relationships : [],\n elementIds: elements.map((e) => e.id),\n svcOf,\n });\n // Where this service RUNS, from the landscape's own deployment model. The\n // pack's reader is an agent about to write the implementation, and until this\n // slice existed it described a service's calls and contracts while saying\n // nothing whatever about its topology — so a failover got written against a\n // map nobody had read. Empty for every fleet that draws none, which is the\n // axis's opt-in and not a finding.\n //\n // Read through the PROJECT when the landscape file itself declares none, for\n // the reason `validate/service/specs.ts` pays the same load: `architecture/`\n // is one LikeC4 project and a fleet's `deployment { }` block is ordinarily a\n // file beside the landscape, not inside it. A single-file read would report\n // \"this service runs nowhere\" over a repo that draws it, which is the one\n // answer a briefing must never give.\n const topology = parses ? (land.deployment ?? NO_DEPLOYMENT) : NO_DEPLOYMENT;\n const deployment = serviceTopology(\n hasDeployment(topology) || !parses ? topology : ((await loadArchitecture(docsDir)).deployment ?? NO_DEPLOYMENT),\n elements,\n known,\n entry.id,\n );\n\n const input = { entry, archSpec: existsSync(paths.archSpec), apiExpected };\n\n const header = (text: string | null): Frontmatter =>\n text === null ? { present: false, malformed: false, data: {}, body: \"\" } : parseFrontmatter(text);\n const fm: Frontmatter = header(specText);\n\n const governs = (op: string): string[] =>\n reqs.filter((r) => r.operations.includes(op)).map((r) => r.name);\n const sent = new Set(events.sent);\n const received = new Set(events.received);\n\n return {\n maturity: serviceMaturity(input),\n missing: maturityGaps(input),\n frontmatter: {\n status: stringField(fm, \"status\") ?? null,\n owner: stringField(fm, \"owner\") ?? null,\n last_verified: stringField(fm, \"last_verified\") ?? null,\n // Through `scopeText`, not `stringField`: a scope whose value is not\n // text is still a stamped partial read, and this pack is read by an\n // agent that will otherwise take `status: verified` at face value.\n vouch_scope: scopeText(fm),\n sources: listField(fm, \"sources\"),\n },\n archSpec: { vouch_scope: scopeText(header(archText)) },\n landscape: { present, parses, modelled, inbound, outbound },\n deployment,\n requirements: reqs.map(packRequirement),\n archRequirements: archReqs.map(packRequirement),\n openapi: { unreadable: api.unreadable, ...(api.error === undefined ? {} : { error: api.error }) },\n // Document order — a fact of the file, hence of the state. `x-loam-remove`\n // markers are filtered exactly as `show` filters them: they are deletion\n // markers from a delta, not operations anybody can call.\n operations: api.ops\n .filter((o) => !o.remove)\n .map((o) => ({ id: o.id, method: o.method.toUpperCase(), path: o.path, governedBy: governs(o.id) })),\n asyncapi: { unreadable: events.unreadable, ...(events.error === undefined ? {} : { error: events.error }) },\n messages: events.messages\n .filter((m) => m.remove !== true)\n .map((m) => ({\n name: m.name,\n slot: m.slot,\n direction: sent.has(m.name) ? \"send\" : received.has(m.name) ? \"receive\" : null,\n })),\n pointers: {\n runbook: { present: entry.has.runbook, path: repoPath(docsDir, paths.runbook) },\n health: { present: entry.has.health, path: repoPath(docsDir, paths.health) },\n // The enumeration already counted the ADRs; a second readdir here could\n // only disagree with it.\n adrs: { count: entry.adrs, path: repoPath(docsDir, paths.adrsDir) },\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"living.js","sourceRoot":"","sources":["../../../src/core/pack/living.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The living slice of one service's context pack: its requirements verbatim,\n * its two contracts, its provenance, its maturity, and the fleet edges around\n * it — everything the docs repo says about the service TODAY, before any\n * feature in flight is consulted.\n *\n * Split from `./pack.ts` because it answers about the LIVING documents while\n * `./features.ts` answers about the deltas over them — two phases of one\n * briefing, and the line limit is what asked the question.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { FleetContext } from \"../fleet-context.js\";\nimport { type LoadedDoc } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport { repoPath } from \"../envelope/json.js\";\nimport { listField, parseFrontmatter, stringField, type Frontmatter } from \"../document/frontmatter.js\";\nimport { scopeText } from \"../provenance/sample/scope.js\";\nimport { type Requirement } from \"../document/spec.js\";\nimport {\n landscapeEvidence,\n maturityGaps,\n serviceMaturity,\n type LandscapeEdge,\n type Maturity,\n} from \"../vocabulary/maturity.js\";\nimport { type ServiceEntry } from \"../repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../repo/paths.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport { NO_DEPLOYMENT } from \"../c4/parsed/deployment.js\";\nimport { serviceTopology, type ServiceTopology } from \"../deployment/objects.js\";\n\n/**\n * A requirement as the pack carries it: the delta brief's `reqJson` shape plus\n * the four join lines (`Requires:`, `Capability:`, `Publishes:`, `Consumes:`)\n * the brief never needed. Verbatim on purpose — the pack is the briefing, and\n * a heading without its body is a table of contents for a file the reader then\n * has to open.\n */\nexport interface PackRequirement {\n kind: Requirement[\"kind\"];\n id?: string;\n name: string;\n text: string;\n operations: string[];\n covers: string[];\n requires: string[];\n capabilities: string[];\n publishes: string[];\n consumes: string[];\n /** Verbatim: the acceptance criteria, and the source for the tests whoever picks this up writes. */\n scenarios: Array<{ name: string; lines: string[] }>;\n}\n\nexport function packRequirement(r: Requirement): PackRequirement {\n return {\n kind: r.kind,\n ...(r.id === undefined ? {} : { id: r.id }),\n name: r.name,\n text: r.text.join(\"\\n\").trim(),\n operations: r.operations,\n covers: r.covers,\n requires: r.requires,\n capabilities: r.capabilities,\n publishes: r.publishes,\n consumes: r.consumes,\n scenarios: r.scenarios.map((s) => ({ name: s.name, lines: s.lines })),\n };\n}\n\nexport interface LivingSlice {\n maturity: Maturity;\n /** What stands between the service and `vouched` — `maturityGaps`, so it matches `list --needs-work`. */\n missing: string[];\n /**\n * `vouch_scope` rides beside `status` because this pack is read by an agent\n * about to work on the service, and a bare `status: verified` over a vouch\n * that read a recorded sample of the document would be read as the whole of\n * it having been checked. Verbatim, null when there is none.\n *\n * spec.md's header ALONE — arch.spec.md carries its own, below.\n */\n frontmatter: {\n status: string | null;\n owner: string | null;\n last_verified: string | null;\n vouch_scope: string | null;\n sources: string[];\n };\n /**\n * arch.spec.md's own `vouch_scope`, under `show --json`'s spelling\n * (`archSpec.vouch_scope`) so the two surfaces name one fact one way.\n *\n * The sample is per FILE: one `loam vouch --sample` run reads a short\n * spec.md in full and stamps a long arch.spec.md sampled, and the pack read\n * only the first header. Every other trust surface — list, show,\n * `vouch --pack`, `validate --all`, status — reported that service as\n * sampled; context printed `verified`, `vouch_scope: null`, and then all of\n * arch.spec.md's requirements verbatim, of which nobody had read half. It is\n * the one surface whose reader is an agent that will act on the licence, and\n * `loam_context` exposes it as an MCP tool besides.\n */\n archSpec: { vouch_scope: string | null };\n /**\n * `present`/`parses` ride beside the edges (explore's shape) so silence is\n * evidence, not absence: an empty edge list under `parses: false` says\n * \"nobody could look\", not \"nobody calls this\".\n *\n * `present` is about the FILE `architecture/landscape.likec4`; `parses` is\n * about the `architecture/` PROJECT, which is what the renderer loads and\n * what `adopt`, `validate --all` and `explore` all grade. A sibling document\n * with a syntax error therefore reads here as \"nobody could look\", exactly as\n * it does everywhere else.\n */\n landscape: {\n present: boolean;\n parses: boolean;\n /**\n * The `architecture/` documents that failed, docs-relative POSIX, deduped\n * and sorted — `[]` while `parses` is true. Spelled exactly as\n * `landscape.invalid` and the adopt brief spell them, because the three\n * surfaces are read one after another on the same tree and a reader has to\n * be able to tell they name the same file. It exists because `parses` moved\n * to the PROJECT while every sentence about it still said\n * `architecture/landscape.likec4`: with a broken sibling that file has no\n * errors at all, so the name sent a reader to bytes loam had read fine\n * (verification 2026-09-04).\n */\n broken: string[];\n modelled: boolean;\n inbound: LandscapeEdge[];\n outbound: LandscapeEdge[];\n };\n /**\n * Where this service runs, off the landscape's `deployment { }` model, and\n * the deployment edges touching it — replication, failover, a network hop.\n *\n * Both ends, not only the outbound one: a replication edge is as often read\n * from the standby's side as from the primary's. Empty for a fleet that draws\n * no topology, and empty is not a finding here any more than it is anywhere\n * else on this axis.\n *\n * WHAT IT IS NOT. A green deployment axis means the documents agree with each\n * other — never that the second cluster exists, is reachable, or holds the\n * data a requirement claims. `core/brief/unchecked.ts` says so beside the\n * sixteen statements that say the same about the rest of the map, and an\n * agent reading this slice is exactly the reader that sentence is for.\n */\n deployment: ServiceTopology;\n requirements: PackRequirement[];\n archRequirements: PackRequirement[];\n /** Whether the living contract could be read at all — the vacuously-green guard every sibling carries. */\n openapi: { unreadable: boolean; error?: string };\n operations: Array<{ id: string; method: string; path: string; governedBy: string[] }>;\n asyncapi: { unreadable: boolean; error?: string };\n messages: Array<{ name: string; slot: string; direction: \"send\" | \"receive\" | null }>;\n /** Free-form prose stays a pointer: unbounded documents do not belong in a bounded pack. */\n pointers: {\n runbook: { present: boolean; path: string };\n health: { present: boolean; path: string };\n adrs: { count: number; path: string };\n };\n}\n\nexport interface LivingRequest {\n docsDir: DocsDir;\n entry: ServiceEntry;\n context: FleetContext;\n}\n\nexport async function buildLiving(req: LivingRequest): Promise<LivingSlice> {\n const { docsDir, entry, context } = req;\n const paths = servicePathsAt(entry.dir);\n\n // Independent reads, fanned out together; each is memoized on the shared\n // context, so anything `./joins.ts` or `./features.ts` re-asks is free.\n const [reqs, archReqs, api, events, specText, archText, land] = await Promise.all([\n entry.has.spec ? context.readRequirements(paths.spec) : [],\n existsSync(paths.archSpec) ? context.readRequirements(paths.archSpec) : [],\n context.readOpenapi(paths.openapi),\n context.readAsyncapi(paths.asyncapi),\n entry.has.spec ? context.readText(paths.spec) : null,\n // Both headers, exactly as `show` reads both (commands/show/service.ts):\n // a memo hit on the same read the requirements above already paid for.\n existsSync(paths.archSpec) ? context.readText(paths.archSpec) : null,\n // The `architecture/` PROJECT, never `architecture/landscape.likec4` alone.\n // The map is a project — the landscape merged with every use case, palette\n // and second `model { }` block beside it — and reading one file of it made\n // this pack answer differently from `loam adopt` about the same tree\n // (review finding F26, reproduced: with a broken `architecture/palette.likec4`\n // beside a landscape that parses, `adopt --service order-service` reported\n // `parses: false` and refused to say anything about the map, while `context\n // order-service` reported `parses: true` and listed its edges). The brief's\n // own banner (core/brief/landscape.ts) already named this module as the\n // reader that disagreed. Whichever answer is right, an agent handed both\n // has no way to tell — and the renderer's answer is the project's.\n //\n // `present` stays a fact about the FILE (`existsSync` below), so a repo\n // with no landscape is still \"absent\" rather than \"empty project\".\n existsSync(landscapePath(docsDir)) ? context.architecture(docsDir) : null,\n ]);\n\n const present = land !== null;\n const parses = land !== null && land.errors.length === 0;\n const elements = parses ? land.elements : [];\n // Edges are filed under the service an element is BOUND to, with the\n // enumerated fleet riding in — the same resolution `show`, `explore` and\n // `validate` use, so no two commands can disagree about who calls whom.\n const known = new Set((await context.listServices(docsDir)).map((s) => s.id));\n const svcOf = serviceResolver(elements, known);\n // The partition, the modelled probe and the apiExpected rule are ONE shared\n // derivation (core/vocabulary/maturity.ts `landscapeEvidence`) — the same\n // one explore's describe uses, so the pack and explore cannot spell the same\n // rule two ways. They are handed the same DOCUMENTS as well: `explore` reads\n // the `architecture/` project too (core/explore/explore.ts), which it did not\n // when this slice moved off the single file — for a while the two differed by\n // input on a fleet whose sibling document is broken, which is a disagreement\n // no reader of both could resolve.\n const { inbound, outbound, modelled, apiExpected } = landscapeEvidence({\n id: entry.id,\n parses,\n relationships: parses ? land.relationships : [],\n elementIds: elements.map((e) => e.id),\n svcOf,\n });\n // Where this service RUNS, from the landscape's own deployment model. The\n // pack's reader is an agent about to write the implementation, and until this\n // slice existed it described a service's calls and contracts while saying\n // nothing whatever about its topology — so a failover got written against a\n // map nobody had read. Empty for every fleet that draws none, which is the\n // axis's opt-in and not a finding.\n //\n // Read off the PROJECT, for the reason `validate/service/specs.ts` pays the\n // same load: `architecture/` is one LikeC4 project and a fleet's\n // `deployment { }` block is ordinarily a file beside the landscape, not\n // inside it. A single-file read would report \"this service runs nowhere\" over\n // a repo that draws it, which is the one answer a briefing must never give.\n // This used to be a SECOND load, made only when the landscape file itself\n // declared no topology; `land` is that project now, so the fallback would be\n // the same document parsed twice.\n const topology = parses ? (land.deployment ?? NO_DEPLOYMENT) : NO_DEPLOYMENT;\n const deployment = serviceTopology(topology, elements, known, entry.id);\n\n const input = { entry, archSpec: existsSync(paths.archSpec), apiExpected };\n\n const header = (text: string | null): Frontmatter =>\n text === null ? { present: false, malformed: false, data: {}, body: \"\" } : parseFrontmatter(text);\n const fm: Frontmatter = header(specText);\n\n const governs = (op: string): string[] =>\n reqs.filter((r) => r.operations.includes(op)).map((r) => r.name);\n const sent = new Set(events.sent);\n const received = new Set(events.received);\n\n return {\n maturity: serviceMaturity(input),\n missing: maturityGaps(input),\n frontmatter: {\n status: stringField(fm, \"status\") ?? null,\n owner: stringField(fm, \"owner\") ?? null,\n last_verified: stringField(fm, \"last_verified\") ?? null,\n // Through `scopeText`, not `stringField`: a scope whose value is not\n // text is still a stamped partial read, and this pack is read by an\n // agent that will otherwise take `status: verified` at face value.\n vouch_scope: scopeText(fm),\n sources: listField(fm, \"sources\"),\n },\n archSpec: { vouch_scope: scopeText(header(archText)) },\n landscape: { present, parses, broken: brokenDocuments(docsDir, land), modelled, inbound, outbound },\n deployment,\n requirements: reqs.map(packRequirement),\n archRequirements: archReqs.map(packRequirement),\n openapi: { unreadable: api.unreadable, ...(api.error === undefined ? {} : { error: api.error }) },\n // Document order — a fact of the file, hence of the state. `x-loam-remove`\n // markers are filtered exactly as `show` filters them: they are deletion\n // markers from a delta, not operations anybody can call.\n operations: api.ops\n .filter((o) => !o.remove)\n .map((o) => ({ id: o.id, method: o.method.toUpperCase(), path: o.path, governedBy: governs(o.id) })),\n asyncapi: { unreadable: events.unreadable, ...(events.error === undefined ? {} : { error: events.error }) },\n messages: events.messages\n .filter((m) => m.remove !== true)\n .map((m) => ({\n name: m.name,\n slot: m.slot,\n direction: sent.has(m.name) ? \"send\" : received.has(m.name) ? \"receive\" : null,\n })),\n pointers: {\n runbook: { present: entry.has.runbook, path: repoPath(docsDir, paths.runbook) },\n health: { present: entry.has.health, path: repoPath(docsDir, paths.health) },\n // The enumeration already counted the ADRs; a second readdir here could\n // only disagree with it.\n adrs: { count: entry.adrs, path: repoPath(docsDir, paths.adrsDir) },\n },\n };\n}\n\n/**\n * The `architecture/` documents that failed, docs-relative POSIX, deduped and\n * sorted — the same spelling `commands/validate/fleet/landscape.ts` uses for\n * `landscape.invalid` and `core/brief/landscape.ts` for the brief's unparseable\n * arm, so the three surfaces name one file one way.\n */\nfunction brokenDocuments(docsDir: DocsDir, land: LoadedDoc | null): string[] {\n if (land === null) return [];\n return [...new Set(land.errors.map((e) => e.sourceFsPath).filter((p): p is string => p !== undefined))]\n .map((abs) => relative(docsDir, abs).split(/[\\\\/]/).join(\"/\"))\n .sort();\n}\n"]}
|
package/dist/core/pack/pack.js
CHANGED
|
@@ -64,12 +64,15 @@ export function packHoles(pack) {
|
|
|
64
64
|
pack.permissionsVocabulary.invalid !== undefined ||
|
|
65
65
|
pack.capabilitiesVocabulary.invalid !== undefined ||
|
|
66
66
|
pack.capabilitiesUnread.length > 0 ||
|
|
67
|
-
// The use-case axis fails the same way, and
|
|
68
|
-
// `landscape.parses`
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// unresolved element
|
|
72
|
-
//
|
|
67
|
+
// The use-case axis fails the same way, and it is still its own clause
|
|
68
|
+
// after `landscape.parses` learned to answer for the whole `architecture/`
|
|
69
|
+
// PROJECT (core/pack/living.ts). It used to be strictly wider: that flag
|
|
70
|
+
// was about `architecture/landscape.likec4` alone, so a use-case file with
|
|
71
|
+
// an unresolved element left it true and emptied every flow in the pack.
|
|
72
|
+
// The two now flip together on a fleet that HAS a landscape — and the one
|
|
73
|
+
// that has none is why this clause stays: `landscape.present` gates the
|
|
74
|
+
// first, so a repo whose only map documents are broken `usecases/*.likec4`
|
|
75
|
+
// would otherwise report a whole empty axis at exit 0.
|
|
73
76
|
pack.useCaseScan.unreadable ||
|
|
74
77
|
pack.features.some((f) => f.architecture.errors.length > 0 || f.openapi.unreadable || f.events.unreadable));
|
|
75
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pack.js","sourceRoot":"","sources":["../../../src/core/pack/pack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAwC,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAoB,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAmB,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,aAAa,EAAoB,MAAM,eAAe,CAAC;AA0BhE;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAgB;IACjD,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC;IACjD,sEAAsE;IACtE,uEAAuE;IACvE,0EAA0E;IAC1E,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAClD,WAAW,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QACxC,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QACvC,aAAa,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;KACpD,CAAC,CAAC;IACH,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,EAAE;QACjB,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC;QAClC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE;QAC7C,GAAG,MAAM;QACT,GAAG,KAAK;QACR,QAAQ;KACT,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,IAAiB;IACzC,OAAO,CACL,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;QAClD,IAAI,CAAC,OAAO,CAAC,UAAU;QACvB,IAAI,CAAC,QAAQ,CAAC,UAAU;QACxB,uEAAuE;QACvE,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,IAAI,CAAC,qBAAqB,CAAC,OAAO,KAAK,SAAS;QAChD,IAAI,CAAC,sBAAsB,CAAC,OAAO,KAAK,SAAS;QACjD,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC;QAClC,
|
|
1
|
+
{"version":3,"file":"pack.js","sourceRoot":"","sources":["../../../src/core/pack/pack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAwC,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAoB,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAmB,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,aAAa,EAAoB,MAAM,eAAe,CAAC;AA0BhE;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAgB;IACjD,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC;IACjD,sEAAsE;IACtE,uEAAuE;IACvE,0EAA0E;IAC1E,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAClD,WAAW,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QACxC,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QACvC,aAAa,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;KACpD,CAAC,CAAC;IACH,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,EAAE;QACjB,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC;QAClC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE;QAC7C,GAAG,MAAM;QACT,GAAG,KAAK;QACR,QAAQ;KACT,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,IAAiB;IACzC,OAAO,CACL,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;QAClD,IAAI,CAAC,OAAO,CAAC,UAAU;QACvB,IAAI,CAAC,QAAQ,CAAC,UAAU;QACxB,uEAAuE;QACvE,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,IAAI,CAAC,qBAAqB,CAAC,OAAO,KAAK,SAAS;QAChD,IAAI,CAAC,sBAAsB,CAAC,OAAO,KAAK,SAAS;QACjD,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC;QAClC,uEAAuE;QACvE,2EAA2E;QAC3E,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,0EAA0E;QAC1E,wEAAwE;QACxE,2EAA2E;QAC3E,uDAAuD;QACvD,IAAI,CAAC,WAAW,CAAC,UAAU;QAC3B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAChB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,CACvF,CACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * One service's context pack: the exact docs slice bound to a service —\n * living requirements verbatim, both contracts, the fleet edges one hop out,\n * the permission and capability joins, provenance, and every active feature's\n * delta over it — assembled as one deterministic payload an agent loads before\n * working in that service's repository.\n *\n * Core rather than the command, exactly as `core/explore/explore.ts` sits\n * behind `loam explore`: a future facade (an MCP tool, a second renderer)\n * reuses the assembly without the printing, and the command layer stays I/O.\n * Deterministic by construction — identical state must yield identical bytes:\n * no timestamps, repo-relative forward-slash paths only, every unioned set\n * sorted with `compareIds`, contract entries in document order (a fact of the\n * file, hence of the state).\n */\nimport { FleetContext } from \"../fleet-context.js\";\nimport { repoPath } from \"../envelope/json.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"../repo/entries.js\";\nimport { buildLiving, type LivingSlice } from \"./living.js\";\nimport { buildJoins, type JoinsSlice } from \"./joins.js\";\nimport { buildFeatures, type PackFeature } from \"./features.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\nexport interface PackRequest {\n docsDir: DocsDir;\n /** The service, as the enumeration answered it — the entry, not an id and a dir (rule 17). */\n entry: ServiceEntry;\n /** The one feature `--feature` resolved, or null for every touching feature. */\n feature: FeatureEntry | null;\n context: FleetContext;\n}\n\nexport interface ContextPack extends LivingSlice, JoinsSlice {\n service: string;\n path: string;\n /**\n * The canonical id of the one feature `--feature` narrowed to, or null for\n * the everything-in-flight default. Echoed so a stored pack SAYS it is a\n * narrowed view: two packs of the same service can otherwise differ only by\n * what they silently left out, and an agent diffing them (or resuming from\n * one) has no way to tell a narrowed pack from a fleet gone quiet.\n */\n feature: string | null;\n features: PackFeature[];\n}\n\n/**\n * Assemble the pack. Reads only; writes nothing, and refuses nothing — every\n * miss is a field, and an unreadable DOCUMENT is a flag the caller grades\n * (`packHoles`). What does throw — an unreadable directory, bytes that are\n * not UTF-8 — is the all-or-nothing class explore takes too: the honest\n * answer is a refusal naming the path, not a pack with a silent hole.\n */\nexport async function assemblePack(req: PackRequest): Promise<ContextPack> {\n const { docsDir, entry, feature, context } = req;\n // The three sections are independent derivations over one shared read\n // index; identical reads share their in-flight promise, so the fan-out\n // costs no duplicate I/O and no section can see a different file version.\n const [living, joins, features] = await Promise.all([\n buildLiving({ docsDir, entry, context }),\n buildJoins({ docsDir, entry, context }),\n buildFeatures({ docsDir, entry, feature, context }),\n ]);\n return {\n service: entry.id,\n path: repoPath(docsDir, entry.dir),\n feature: feature === null ? null : feature.id,\n ...living,\n ...joins,\n features,\n };\n}\n\n/**\n * Whether the pack has a silent hole: a document that exists but could not be\n * read, so a section that LOOKS empty is really unanswered. The command maps\n * this to exit 1 with `ok: true` — delta's precedent, same reasoning: the\n * pack is consumed by agents that never ran validate, so the exit code must\n * carry the failure itself. Set before the format fork; the guard is about\n * the pack, not about how it is rendered.\n */\nexport function packHoles(pack: ContextPack): boolean {\n return (\n (pack.landscape.present && !pack.landscape.parses) ||\n pack.openapi.unreadable ||\n pack.asyncapi.unreadable ||\n // The two fleet vocabularies and the rollup's fleet walk fail the same\n // way the contracts do: an unreadable file EMPTIES a section (`declared:\n // false` on every permission, \"(none realized here)\") while looking like\n // an answer, so their health is part of the hole contract, not decoration.\n pack.permissionsVocabulary.invalid !== undefined ||\n pack.capabilitiesVocabulary.invalid !== undefined ||\n pack.capabilitiesUnread.length > 0 ||\n // The use-case axis fails the same way, and it is still its own clause\n // after `landscape.parses` learned to answer for the whole `architecture/`\n // PROJECT (core/pack/living.ts). It used to be strictly wider: that flag\n // was about `architecture/landscape.likec4` alone, so a use-case file with\n // an unresolved element left it true and emptied every flow in the pack.\n // The two now flip together on a fleet that HAS a landscape — and the one\n // that has none is why this clause stays: `landscape.present` gates the\n // first, so a repo whose only map documents are broken `usecases/*.likec4`\n // would otherwise report a whole empty axis at exit 0.\n pack.useCaseScan.unreadable ||\n pack.features.some(\n (f) => f.architecture.errors.length > 0 || f.openapi.unreadable || f.events.unreadable,\n )\n );\n}\n"]}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* because `core/c4/arch.ts` already exists and a twin name invites the wrong
|
|
7
7
|
* import.
|
|
8
8
|
*/
|
|
9
|
-
import {} from "../c4/likec4.js";
|
|
9
|
+
import { errorText } from "../c4/likec4.js";
|
|
10
10
|
import { elementService, serviceResolver } from "../c4/resolve/service.js";
|
|
11
11
|
import { DocsRepoUnavailableError } from "../repo/state.js";
|
|
12
12
|
import { listServices } from "../repo/repo.js";
|
|
@@ -37,7 +37,7 @@ export function archSlice(doc, service, featureId, known) {
|
|
|
37
37
|
return empty;
|
|
38
38
|
const { errors, elements, relationships } = doc;
|
|
39
39
|
if (errors.length > 0) {
|
|
40
|
-
return { ...empty, errors: errors.map(
|
|
40
|
+
return { ...empty, errors: errors.map(errorText) };
|
|
41
41
|
}
|
|
42
42
|
// Which service an element stands for is the binding's call, not the title's
|
|
43
43
|
// — and the enumerated fleet rides in so the slice agrees with `validate`
|