@ybotok/loam 0.2.0-alpha.4 → 0.2.0-alpha.6
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 +947 -0
- package/MIGRATING-from-OpenSpec.md +7 -1
- package/QUICKSTART.md +8 -3
- package/README.md +20 -13
- package/ROADMAP.md +329 -22
- package/SCHEMA.md +472 -24
- 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 +5 -2
- 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/plan.js +97 -3
- 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/sync.js +184 -39
- 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 +124 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +29 -3
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +113 -16
- 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/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 +11 -10
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +128 -114
- 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 +103 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +96 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +298 -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/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 +13 -9
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +97 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +2 -2
- 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 +18 -2
- 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 +51 -2
- 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.js +14 -4
- 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 +125 -6
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +35 -19
- 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/brief/brief.js +63 -11
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- 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 +30 -26
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +12 -2
- 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 +52 -4
- 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 +9 -4
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +77 -38
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +46 -12
- 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 +4 -0
- 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 +31 -22
- 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 +48 -9
- 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/families.js +1 -1
- package/dist/core/explain/families.js.map +1 -1
- 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/partners.js +5 -9
- 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 +40 -0
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +2 -3
- package/dist/core/repo/tree/render/stale.js.map +1 -1
- package/dist/core/repo/tree/render/views.js +51 -3
- 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/vocabulary/maturity.js +10 -0
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +23 -0
- package/examples/docs/architecture/landscape.likec4 +41 -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 +53 -67
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +32 -33
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +29 -33
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +39 -59
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../../src/core/fleet-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Request-scoped read index for commands that walk a whole loam repository.\n *\n * The filesystem remains the source of truth. This class deliberately has no\n * singleton/global instance: a command creates one after loading loam.json and\n * drops it when that invocation finishes. Within that lifetime identical\n * reads share their in-flight Promise, which removes both duplicate I/O and\n * duplicate parsing without making a later CLI invocation observe stale data.\n *\n * Because every markdown artifact is read HERE, this is also where the two\n * failures a read must never swallow are answered: bytes that are not UTF-8\n * (which `readFile(path, \"utf8\")` silently turns into U+FFFD, so a UTF-16\n * spec.md parses as zero requirements and grades green), and git conflict\n * markers left in a living document (which parse as prose, and which the next\n * `loam archive` rewrites away). One place, so no reader can forget. Both\n * RULES live in leaves — the decode in `document-bytes.ts`, the marker scan\n * and its findings in `conflict-markers.ts` — because the parsers and graders\n * that touch a single file without an index owe the same rules, and must not\n * have to import this module — and the fleet it pulls in behind it — to obey\n * them.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readAsyncapi } from \"./asyncapi/read.js\";\nimport type { AsyncapiDoc } from \"./asyncapi/model.js\";\nimport { readCapabilityVocabulary, type CapabilityVocabulary } from \"./capabilities/capabilities.js\";\nimport { featureCapabilityDeltas } from \"./capabilities/delta/tree.js\";\nimport type { CapabilityTree } from \"./capabilities/tree.js\";\nimport { conflictMarkerLines } from \"./conflict-markers.js\";\nimport { decodeDocument } from \"./kernel/document-bytes.js\";\nimport { loadFile, type LoadedDoc } from \"./c4/likec4.js\";\nimport { loadBatch } from \"./c4/workspace.js\";\nimport { readOpenapi, type OpenapiDoc, type Operation } from \"./openapi/doc.js\";\nimport type { RawServiceId } from \"./kernel/ids/service.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"./repo/entries.js\";\nimport { featureSpecServices, listFeatures, listFleetTree, serviceEntries } from \"./repo/repo.js\";\nimport type { FleetTree } from \"./repo/tree/walk.js\";\nimport { parseRequirements } from \"./document/parse.js\";\nimport { type Requirement } from \"./document/spec.js\";\nimport type { DocsDir, FeatureDir } from \"./kernel/ids/dirs.js\";\n\nexport interface FleetContextStats {\n serviceEnumerations: number;\n featureEnumerations: number;\n featureServiceEnumerations: number;\n textReads: number;\n requirementParses: number;\n openapiParses: number;\n /**\n * Reads of a service's `asyncapi.yaml`. Counted separately because this axis\n * is the one that makes a per-service run read the whole fleet: \"does anybody\n * publish this message\" has no local answer, so `spine.message-unproduced`\n * walks every service's contract. The memo is what keeps that one walk per\n * command instead of one per consuming edge.\n */\n asyncapiParses: number;\n /**\n * Reads of the capability vocabulary — `architecture/capabilities.yaml` AND\n * the `capabilities/` walk, which are one vocabulary and so one read. One per\n * invocation is the capability axis's exit criterion, and this counter is how\n * a test pins it. (Permissions predates the memo and re-parses per service —\n * do not copy that.)\n */\n capabilityParses: number;\n /**\n * Walks of ONE feature's `capabilities/` delta tree. Counted apart from\n * `capabilityParses` because it answers a different question about a\n * different directory, and because a fleet that has not adopted the business\n * axis must be able to prove it pays nothing: every feature's walk is a\n * single `existsSync` that finds no directory, so this counter rises while\n * `textReads` and `requirementParses` do not.\n */\n featureCapabilityWalks: number;\n likec4Loads: number;\n}\n\nconst key = (path: string): string => resolve(path);\n\n/** One coherent filesystem snapshot for one command invocation. */\nexport class FleetContext {\n private readonly trees = new Map<string, Promise<FleetTree>>();\n private readonly services = new Map<string, Promise<ServiceEntry[]>>();\n private readonly features = new Map<string, Promise<FeatureEntry[]>>();\n private readonly featureServices = new Map<string, Promise<RawServiceId[]>>();\n private readonly texts = new Map<string, Promise<string>>();\n private readonly requirements = new Map<string, Promise<Requirement[]>>();\n private readonly openapis = new Map<string, Promise<OpenapiDoc>>();\n private readonly asyncapis = new Map<string, Promise<AsyncapiDoc>>();\n private readonly capabilityVocabularies = new Map<string, Promise<CapabilityVocabulary>>();\n private readonly featureCapabilities = new Map<string, Promise<CapabilityTree>>();\n private readonly likec4 = new Map<string, Promise<LoadedDoc>>();\n\n private readonly counts: FleetContextStats = {\n serviceEnumerations: 0,\n featureEnumerations: 0,\n featureServiceEnumerations: 0,\n textReads: 0,\n requirementParses: 0,\n openapiParses: 0,\n asyncapiParses: 0,\n capabilityParses: 0,\n featureCapabilityWalks: 0,\n likec4Loads: 0,\n };\n\n /** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */\n stats(): Readonly<FleetContextStats> {\n return { ...this.counts };\n }\n\n /**\n * The classified `services/` tree — the ONE enumeration this invocation\n * performs. `listServices` below derives its entries from this memo, so a\n * command that asks for both the fleet and the tree's findings (`validate\n * --all` does) still walks the directories exactly once. The counter keeps\n * its historical meaning — walks of `services/` — it just counts the walk\n * where the walk now happens.\n */\n fleetTree(docsDir: DocsDir): Promise<FleetTree> {\n const k = key(docsDir);\n let pending = this.trees.get(k);\n if (pending === undefined) {\n this.counts.serviceEnumerations += 1;\n pending = listFleetTree(docsDir);\n this.trees.set(k, pending);\n }\n return pending;\n }\n\n listServices(docsDir: DocsDir): Promise<ServiceEntry[]> {\n const k = key(docsDir);\n let pending = this.services.get(k);\n if (pending === undefined) {\n pending = this.fleetTree(docsDir).then(serviceEntries);\n this.services.set(k, pending);\n }\n return pending;\n }\n\n listFeatures(docsDir: DocsDir, opts: { includeArchived?: boolean } = {}): Promise<FeatureEntry[]> {\n const k = `${key(docsDir)}\\0${opts.includeArchived === true ? \"all\" : \"active\"}`;\n let pending = this.features.get(k);\n if (pending === undefined) {\n this.counts.featureEnumerations += 1;\n pending = listFeatures(docsDir, opts).then((entries) => {\n // listFeatures has already enumerated every feature's specs/ directory;\n // seed the narrower lookup so validation/coherence do not read it again.\n for (const entry of entries) {\n const featureKey = key(entry.dir);\n if (!this.featureServices.has(featureKey)) {\n this.featureServices.set(featureKey, Promise.resolve(entry.services));\n }\n }\n return entries;\n });\n this.features.set(k, pending);\n }\n return pending;\n }\n\n featureSpecServices(featureDir: FeatureDir): Promise<RawServiceId[]> {\n const k = key(featureDir);\n let pending = this.featureServices.get(k);\n if (pending === undefined) {\n this.counts.featureServiceEnumerations += 1;\n pending = featureSpecServices(featureDir);\n this.featureServices.set(k, pending);\n }\n return pending;\n }\n\n /**\n * A markdown artifact as text. Bytes in, decoded once, and refused rather\n * than degraded when they are not UTF-8 — see NotUtf8DocumentError.\n */\n readText(path: string): Promise<string> {\n const k = key(path);\n let pending = this.texts.get(k);\n if (pending === undefined) {\n this.counts.textReads += 1;\n pending = readFile(path).then((bytes) => decodeDocument(bytes, path));\n this.texts.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Git conflict markers in an already-read document, by line. Free for any\n * caller that was going to read the file anyway — which is every caller,\n * which is the point: the marker check used to cost a whole extra read of one\n * hand-picked file, so it was done for one hand-picked file.\n */\n async conflictMarkers(path: string): Promise<number[]> {\n return conflictMarkerLines(await this.readText(path));\n }\n\n readRequirements(path: string): Promise<Requirement[]> {\n const k = key(path);\n let pending = this.requirements.get(k);\n if (pending === undefined) {\n this.counts.requirementParses += 1;\n pending = this.readText(path).then(parseRequirements);\n this.requirements.set(k, pending);\n }\n return pending;\n }\n\n readOpenapi(path: string): Promise<OpenapiDoc> {\n const k = key(path);\n let pending = this.openapis.get(k);\n if (pending === undefined) {\n this.counts.openapiParses += 1;\n pending = readOpenapi(path);\n this.openapis.set(k, pending);\n }\n return pending;\n }\n\n readAsyncapi(path: string): Promise<AsyncapiDoc> {\n const k = key(path);\n let pending = this.asyncapis.get(k);\n if (pending === undefined) {\n this.counts.asyncapiParses += 1;\n pending = readAsyncapi(path);\n this.asyncapis.set(k, pending);\n }\n return pending;\n }\n\n /**\n * The capability vocabulary, read once per invocation. Direction rule,\n * load-bearing: this file imports `capabilities/capabilities.js`, so nothing\n * under src/core/capabilities/ may import this module back — that edge is a\n * core-root↔capabilities package cycle `import/no-cycle` cannot see, which is\n * why the functions there take plain data or a `read` function, never a FleetContext.\n */\n capabilities(docsDir: DocsDir): Promise<CapabilityVocabulary> {\n const k = key(docsDir);\n let pending = this.capabilityVocabularies.get(k);\n if (pending === undefined) {\n this.counts.capabilityParses += 1;\n pending = readCapabilityVocabulary(docsDir);\n this.capabilityVocabularies.set(k, pending);\n }\n return pending;\n }\n\n /**\n * One feature's `capabilities/` delta tree, walked once per invocation. Two\n * readers ask for it in the same run — the delta-shape walk\n * (`core/delta/delta.ts`) and the capability overlay\n * (`core/coherence/declared.ts`) — and without the memo a feature carrying\n * three capability deltas paid the readdirs twice.\n *\n * The direction rule stated on `capabilities()` above applies verbatim: the\n * function this stands in front of takes a plain `FeatureDir`, never a\n * FleetContext, so `src/core/capabilities/` never imports this module back.\n */\n featureCapabilityDeltas(featureDir: FeatureDir): Promise<CapabilityTree> {\n const k = key(featureDir);\n let pending = this.featureCapabilities.get(k);\n if (pending === undefined) {\n this.counts.featureCapabilityWalks += 1;\n pending = featureCapabilityDeltas(featureDir);\n this.featureCapabilities.set(k, pending);\n }\n return pending;\n }\n\n async operations(path: string): Promise<Operation[]> {\n return (await this.readOpenapi(path)).ops;\n }\n\n async operationIds(path: string): Promise<string[]> {\n return (await this.readOpenapi(path)).ops.map((op) => op.id);\n }\n\n /*\n * Deliberately no `serviceOperationIds` here. The living-plus-feature union is\n * a RULE — every removal applied before any upsert — not a read, and a second\n * copy of a rule is a second chance to spell it differently. This one did: it\n * interleaved removals with upserts, so a relocated operation answered\n * \"defined\" through `archive` (no context) and \"gone\" through `validate` and\n * `status` (context), or the reverse, depending on the order the feature's\n * YAML happened to spell the two slots in. `core/openapi/doc.ts` owns the rule and\n * threads a context through the reads underneath it, which is the only part\n * this class was ever needed for.\n */\n\n loadLikeC4(path: string): Promise<LoadedDoc> {\n const k = key(path);\n let pending = this.likec4.get(k);\n if (pending === undefined) {\n this.counts.likec4Loads += 1;\n pending = loadFile(path);\n this.likec4.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Parse many `.likec4` documents through ONE shared workspace (`loadBatch`)\n * and seed the memo above, so every later `loadLikeC4` in this invocation is\n * a hit. Purely an accelerator: the cache is still this instance's — one\n * invocation, one snapshot, no module state — and the counter still means\n * \"documents parsed via LikeC4\", incremented once per batch-parsed document.\n *\n * The failure story is the contract here. A batch-INFRASTRUCTURE failure —\n * a sandboxed runner denying tmpdir writes is the environment class ROADMAP\n * documents — degrades to today's per-path loads by seeding nothing: never\n * an error, and never fewer findings, only the old speed. A path the batch\n * could not stage (missing, unreadable) is simply absent from the result, so\n * the later `loadLikeC4` reproduces today's per-path error exactly. And\n * fewer than two misses returns without a workspace at all: one document\n * gains nothing from batch isolation it already has.\n */\n async prefetchLikeC4(paths: string[]): Promise<void> {\n const missing = [...new Set(paths.map(key))].filter((k) => !this.likec4.has(k));\n if (missing.length < 2) return;\n let docs: Map<string, LoadedDoc>;\n try {\n docs = await loadBatch(missing);\n } catch {\n return;\n }\n for (const [k, doc] of docs) {\n // Keyed by the same `key()` as loadLikeC4 — loadBatch resolves paths the\n // identical way, and `missing` was already resolved, so this holds by\n // construction; the has() guard keeps a concurrent load's promise\n // authoritative rather than double-counting the parse.\n if (this.likec4.has(k)) continue;\n this.counts.likec4Loads += 1;\n this.likec4.set(k, Promise.resolve(doc));\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../../src/core/fleet-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Request-scoped read index for commands that walk a whole loam repository.\n *\n * The filesystem remains the source of truth. This class deliberately has no\n * singleton/global instance: a command creates one after loading loam.json and\n * drops it when that invocation finishes. Within that lifetime identical\n * reads share their in-flight Promise, which removes both duplicate I/O and\n * duplicate parsing without making a later CLI invocation observe stale data.\n *\n * Because every markdown artifact is read HERE, this is also where the two\n * failures a read must never swallow are answered: bytes that are not UTF-8\n * (which `readFile(path, \"utf8\")` silently turns into U+FFFD, so a UTF-16\n * spec.md parses as zero requirements and grades green), and git conflict\n * markers left in a living document (which parse as prose, and which the next\n * `loam archive` rewrites away). One place, so no reader can forget. Both\n * RULES live in leaves — the decode in `document-bytes.ts`, the marker scan\n * and its findings in `conflict-markers.ts` — because the parsers and graders\n * that touch a single file without an index owe the same rules, and must not\n * have to import this module — and the fleet it pulls in behind it — to obey\n * them.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readAsyncapi } from \"./asyncapi/read.js\";\nimport type { AsyncapiDoc } from \"./asyncapi/model.js\";\nimport { readCapabilityVocabulary, type CapabilityVocabulary } from \"./capabilities/capabilities.js\";\nimport { featureCapabilityDeltas } from \"./capabilities/delta/tree.js\";\nimport type { CapabilityTree } from \"./capabilities/tree.js\";\nimport { conflictMarkerLines } from \"./conflict-markers.js\";\nimport { decodeDocument } from \"./kernel/document-bytes.js\";\nimport { loadFile, type LoadedDoc } from \"./c4/likec4.js\";\nimport { loadBatch } from \"./c4/workspace.js\";\nimport { ServiceModels } from \"./c4/prefetch/fleet.js\";\nimport type { ServiceModel } from \"./c4/service-model/load.js\";\nimport type { ModelShape } from \"./c4/service-model/shape.js\";\nimport type { ServicePaths } from \"./repo/paths.js\";\nimport { readOpenapi, type OpenapiDoc, type Operation } from \"./openapi/doc.js\";\nimport type { RawServiceId } from \"./kernel/ids/service.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"./repo/entries.js\";\nimport { featureSpecServices, listFeatures, listFleetTree, serviceEntries } from \"./repo/repo.js\";\nimport type { FleetTree } from \"./repo/tree/walk.js\";\nimport { parseRequirements } from \"./document/parse.js\";\nimport { type Requirement } from \"./document/spec.js\";\nimport type { DocsDir, FeatureDir } from \"./kernel/ids/dirs.js\";\n\nexport interface FleetContextStats {\n serviceEnumerations: number;\n featureEnumerations: number;\n featureServiceEnumerations: number;\n textReads: number;\n requirementParses: number;\n openapiParses: number;\n /**\n * Reads of a service's `asyncapi.yaml`. Counted separately because this axis\n * is the one that makes a per-service run read the whole fleet: \"does anybody\n * publish this message\" has no local answer, so `spine.message-unproduced`\n * walks every service's contract. The memo is what keeps that one walk per\n * command instead of one per consuming edge.\n */\n asyncapiParses: number;\n /**\n * Reads of the capability vocabulary — `architecture/capabilities.yaml` AND\n * the `capabilities/` walk, which are one vocabulary and so one read. One per\n * invocation is the capability axis's exit criterion, and this counter is how\n * a test pins it. (Permissions predates the memo and re-parses per service —\n * do not copy that.)\n */\n capabilityParses: number;\n /**\n * Walks of ONE feature's `capabilities/` delta tree. Counted apart from\n * `capabilityParses` because it answers a different question about a\n * different directory, and because a fleet that has not adopted the business\n * axis must be able to prove it pays nothing: every feature's walk is a\n * single `existsSync` that finds no directory, so this counter rises while\n * `textReads` and `requirementParses` do not.\n */\n featureCapabilityWalks: number;\n likec4Loads: number;\n /**\n * PER-SERVICE PROJECTS parsed — the map plus one extending model, staged\n * together (`c4/service-model/`). Apart from `likec4Loads` because they are\n * different units: that counter means documents parsed alone, and a fleet\n * whose models all extend the map moves every unit to this one. One project\n * per service is the axis's exit criterion, and this is how a test pins it.\n */\n projectLoads: number;\n}\n\nconst key = (path: string): string => resolve(path);\n\n/** One coherent filesystem snapshot for one command invocation. */\nexport class FleetContext {\n private readonly trees = new Map<string, Promise<FleetTree>>();\n private readonly services = new Map<string, Promise<ServiceEntry[]>>();\n private readonly features = new Map<string, Promise<FeatureEntry[]>>();\n private readonly featureServices = new Map<string, Promise<RawServiceId[]>>();\n private readonly texts = new Map<string, Promise<string>>();\n private readonly requirements = new Map<string, Promise<Requirement[]>>();\n private readonly openapis = new Map<string, Promise<OpenapiDoc>>();\n private readonly asyncapis = new Map<string, Promise<AsyncapiDoc>>();\n private readonly capabilityVocabularies = new Map<string, Promise<CapabilityVocabulary>>();\n private readonly featureCapabilities = new Map<string, Promise<CapabilityTree>>();\n private readonly likec4 = new Map<string, Promise<LoadedDoc>>();\n /**\n * The service-model axis's own memos, one object because the rules they\n * encode are `c4/prefetch/fleet.ts`'s rather than this class's. It counts\n * through this instance's counter, so `stats()` stays the one place a caller\n * reads what an invocation cost.\n */\n private readonly models = new ServiceModels({\n onProjectLoad: () => {\n this.counts.projectLoads += 1;\n },\n known: async (docsDir) => new Set((await this.listServices(docsDir)).map((s) => s.id)),\n standalone: (path) => this.loadLikeC4(path),\n });\n\n private readonly counts: FleetContextStats = {\n serviceEnumerations: 0,\n featureEnumerations: 0,\n featureServiceEnumerations: 0,\n textReads: 0,\n requirementParses: 0,\n openapiParses: 0,\n asyncapiParses: 0,\n capabilityParses: 0,\n featureCapabilityWalks: 0,\n likec4Loads: 0,\n projectLoads: 0,\n };\n\n /** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */\n stats(): Readonly<FleetContextStats> {\n return { ...this.counts };\n }\n\n /**\n * The classified `services/` tree — the ONE enumeration this invocation\n * performs. `listServices` below derives its entries from this memo, so a\n * command that asks for both the fleet and the tree's findings (`validate\n * --all` does) still walks the directories exactly once. The counter keeps\n * its historical meaning — walks of `services/` — it just counts the walk\n * where the walk now happens.\n */\n fleetTree(docsDir: DocsDir): Promise<FleetTree> {\n const k = key(docsDir);\n let pending = this.trees.get(k);\n if (pending === undefined) {\n this.counts.serviceEnumerations += 1;\n pending = listFleetTree(docsDir);\n this.trees.set(k, pending);\n }\n return pending;\n }\n\n listServices(docsDir: DocsDir): Promise<ServiceEntry[]> {\n const k = key(docsDir);\n let pending = this.services.get(k);\n if (pending === undefined) {\n pending = this.fleetTree(docsDir).then(serviceEntries);\n this.services.set(k, pending);\n }\n return pending;\n }\n\n listFeatures(docsDir: DocsDir, opts: { includeArchived?: boolean } = {}): Promise<FeatureEntry[]> {\n const k = `${key(docsDir)}\\0${opts.includeArchived === true ? \"all\" : \"active\"}`;\n let pending = this.features.get(k);\n if (pending === undefined) {\n this.counts.featureEnumerations += 1;\n pending = listFeatures(docsDir, opts).then((entries) => {\n // listFeatures has already enumerated every feature's specs/ directory;\n // seed the narrower lookup so validation/coherence do not read it again.\n for (const entry of entries) {\n const featureKey = key(entry.dir);\n if (!this.featureServices.has(featureKey)) {\n this.featureServices.set(featureKey, Promise.resolve(entry.services));\n }\n }\n return entries;\n });\n this.features.set(k, pending);\n }\n return pending;\n }\n\n featureSpecServices(featureDir: FeatureDir): Promise<RawServiceId[]> {\n const k = key(featureDir);\n let pending = this.featureServices.get(k);\n if (pending === undefined) {\n this.counts.featureServiceEnumerations += 1;\n pending = featureSpecServices(featureDir);\n this.featureServices.set(k, pending);\n }\n return pending;\n }\n\n /**\n * A markdown artifact as text. Bytes in, decoded once, and refused rather\n * than degraded when they are not UTF-8 — see NotUtf8DocumentError.\n */\n readText(path: string): Promise<string> {\n const k = key(path);\n let pending = this.texts.get(k);\n if (pending === undefined) {\n this.counts.textReads += 1;\n pending = readFile(path).then((bytes) => decodeDocument(bytes, path));\n this.texts.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Git conflict markers in an already-read document, by line. Free for any\n * caller that was going to read the file anyway — which is every caller,\n * which is the point: the marker check used to cost a whole extra read of one\n * hand-picked file, so it was done for one hand-picked file.\n */\n async conflictMarkers(path: string): Promise<number[]> {\n return conflictMarkerLines(await this.readText(path));\n }\n\n readRequirements(path: string): Promise<Requirement[]> {\n const k = key(path);\n let pending = this.requirements.get(k);\n if (pending === undefined) {\n this.counts.requirementParses += 1;\n pending = this.readText(path).then(parseRequirements);\n this.requirements.set(k, pending);\n }\n return pending;\n }\n\n readOpenapi(path: string): Promise<OpenapiDoc> {\n const k = key(path);\n let pending = this.openapis.get(k);\n if (pending === undefined) {\n this.counts.openapiParses += 1;\n pending = readOpenapi(path);\n this.openapis.set(k, pending);\n }\n return pending;\n }\n\n readAsyncapi(path: string): Promise<AsyncapiDoc> {\n const k = key(path);\n let pending = this.asyncapis.get(k);\n if (pending === undefined) {\n this.counts.asyncapiParses += 1;\n pending = readAsyncapi(path);\n this.asyncapis.set(k, pending);\n }\n return pending;\n }\n\n /**\n * The capability vocabulary, read once per invocation. Direction rule,\n * load-bearing: this file imports `capabilities/capabilities.js`, so nothing\n * under src/core/capabilities/ may import this module back — that edge is a\n * core-root↔capabilities package cycle `import/no-cycle` cannot see, which is\n * why the functions there take plain data or a `read` function, never a FleetContext.\n */\n capabilities(docsDir: DocsDir): Promise<CapabilityVocabulary> {\n const k = key(docsDir);\n let pending = this.capabilityVocabularies.get(k);\n if (pending === undefined) {\n this.counts.capabilityParses += 1;\n pending = readCapabilityVocabulary(docsDir);\n this.capabilityVocabularies.set(k, pending);\n }\n return pending;\n }\n\n /**\n * One feature's `capabilities/` delta tree, walked once per invocation. Two\n * readers ask for it in the same run — the delta-shape walk\n * (`core/delta/delta.ts`) and the capability overlay\n * (`core/coherence/declared.ts`) — and without the memo a feature carrying\n * three capability deltas paid the readdirs twice.\n *\n * The direction rule stated on `capabilities()` above applies verbatim: the\n * function this stands in front of takes a plain `FeatureDir`, never a\n * FleetContext, so `src/core/capabilities/` never imports this module back.\n */\n featureCapabilityDeltas(featureDir: FeatureDir): Promise<CapabilityTree> {\n const k = key(featureDir);\n let pending = this.featureCapabilities.get(k);\n if (pending === undefined) {\n this.counts.featureCapabilityWalks += 1;\n pending = featureCapabilityDeltas(featureDir);\n this.featureCapabilities.set(k, pending);\n }\n return pending;\n }\n\n async operations(path: string): Promise<Operation[]> {\n return (await this.readOpenapi(path)).ops;\n }\n\n async operationIds(path: string): Promise<string[]> {\n return (await this.readOpenapi(path)).ops.map((op) => op.id);\n }\n\n /*\n * Deliberately no `serviceOperationIds` here. The living-plus-feature union is\n * a RULE — every removal applied before any upsert — not a read, and a second\n * copy of a rule is a second chance to spell it differently. This one did: it\n * interleaved removals with upserts, so a relocated operation answered\n * \"defined\" through `archive` (no context) and \"gone\" through `validate` and\n * `status` (context), or the reverse, depending on the order the feature's\n * YAML happened to spell the two slots in. `core/openapi/doc.ts` owns the rule and\n * threads a context through the reads underneath it, which is the only part\n * this class was ever needed for.\n */\n\n loadLikeC4(path: string): Promise<LoadedDoc> {\n const k = key(path);\n let pending = this.likec4.get(k);\n if (pending === undefined) {\n this.counts.likec4Loads += 1;\n pending = loadFile(path);\n this.likec4.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Parse many `.likec4` documents through ONE shared workspace (`loadBatch`)\n * and seed the memo above, so every later `loadLikeC4` in this invocation is\n * a hit. Purely an accelerator: the cache is still this instance's — one\n * invocation, one snapshot, no module state — and the counter still means\n * \"documents parsed via LikeC4\", incremented once per batch-parsed document.\n *\n * The failure story is the contract here. A batch-INFRASTRUCTURE failure —\n * a sandboxed runner denying tmpdir writes is the environment class ROADMAP\n * documents — degrades to today's per-path loads by seeding nothing: never\n * an error, and never fewer findings, only the old speed. A path the batch\n * could not stage (missing, unreadable) is simply absent from the result, so\n * the later `loadLikeC4` reproduces today's per-path error exactly. And\n * fewer than two misses returns without a workspace at all: one document\n * gains nothing from batch isolation it already has.\n */\n async prefetchLikeC4(paths: string[]): Promise<void> {\n const missing = [...new Set(paths.map(key))].filter((k) => !this.likec4.has(k));\n if (missing.length < 2) return;\n let docs: Map<string, LoadedDoc>;\n try {\n docs = await loadBatch(missing);\n } catch {\n return;\n }\n for (const [k, doc] of docs) {\n // Keyed by the same `key()` as loadLikeC4 — loadBatch resolves paths the\n // identical way, and `missing` was already resolved, so this holds by\n // construction; the has() guard keeps a concurrent load's promise\n // authoritative rather than double-counting the parse.\n if (this.likec4.has(k)) continue;\n this.counts.likec4Loads += 1;\n this.likec4.set(k, Promise.resolve(doc));\n }\n }\n\n /** The fleet map as the PROJECT it is — landscape plus use cases, minus the generated views file. */\n architecture(docsDir: DocsDir): Promise<LoadedDoc> {\n return this.models.architecture(docsDir);\n }\n\n /**\n * Which shape each of these models has, keyed by resolved path — one read of\n * each file's bytes per invocation. Ask over the FULL enumeration, never a\n * narrowed run's subset: shapes decide which directories are owed a\n * per-service `likec4.config.json`, and a partial map answers \"not\n * standalone\" for every service outside it.\n */\n modelShapes(paths: readonly string[]): Promise<Map<string, ModelShape>> {\n return this.models.shapes(paths);\n }\n\n /**\n * A service's C4 model, read the way its own SHAPE demands — the one entry\n * point for every reader of `model.likec4`, standalone or extending\n * (`c4/service-model/load.ts` states the four arms and why each exists).\n */\n serviceModel(docsDir: DocsDir, paths: ServicePaths): Promise<ServiceModel> {\n return this.models.model(docsDir, paths);\n }\n\n /**\n * Every service's model prepared in two workspaces whatever the fleet's mix:\n * the extending ones as one project each in one of them, the standalone ones\n * through the document batch above. An accelerator with `prefetchLikeC4`'s\n * failure story exactly — a batch that could not be created seeds nothing, so\n * findings never change because of a tmpdir, only the speed does.\n */\n async prefetchServiceModels(docsDir: DocsDir, paths: readonly ServicePaths[]): Promise<void> {\n await this.prefetchLikeC4(await this.models.prefetch(docsDir, paths));\n }\n}\n"]}
|
|
@@ -19,18 +19,11 @@ import { ACTOR_KINDS, EXTERNAL_TAG, landscapeEvidence, maturityGaps, serviceMatu
|
|
|
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
28
|
* What the map says about one edge END: does it stand for a person, and is it
|
|
36
29
|
* tagged `#external`? Walked up the ancestor chain because both facts are
|
|
@@ -46,7 +39,10 @@ function selfAndAncestors(id) {
|
|
|
46
39
|
function endFacts(byId, known, id) {
|
|
47
40
|
let actor = false;
|
|
48
41
|
let external = false;
|
|
49
|
-
|
|
42
|
+
// The id itself, then each dotted ancestor — the same walk `serviceResolver`
|
|
43
|
+
// resolves through, out of the one module that spells it
|
|
44
|
+
// (`core/kernel/ids/fqn/ancestors.ts`).
|
|
45
|
+
for (const candidate of ancestorIds(id)) {
|
|
50
46
|
const e = byId.get(candidate);
|
|
51
47
|
if (e === undefined)
|
|
52
48
|
continue;
|
|
@@ -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 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, 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 // 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 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"]}
|
|
@@ -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"]}
|