archctx 0.9.1 → 0.9.3
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/assets/catalog.yaml +2 -2
- package/bin/archctx.mjs +681 -123
- package/package.json +1 -1
- package/schemas/repo/architecture-node.schema.json +33 -1
- package/schemas/repo/architecture-relation.schema.json +1 -1
- package/schemas/runtime/capability-dependencies.schema.json +1 -1
- package/schemas/runtime/projection-manifest.schema.json +27 -12
- package/schemas/runtime/projection-target.schema.json +1 -1
package/bin/archctx.mjs
CHANGED
|
@@ -2377,8 +2377,9 @@ var init_failure = __esm(() => {
|
|
|
2377
2377
|
"model-missing": { code: "AC_MODEL_ADOPTION_REQUIRED", message: "Architecture debt requires an adopted model.", fields: ["checks"] },
|
|
2378
2378
|
"changed-path-invalid": { code: "AC_SCHEMA_INVALID", message: "Changed paths must be an array of repository-relative POSIX paths.", fields: ["checks"] },
|
|
2379
2379
|
"debt-report-invalid": { code: "AC_SCHEMA_INVALID", message: "Architecture debt evidence violates its contract.", fields: ["checks"] },
|
|
2380
|
-
"projection-manifest-upgrade-required": { code: "AC_PRECONDITION_FAILED", message: "The committed projection manifest requires supported re-baselining." },
|
|
2380
|
+
"projection-manifest-upgrade-required": { code: "AC_PRECONDITION_FAILED", message: "The committed projection manifest requires supported re-baselining. First restore the original model if missing; the manifest digest cannot recover it. With the reviewed model available, follow docs/runbooks/architecture-documentation-projections.md before running archctx docs apply --approved --profile <default|repo-harness/v1>." },
|
|
2381
2381
|
"projection-manifest-invalid": { code: "AC_PRECONDITION_FAILED", message: "The committed projection manifest violates its contract." },
|
|
2382
|
+
"architecture-model-ignored": { code: "AC_PRECONDITION_FAILED", action: "commit-architecture-model", message: "Git ignore rules exclude architecture model paths, so the model cannot be committed and a manifest digest cannot rebuild it. Remove or narrow the rule, commit the model, then retry adoption. The diagnostic command prints each deciding rule.", facts: { pathCount: "uint", paths: "optional:diagnosticPaths", commands: "optional:diagnosticCommands", omittedPathCount: "uint" } },
|
|
2382
2383
|
"projection-footprint-uncovered": { code: "AC_PRECONDITION_FAILED", message: "Declared source footprints lack a projection target." },
|
|
2383
2384
|
"index-missing": { code: "AC_CODE_FACTS_UNAVAILABLE", message: "Required code facts have no available index. Run codegraph init." },
|
|
2384
2385
|
"code-facts-not-ready": { code: "AC_CODE_FACTS_NOT_READY", message: "Repository code facts are not ready. Run archctx doctor --json and follow the reported action.", facts: { issues: "codeFactsIssues" } },
|
|
@@ -2816,6 +2817,89 @@ var init_failure = __esm(() => {
|
|
|
2816
2817
|
};
|
|
2817
2818
|
});
|
|
2818
2819
|
|
|
2820
|
+
// packages/contracts/src/runtime-topology.ts
|
|
2821
|
+
function isRuntimeTopologyNodeKind(kind) {
|
|
2822
|
+
return typeof kind === "string" && RUNTIME_TOPOLOGY_NODE_KINDS.includes(kind);
|
|
2823
|
+
}
|
|
2824
|
+
function runtimeTopologyModelIssues(model) {
|
|
2825
|
+
const issues = [];
|
|
2826
|
+
const nodesById = new Map;
|
|
2827
|
+
for (const node of model.nodes)
|
|
2828
|
+
if (typeof node.id === "string")
|
|
2829
|
+
nodesById.set(node.id, node);
|
|
2830
|
+
const namespacesByProcess = new Map;
|
|
2831
|
+
const mountersByMount = new Map;
|
|
2832
|
+
for (const relation of model.relations) {
|
|
2833
|
+
if (typeof relation.id !== "string" || typeof relation.kind !== "string" || !Object.hasOwn(RUNTIME_TOPOLOGY_RELATION_ENDPOINT_KINDS, relation.kind))
|
|
2834
|
+
continue;
|
|
2835
|
+
const rule = RUNTIME_TOPOLOGY_RELATION_ENDPOINT_KINDS[relation.kind];
|
|
2836
|
+
let endpointsValid = true;
|
|
2837
|
+
for (const field of ["source", "target"]) {
|
|
2838
|
+
const endpoint = relation[field];
|
|
2839
|
+
const node = typeof endpoint === "string" ? nodesById.get(endpoint) : undefined;
|
|
2840
|
+
if (!node) {
|
|
2841
|
+
issues.push({ entityId: relation.id, field, invariant: "topology-relation-endpoint-missing" });
|
|
2842
|
+
endpointsValid = false;
|
|
2843
|
+
} else if (!rule[field].includes(node.kind)) {
|
|
2844
|
+
issues.push({ entityId: relation.id, field, invariant: field === "source" ? "topology-relation-source-kind-invalid" : "topology-relation-target-kind-invalid" });
|
|
2845
|
+
endpointsValid = false;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
if (!endpointsValid)
|
|
2849
|
+
continue;
|
|
2850
|
+
const source = relation.source;
|
|
2851
|
+
const target = relation.target;
|
|
2852
|
+
if (relation.kind === "runs-in")
|
|
2853
|
+
namespacesByProcess.set(source, [...namespacesByProcess.get(source) ?? [], target]);
|
|
2854
|
+
if (relation.kind === "mounts")
|
|
2855
|
+
mountersByMount.set(target, [...mountersByMount.get(target) ?? [], source]);
|
|
2856
|
+
}
|
|
2857
|
+
for (const [processId, namespaces] of namespacesByProcess) {
|
|
2858
|
+
if (new Set(namespaces).size > 1)
|
|
2859
|
+
issues.push({ entityId: processId, field: "runs-in", invariant: "topology-process-namespace-ambiguous" });
|
|
2860
|
+
}
|
|
2861
|
+
for (const [mountId, mounters] of mountersByMount) {
|
|
2862
|
+
if (new Set(mounters).size > 1)
|
|
2863
|
+
issues.push({ entityId: mountId, field: "mounts", invariant: "topology-mount-mounter-ambiguous" });
|
|
2864
|
+
}
|
|
2865
|
+
for (const [id, node] of nodesById) {
|
|
2866
|
+
if (node.kind !== "namespace" || node.parent === undefined)
|
|
2867
|
+
continue;
|
|
2868
|
+
const parent = typeof node.parent === "string" ? nodesById.get(node.parent) : undefined;
|
|
2869
|
+
if (!parent) {
|
|
2870
|
+
issues.push({ entityId: id, field: "parent", invariant: "topology-namespace-parent-missing" });
|
|
2871
|
+
continue;
|
|
2872
|
+
}
|
|
2873
|
+
if (parent.kind !== "namespace") {
|
|
2874
|
+
issues.push({ entityId: id, field: "parent", invariant: "topology-namespace-parent-kind-invalid" });
|
|
2875
|
+
continue;
|
|
2876
|
+
}
|
|
2877
|
+
const seen = new Set([id]);
|
|
2878
|
+
let cursor = parent;
|
|
2879
|
+
while (cursor && cursor.kind === "namespace" && typeof cursor.id === "string") {
|
|
2880
|
+
if (seen.has(cursor.id)) {
|
|
2881
|
+
issues.push({ entityId: id, field: "parent", invariant: "topology-namespace-parent-cycle" });
|
|
2882
|
+
break;
|
|
2883
|
+
}
|
|
2884
|
+
seen.add(cursor.id);
|
|
2885
|
+
cursor = typeof cursor.parent === "string" ? nodesById.get(cursor.parent) : undefined;
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
return issues.sort((left, right) => compare(left.entityId, right.entityId) || compare(left.field, right.field) || compare(left.invariant, right.invariant));
|
|
2889
|
+
}
|
|
2890
|
+
function compare(left, right) {
|
|
2891
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
2892
|
+
}
|
|
2893
|
+
var RUNTIME_TOPOLOGY_NODE_KINDS, RUNTIME_TOPOLOGY_RELATION_ENDPOINT_KINDS;
|
|
2894
|
+
var init_runtime_topology = __esm(() => {
|
|
2895
|
+
RUNTIME_TOPOLOGY_NODE_KINDS = ["process", "namespace", "mount"];
|
|
2896
|
+
RUNTIME_TOPOLOGY_RELATION_ENDPOINT_KINDS = {
|
|
2897
|
+
spawns: { source: ["process"], target: ["process"] },
|
|
2898
|
+
"runs-in": { source: ["process"], target: ["namespace"] },
|
|
2899
|
+
mounts: { source: ["process", "namespace"], target: ["mount"] }
|
|
2900
|
+
};
|
|
2901
|
+
});
|
|
2902
|
+
|
|
2819
2903
|
// packages/contracts/src/validator.ts
|
|
2820
2904
|
function jsonSchemaResolver(documents) {
|
|
2821
2905
|
const byId = new Map;
|
|
@@ -3159,6 +3243,10 @@ function nativeContextModelIssues(model, schemas, recordPaths) {
|
|
|
3159
3243
|
if (relation[field] !== undefined && !nodes.has(relation[field]))
|
|
3160
3244
|
add(relation, field, "relation-node-missing");
|
|
3161
3245
|
}
|
|
3246
|
+
const relations = new Map(model.relations.map((relation) => [relation.id, relation]));
|
|
3247
|
+
for (const issue of runtimeTopologyModelIssues(model)) {
|
|
3248
|
+
add(relations.get(issue.entityId) ?? nodes.get(issue.entityId) ?? issue.entityId, issue.field, issue.invariant);
|
|
3249
|
+
}
|
|
3162
3250
|
for (const flow of model.flows) {
|
|
3163
3251
|
if (nodes.get(flow.capabilityId)?.kind !== "capability")
|
|
3164
3252
|
add(flow, "capabilityId", "flow-capability-missing");
|
|
@@ -3201,6 +3289,7 @@ var NATIVE_CONTEXT_SCHEMA_VERSION = "archcontext.native-context-export/v1", NATI
|
|
|
3201
3289
|
var init_native_context = __esm(() => {
|
|
3202
3290
|
init_failure();
|
|
3203
3291
|
init_schema();
|
|
3292
|
+
init_runtime_topology();
|
|
3204
3293
|
init_validator();
|
|
3205
3294
|
NATIVE_CONTEXT_LIMITS = Object.freeze({
|
|
3206
3295
|
inputFiles: 1e4,
|
|
@@ -3373,6 +3462,9 @@ var PROJECTION_PUBLISH_RUN_SCHEMA_VERSION = "archcontext.projection-publish-run/
|
|
|
3373
3462
|
var init_projection_writer = () => {};
|
|
3374
3463
|
|
|
3375
3464
|
// packages/contracts/src/projection-manifest.ts
|
|
3465
|
+
function isRuntimeTopologyTargetType(type) {
|
|
3466
|
+
return typeof type === "string" && RUNTIME_TOPOLOGY_TARGET_TYPES.includes(type);
|
|
3467
|
+
}
|
|
3376
3468
|
function architectureDocsProjectionManifestIssues(value) {
|
|
3377
3469
|
const issues = [];
|
|
3378
3470
|
const manifest = record3(value);
|
|
@@ -3408,6 +3500,7 @@ function architectureDocsProjectionManifestIssues(value) {
|
|
|
3408
3500
|
}
|
|
3409
3501
|
const paths = new Set;
|
|
3410
3502
|
const stampOwners = new Map;
|
|
3503
|
+
let topologyStamps;
|
|
3411
3504
|
for (const [targetId, row] of Object.entries(targets)) {
|
|
3412
3505
|
const prefix = `manifest.targets[${JSON.stringify(targetId)}]`;
|
|
3413
3506
|
if (!isNonEmptyString(targetId))
|
|
@@ -3419,7 +3512,23 @@ function architectureDocsProjectionManifestIssues(value) {
|
|
|
3419
3512
|
issues.push(`${prefix}.path is recorded by another target`);
|
|
3420
3513
|
paths.add(path);
|
|
3421
3514
|
}
|
|
3422
|
-
|
|
3515
|
+
const stamps = record3(record3(row)?.sourceStamps) ?? {};
|
|
3516
|
+
if (isRuntimeTopologyTargetType(record3(row)?.type)) {
|
|
3517
|
+
const serialized = JSON.stringify(Object.keys(stamps).sort(compareStrings).map((nodeId) => [nodeId, stamps[nodeId]]));
|
|
3518
|
+
if (topologyStamps === undefined)
|
|
3519
|
+
topologyStamps = { targetId, stamps: serialized };
|
|
3520
|
+
else if (topologyStamps.stamps !== serialized)
|
|
3521
|
+
issues.push(`${prefix}.sourceStamps must equal the sourceStamps of ${topologyStamps.targetId}`);
|
|
3522
|
+
for (const nodeId of Object.keys(stamps)) {
|
|
3523
|
+
const owner = stampOwners.get(nodeId);
|
|
3524
|
+
if (owner !== undefined && !isRuntimeTopologyTargetType(record3(targets[owner])?.type))
|
|
3525
|
+
issues.push(`${prefix}.sourceStamps.${nodeId} is already stamped on ${owner}`);
|
|
3526
|
+
else if (owner === undefined)
|
|
3527
|
+
stampOwners.set(nodeId, targetId);
|
|
3528
|
+
}
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
for (const nodeId of Object.keys(stamps)) {
|
|
3423
3532
|
const owner = stampOwners.get(nodeId);
|
|
3424
3533
|
if (owner !== undefined)
|
|
3425
3534
|
issues.push(`${prefix}.sourceStamps.${nodeId} is already stamped on ${owner}`);
|
|
@@ -3674,9 +3783,11 @@ function manifestTargetIssues(value, prefix) {
|
|
|
3674
3783
|
issues.push(`${prefix}.format is unsupported`);
|
|
3675
3784
|
if (!isDigest(target.outputDigest))
|
|
3676
3785
|
issues.push(`${prefix}.outputDigest must be a SHA-256 digest`);
|
|
3677
|
-
if (
|
|
3678
|
-
issues.push(`${prefix}.sourceStamps
|
|
3786
|
+
if (target.sourceStamps !== undefined && target.type !== "entity-summary" && !isRuntimeTopologyTargetType(target.type)) {
|
|
3787
|
+
issues.push(`${prefix}.sourceStamps are only allowed on entity-summary and runtime topology targets`);
|
|
3679
3788
|
}
|
|
3789
|
+
if (target.scale !== undefined && target.type !== "entity-summary")
|
|
3790
|
+
issues.push(`${prefix}.scale is only allowed on entity-summary targets`);
|
|
3680
3791
|
if (target.sourceStamps !== undefined) {
|
|
3681
3792
|
const stamps = record3(target.sourceStamps);
|
|
3682
3793
|
if (!stamps || Object.keys(stamps).length === 0)
|
|
@@ -3765,7 +3876,7 @@ function architectureDocsProjectionCompatibilityIssues(value, engine = {
|
|
|
3765
3876
|
}
|
|
3766
3877
|
return issues;
|
|
3767
3878
|
}
|
|
3768
|
-
var ARCHITECTURE_DOCS_PROJECTION_MANIFEST_SCHEMA_VERSION = "archcontext.architecture-docs-projection-manifest/v3", SUPERSEDED_ARCHITECTURE_DOCS_PROJECTION_MANIFEST_SCHEMA_VERSIONS, PROJECTION_MANIFEST_UPGRADE_REQUIRED = "projection-manifest-upgrade-required", PROJECTION_MANIFEST_INVALID = "projection-manifest-invalid", PROJECTION_FOOTPRINT_UNCOVERED = "projection-footprint-uncovered", ARCHITECTURE_DOCS_PROJECTION_PROVENANCE_SCHEMA_VERSION = "archcontext.architecture-docs-projection-provenance/v3", ARCHITECTURE_DOCS_LAYOUT_VERSION_V1 = "archcontext.docs-layout/v1", ARCHITECTURE_SEMANTIC_STATE_SCHEMA_VERSION = "archcontext.architecture-semantic-state/v1", ARCHITECTURE_DOCS_PROJECTION_PROFILES, SHA256, TARGET_TYPES, SCOPE_KINDS, OWNERSHIPS, FORMATS;
|
|
3879
|
+
var ARCHITECTURE_DOCS_PROJECTION_MANIFEST_SCHEMA_VERSION = "archcontext.architecture-docs-projection-manifest/v3", SUPERSEDED_ARCHITECTURE_DOCS_PROJECTION_MANIFEST_SCHEMA_VERSIONS, PROJECTION_MANIFEST_UPGRADE_REQUIRED = "projection-manifest-upgrade-required", PROJECTION_MANIFEST_INVALID = "projection-manifest-invalid", PROJECTION_FOOTPRINT_UNCOVERED = "projection-footprint-uncovered", ARCHITECTURE_DOCS_PROJECTION_PROVENANCE_SCHEMA_VERSION = "archcontext.architecture-docs-projection-provenance/v3", ARCHITECTURE_DOCS_LAYOUT_VERSION_V1 = "archcontext.docs-layout/v1", ARCHITECTURE_SEMANTIC_STATE_SCHEMA_VERSION = "archcontext.architecture-semantic-state/v1", ARCHITECTURE_DOCS_PROJECTION_PROFILES, SHA256, TARGET_TYPES, RUNTIME_TOPOLOGY_TARGET_TYPES, SCOPE_KINDS, OWNERSHIPS, FORMATS;
|
|
3769
3880
|
var init_projection_manifest = __esm(() => {
|
|
3770
3881
|
init_projection();
|
|
3771
3882
|
init_schema();
|
|
@@ -3784,8 +3895,11 @@ var init_projection_manifest = __esm(() => {
|
|
|
3784
3895
|
"diagram-mermaid": true,
|
|
3785
3896
|
"diagram-structurizr": true,
|
|
3786
3897
|
"diagram-likec4": true,
|
|
3898
|
+
"diagram-runtime-topology": true,
|
|
3899
|
+
"runtime-topology-summary": true,
|
|
3787
3900
|
"agent-context": true
|
|
3788
3901
|
};
|
|
3902
|
+
RUNTIME_TOPOLOGY_TARGET_TYPES = ["diagram-runtime-topology", "runtime-topology-summary"];
|
|
3789
3903
|
SCOPE_KINDS = { repository: true, entity: true, relation: true, decision: true, diagram: true, changelog: true };
|
|
3790
3904
|
OWNERSHIPS = { generated: true, mixed: true };
|
|
3791
3905
|
FORMATS = { markdown: true, mermaid: true, "structurizr-json": true, likec4: true };
|
|
@@ -3876,7 +3990,7 @@ function compareVersionTriples(left, right) {
|
|
|
3876
3990
|
}
|
|
3877
3991
|
return 0;
|
|
3878
3992
|
}
|
|
3879
|
-
var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.9.
|
|
3993
|
+
var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.9.3", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.3", ARCHCONTEXT_NODE_RANGE = ">=22.22 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v2", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
|
|
3880
3994
|
|
|
3881
3995
|
// packages/contracts/src/refactor.ts
|
|
3882
3996
|
function refactorNamespaceIssues(value, label = "product.id") {
|
|
@@ -4981,6 +5095,7 @@ var init_src = __esm(() => {
|
|
|
4981
5095
|
init_projection_writer();
|
|
4982
5096
|
init_projection_manifest();
|
|
4983
5097
|
init_refactor();
|
|
5098
|
+
init_runtime_topology();
|
|
4984
5099
|
init_schema();
|
|
4985
5100
|
init_failure();
|
|
4986
5101
|
init_validator();
|
|
@@ -17694,6 +17809,16 @@ var FIXED_TARGETS = [
|
|
|
17694
17809
|
layoutTarget("projection_target.diagram.structurizr", "diagram-structurizr", { kind: "diagram", id: "architecture" }, "docs/architecture/diagrams/architecture.structurizr.json", "generated", "structurizr-json"),
|
|
17695
17810
|
layoutTarget("projection_target.diagram.likec4", "diagram-likec4", { kind: "diagram", id: "architecture" }, "docs/architecture/diagrams/architecture.likec4", "generated", "likec4")
|
|
17696
17811
|
];
|
|
17812
|
+
var RUNTIME_TOPOLOGY_DIAGRAM_PATH = "docs/architecture/diagrams/runtime-topology.mmd";
|
|
17813
|
+
var RUNTIME_TOPOLOGY_SUMMARY_PATH = "docs/architecture/runtime-topology.md";
|
|
17814
|
+
var RUNTIME_TOPOLOGY_SCOPE_ID = "runtime-topology";
|
|
17815
|
+
var RUNTIME_TOPOLOGY_TARGETS = [
|
|
17816
|
+
layoutTarget("projection_target.diagram.runtime-topology", "diagram-runtime-topology", { kind: "diagram", id: RUNTIME_TOPOLOGY_SCOPE_ID }, RUNTIME_TOPOLOGY_DIAGRAM_PATH, "generated", "mermaid"),
|
|
17817
|
+
layoutTarget("projection_target.runtime-topology.summary", "runtime-topology-summary", { kind: "diagram", id: RUNTIME_TOPOLOGY_SCOPE_ID }, RUNTIME_TOPOLOGY_SUMMARY_PATH, "generated", "markdown")
|
|
17818
|
+
];
|
|
17819
|
+
function hasRuntimeTopologyNodes(nodes) {
|
|
17820
|
+
return nodes.some((node) => isRuntimeTopologyNodeKind(node.kind));
|
|
17821
|
+
}
|
|
17697
17822
|
function parseRepoHarnessNodeProfile(node) {
|
|
17698
17823
|
const match = /^capability\.([a-z0-9]+(?:-[a-z0-9]+)*)\.([a-z0-9]+(?:-[a-z0-9]+)*)$/.exec(node.id);
|
|
17699
17824
|
if (!match || node.kind !== "capability") {
|
|
@@ -17721,7 +17846,7 @@ function parseRepoHarnessNodeProfile(node) {
|
|
|
17721
17846
|
};
|
|
17722
17847
|
}
|
|
17723
17848
|
function entitySummaryTargetNodes(nodes, profile) {
|
|
17724
|
-
return profile === REPO_HARNESS_PROJECTION_PROFILE ? nodes.filter((node) => node.kind === "capability") :
|
|
17849
|
+
return profile === REPO_HARNESS_PROJECTION_PROFILE ? nodes.filter((node) => node.kind === "capability") : nodes.filter((node) => !isRuntimeTopologyNodeKind(node.kind));
|
|
17725
17850
|
}
|
|
17726
17851
|
function resolveArchitectureDocumentationLayout(input) {
|
|
17727
17852
|
const profile = input.profile ?? "default";
|
|
@@ -17731,7 +17856,8 @@ function resolveArchitectureDocumentationLayout(input) {
|
|
|
17731
17856
|
return layoutTarget(`projection_target.entity.${stableId(node.id)}`, "entity-summary", { kind: "entity", id: node.id, entityKind: node.kind }, path, "mixed", "markdown");
|
|
17732
17857
|
});
|
|
17733
17858
|
const relationTargets = (profile === REPO_HARNESS_PROJECTION_PROFILE ? [] : input.relations).map((relation) => layoutTarget(`projection_target.relation.${stableId(relation.id)}`, "relation-summary", { kind: "relation", id: relation.id }, `docs/architecture/relations/${pathSegment(relation.id)}.md`, "mixed", "markdown"));
|
|
17734
|
-
const
|
|
17859
|
+
const topologyTargets = hasRuntimeTopologyNodes(input.nodes) ? RUNTIME_TOPOLOGY_TARGETS : [];
|
|
17860
|
+
const targets = [FIXED_TARGETS[0], ...entityTargets, ...relationTargets, ...FIXED_TARGETS.slice(1), ...topologyTargets].map((target) => ({ ...target, path: requiredPath(target.path, target.targetId) }));
|
|
17735
17861
|
assertUniqueTargets(targets);
|
|
17736
17862
|
return {
|
|
17737
17863
|
profile,
|
|
@@ -19369,6 +19495,7 @@ function renderArchitectureDocumentationProjection(input) {
|
|
|
19369
19495
|
const rendered = targetDrafts.map((draft) => {
|
|
19370
19496
|
const existing = existingByPath.get(draft.path);
|
|
19371
19497
|
const nodeId = draft.type === "entity-summary" ? draft.scope.id : undefined;
|
|
19498
|
+
const topology = isRuntimeTopologyTargetType(draft.type);
|
|
19372
19499
|
const generatedBody = renderTargetGeneratedBody(draft, model, {
|
|
19373
19500
|
scaleSignalsByNodeId,
|
|
19374
19501
|
scaleByNodeId,
|
|
@@ -19392,6 +19519,10 @@ function renderArchitectureDocumentationProjection(input) {
|
|
|
19392
19519
|
if (scale === undefined)
|
|
19393
19520
|
throw new Error(`architecture-docs-projection-scale-signal-missing: ${nodeId}`);
|
|
19394
19521
|
}
|
|
19522
|
+
} else if (topology) {
|
|
19523
|
+
const owned = Object.keys(sourceStamps).filter((stamped) => footprintOwners.get(stamped) === RUNTIME_TOPOLOGY_SCOPE_ID);
|
|
19524
|
+
if (owned.length > 0)
|
|
19525
|
+
targetStamps = Object.fromEntries(owned.map((stamped) => [stamped, sourceStamps[stamped]]));
|
|
19395
19526
|
}
|
|
19396
19527
|
const generatedBodyDigest = digestJson({ targetId: draft.targetId, body: generatedBody });
|
|
19397
19528
|
const target = projectionTarget({
|
|
@@ -19757,6 +19888,10 @@ function architectureProjectionFootprintOwners(model, profile = "default") {
|
|
|
19757
19888
|
for (const node of [...model.nodes].sort((left, right) => compareStrings3(left.id, right.id))) {
|
|
19758
19889
|
if ((nativeNodeSource(node)?.include ?? []).length === 0)
|
|
19759
19890
|
continue;
|
|
19891
|
+
if (isRuntimeTopologyNodeKind(node.kind)) {
|
|
19892
|
+
owners.set(node.id, RUNTIME_TOPOLOGY_SCOPE_ID);
|
|
19893
|
+
continue;
|
|
19894
|
+
}
|
|
19760
19895
|
const seen = new Set;
|
|
19761
19896
|
let current = node;
|
|
19762
19897
|
while (current && !targetNodeIds.has(current.id) && !seen.has(current.id)) {
|
|
@@ -20103,12 +20238,52 @@ function normalizeNativeModel(model) {
|
|
|
20103
20238
|
flows: [...model.flows ?? []].sort((a, b) => a.id.localeCompare(b.id))
|
|
20104
20239
|
};
|
|
20105
20240
|
}
|
|
20241
|
+
var ARCHITECTURE_NODE_LAYER_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
20242
|
+
function nativeNodeLayer(node) {
|
|
20243
|
+
const layer = node.layer;
|
|
20244
|
+
if (layer === undefined)
|
|
20245
|
+
return;
|
|
20246
|
+
if (typeof layer !== "string" || !ARCHITECTURE_NODE_LAYER_PATTERN.test(layer)) {
|
|
20247
|
+
throw new Error(`architecture-node-layer-invalid: ${node.id} (${JSON.stringify(layer)})`);
|
|
20248
|
+
}
|
|
20249
|
+
return layer;
|
|
20250
|
+
}
|
|
20251
|
+
function architectureLayerGroups(model) {
|
|
20252
|
+
const nodesByLayer = new Map;
|
|
20253
|
+
const ungroupedNodes = [];
|
|
20254
|
+
const layerByNodeId = new Map;
|
|
20255
|
+
for (const node of [...model.nodes].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
20256
|
+
const layer = nativeNodeLayer(node);
|
|
20257
|
+
if (layer === undefined) {
|
|
20258
|
+
ungroupedNodes.push(node);
|
|
20259
|
+
continue;
|
|
20260
|
+
}
|
|
20261
|
+
const members = nodesByLayer.get(layer);
|
|
20262
|
+
if (members)
|
|
20263
|
+
members.push(node);
|
|
20264
|
+
else
|
|
20265
|
+
nodesByLayer.set(layer, [node]);
|
|
20266
|
+
layerByNodeId.set(node.id, layer);
|
|
20267
|
+
}
|
|
20268
|
+
const layers = [...nodesByLayer.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([layer, nodes]) => ({ layer, nodes }));
|
|
20269
|
+
return { layers, ungroupedNodes, layerByNodeId };
|
|
20270
|
+
}
|
|
20271
|
+
function mermaidLayerId(layer) {
|
|
20272
|
+
return `layer__${layer.replace(/-/g, "_")}`;
|
|
20273
|
+
}
|
|
20106
20274
|
function exportMermaidModel(model) {
|
|
20107
20275
|
const normalized = normalizeNativeModel(model);
|
|
20276
|
+
const grouping = architectureLayerGroups(normalized);
|
|
20277
|
+
const nodeLine = (indent, node) => `${indent}${mermaidId(node.id)}["${escapeMermaid(node.name)}"]`;
|
|
20108
20278
|
const lines = ["%% Generated by ArchContext. Native model remains source of truth.", "flowchart LR"];
|
|
20109
|
-
for (const
|
|
20110
|
-
lines.push(` ${
|
|
20279
|
+
for (const group of grouping.layers) {
|
|
20280
|
+
lines.push(` subgraph ${mermaidLayerId(group.layer)}["${escapeMermaid(group.layer)}"]`);
|
|
20281
|
+
for (const node of group.nodes)
|
|
20282
|
+
lines.push(nodeLine(" ", node));
|
|
20283
|
+
lines.push(" end");
|
|
20111
20284
|
}
|
|
20285
|
+
for (const node of grouping.ungroupedNodes)
|
|
20286
|
+
lines.push(nodeLine(" ", node));
|
|
20112
20287
|
for (const relation of normalized.relations) {
|
|
20113
20288
|
lines.push(` ${mermaidId(relation.source)} -->|"${escapeMermaid(relation.kind)}"| ${mermaidId(relation.target)}`);
|
|
20114
20289
|
}
|
|
@@ -20125,22 +20300,15 @@ function exportDocumentationLikeC4Model(model) {
|
|
|
20125
20300
|
const normalized = normalizeNativeModel(model);
|
|
20126
20301
|
const lines = [
|
|
20127
20302
|
"// Generated by ArchContext. Native model remains source of truth.",
|
|
20128
|
-
|
|
20129
|
-
" element capability",
|
|
20130
|
-
" element module",
|
|
20131
|
-
" element component",
|
|
20132
|
-
" element interface",
|
|
20133
|
-
" element datastore",
|
|
20134
|
-
" element external_system",
|
|
20135
|
-
"}",
|
|
20303
|
+
...likeC4SpecificationLines(normalized.nodes),
|
|
20136
20304
|
"",
|
|
20137
20305
|
"model {"
|
|
20138
20306
|
];
|
|
20139
20307
|
for (const node of normalized.nodes)
|
|
20140
|
-
lines.push(` ${mermaidId(node.id)} = ${
|
|
20308
|
+
lines.push(` ${mermaidId(node.id)} = ${likeC4ElementKind(node.kind)} "${escapeDsl(node.name)}"`);
|
|
20141
20309
|
for (const relation of normalized.relations)
|
|
20142
20310
|
lines.push(` ${mermaidId(relation.source)} -> ${mermaidId(relation.target)} "${escapeDsl(relation.kind)}"`);
|
|
20143
|
-
lines.push("}", "",
|
|
20311
|
+
lines.push("}", "", ...likeC4IndexViewLines(architectureLayerGroups(normalized), mermaidId));
|
|
20144
20312
|
const content = `${lines.join(`
|
|
20145
20313
|
`)}
|
|
20146
20314
|
`;
|
|
@@ -20150,18 +20318,73 @@ function exportDocumentationLikeC4Model(model) {
|
|
|
20150
20318
|
files: [{ path: "docs/architecture/diagrams/architecture.likec4", content }]
|
|
20151
20319
|
};
|
|
20152
20320
|
}
|
|
20321
|
+
function likeC4SpecificationLines(nodes) {
|
|
20322
|
+
const topologyKinds = RUNTIME_TOPOLOGY_NODE_KINDS.filter((kind) => nodes.some((node) => node.kind === kind));
|
|
20323
|
+
return [
|
|
20324
|
+
"specification {",
|
|
20325
|
+
" element capability",
|
|
20326
|
+
" element module",
|
|
20327
|
+
" element component",
|
|
20328
|
+
" element interface",
|
|
20329
|
+
" element datastore",
|
|
20330
|
+
" element external_system",
|
|
20331
|
+
...topologyKinds.map((kind) => ` element ${kind}`),
|
|
20332
|
+
"}"
|
|
20333
|
+
];
|
|
20334
|
+
}
|
|
20335
|
+
function likeC4ElementKind(kind) {
|
|
20336
|
+
if (kind === "external-system")
|
|
20337
|
+
return "external_system";
|
|
20338
|
+
if (kind === "datastore")
|
|
20339
|
+
return "datastore";
|
|
20340
|
+
if (["capability", "module", "component", "interface"].includes(kind))
|
|
20341
|
+
return kind;
|
|
20342
|
+
if (isRuntimeTopologyNodeKind(kind))
|
|
20343
|
+
return kind;
|
|
20344
|
+
return "component";
|
|
20345
|
+
}
|
|
20346
|
+
function architectureStructurizrElementType(kind) {
|
|
20347
|
+
if (kind === "capability")
|
|
20348
|
+
return "Software System";
|
|
20349
|
+
if (kind === "module")
|
|
20350
|
+
return "Container";
|
|
20351
|
+
if (kind === "datastore")
|
|
20352
|
+
return "Database";
|
|
20353
|
+
if (kind === "external-system")
|
|
20354
|
+
return "External System";
|
|
20355
|
+
if (kind === "namespace")
|
|
20356
|
+
return "Deployment Node";
|
|
20357
|
+
if (kind === "process")
|
|
20358
|
+
return "Container Instance";
|
|
20359
|
+
if (kind === "mount")
|
|
20360
|
+
return "Infrastructure Node";
|
|
20361
|
+
return "Component";
|
|
20362
|
+
}
|
|
20363
|
+
function likeC4IndexViewLines(grouping, elementId) {
|
|
20364
|
+
const groupLines = grouping.layers.flatMap((group) => [
|
|
20365
|
+
` group "${escapeDsl(group.layer)}" {`,
|
|
20366
|
+
...group.nodes.map((node) => ` include ${elementId(node.id)}`),
|
|
20367
|
+
" }"
|
|
20368
|
+
]);
|
|
20369
|
+
return ["views {", " view index {", ...groupLines, " include *", " autoLayout TopBottom", " }", "}"];
|
|
20370
|
+
}
|
|
20153
20371
|
function exportDocumentationStructurizrWorkspace(model) {
|
|
20154
20372
|
const normalized = normalizeNativeModel(model);
|
|
20373
|
+
const { layerByNodeId } = architectureLayerGroups(normalized);
|
|
20155
20374
|
const workspace = {
|
|
20156
20375
|
schemaVersion: "archcontext.structurizr-export/v1",
|
|
20157
20376
|
name: "ArchContext Architecture",
|
|
20158
20377
|
model: {
|
|
20159
|
-
elements: normalized.nodes.map((node) =>
|
|
20160
|
-
|
|
20161
|
-
|
|
20162
|
-
|
|
20163
|
-
|
|
20164
|
-
|
|
20378
|
+
elements: normalized.nodes.map((node) => {
|
|
20379
|
+
const layer = layerByNodeId.get(node.id);
|
|
20380
|
+
return {
|
|
20381
|
+
id: node.id,
|
|
20382
|
+
name: node.name,
|
|
20383
|
+
type: architectureStructurizrElementType(node.kind),
|
|
20384
|
+
tags: [node.kind],
|
|
20385
|
+
...layer === undefined ? {} : { group: layer }
|
|
20386
|
+
};
|
|
20387
|
+
}),
|
|
20165
20388
|
relationships: normalized.relations.map((relation) => ({
|
|
20166
20389
|
id: relation.id,
|
|
20167
20390
|
sourceId: relation.source,
|
|
@@ -20184,6 +20407,214 @@ function exportDocumentationStructurizrWorkspace(model) {
|
|
|
20184
20407
|
files: [{ path: "docs/architecture/diagrams/architecture.structurizr.json", content }]
|
|
20185
20408
|
};
|
|
20186
20409
|
}
|
|
20410
|
+
var RUNTIME_TOPOLOGY_NAMESPACE_TYPES = ["host", "container", "pod", "kubernetes", "pid", "mount", "net", "user"];
|
|
20411
|
+
var RUNTIME_TOPOLOGY_MOUNT_TARGET_PATTERN = /^\/([^/\u0000]+(\/[^/\u0000]+)*)?$/;
|
|
20412
|
+
function runtimeTopologyView(model) {
|
|
20413
|
+
const issues = runtimeTopologyModelIssues(model);
|
|
20414
|
+
if (issues.length > 0) {
|
|
20415
|
+
throw new Error(`runtime-topology-model-invalid: ${issues.map((issue) => `${issue.entityId} ${issue.field} ${issue.invariant}`).join("; ")}`);
|
|
20416
|
+
}
|
|
20417
|
+
const byId = (left, right) => compareStrings3(left.id, right.id);
|
|
20418
|
+
const nodes = [...model.nodes].sort(byId);
|
|
20419
|
+
const relations = [...model.relations].sort(byId);
|
|
20420
|
+
const nodesById = new Map(nodes.map((node) => [node.id, node]));
|
|
20421
|
+
const namespaces = nodes.filter((node) => node.kind === "namespace").map((node) => ({
|
|
20422
|
+
node,
|
|
20423
|
+
type: runtimeTopologyNamespaceType(node),
|
|
20424
|
+
...typeof node.parent === "string" ? { parentId: node.parent } : {},
|
|
20425
|
+
namespaceIds: [],
|
|
20426
|
+
processIds: []
|
|
20427
|
+
}));
|
|
20428
|
+
const namespaceById = new Map(namespaces.map((namespace) => [namespace.node.id, namespace]));
|
|
20429
|
+
for (const namespace of namespaces) {
|
|
20430
|
+
if (namespace.parentId !== undefined)
|
|
20431
|
+
namespaceById.get(namespace.parentId).namespaceIds.push(namespace.node.id);
|
|
20432
|
+
}
|
|
20433
|
+
const namespaceByProcessId = new Map(relations.filter((relation) => relation.kind === "runs-in").map((relation) => [relation.source, relation.target]));
|
|
20434
|
+
const processes = nodes.filter((node) => node.kind === "process").map((node) => {
|
|
20435
|
+
const namespaceId = namespaceByProcessId.get(node.id);
|
|
20436
|
+
return namespaceId === undefined ? { node } : { node, namespaceId };
|
|
20437
|
+
});
|
|
20438
|
+
for (const topologyProcess of processes) {
|
|
20439
|
+
if (topologyProcess.namespaceId !== undefined)
|
|
20440
|
+
namespaceById.get(topologyProcess.namespaceId).processIds.push(topologyProcess.node.id);
|
|
20441
|
+
}
|
|
20442
|
+
const mounts = nodes.filter((node) => node.kind === "mount").map(runtimeTopologyMount);
|
|
20443
|
+
const mounterByMountId = new Map(relations.filter((relation) => relation.kind === "mounts").map((relation) => [relation.target, relation.source]));
|
|
20444
|
+
const stacksByMounterId = new Map;
|
|
20445
|
+
for (const mount of mounts) {
|
|
20446
|
+
const mounterId = mounterByMountId.get(mount.node.id);
|
|
20447
|
+
if (mounterId === undefined)
|
|
20448
|
+
continue;
|
|
20449
|
+
stacksByMounterId.set(mounterId, [...stacksByMounterId.get(mounterId) ?? [], mount]);
|
|
20450
|
+
}
|
|
20451
|
+
const mountStacks = [...stacksByMounterId.entries()].sort(([left], [right]) => compareStrings3(left, right)).map(([mounterId, stack]) => ({
|
|
20452
|
+
mounterId,
|
|
20453
|
+
mounts: stack.sort((left, right) => left.order - right.order || compareStrings3(left.node.id, right.node.id))
|
|
20454
|
+
}));
|
|
20455
|
+
const deployments = relations.filter((relation) => relation.kind === "deployed-on" && isRuntimeTopologyNodeKind(nodesById.get(relation.target)?.kind));
|
|
20456
|
+
const linkedNodeIds = new Set;
|
|
20457
|
+
for (const relation of deployments) {
|
|
20458
|
+
const source = nodesById.get(relation.source);
|
|
20459
|
+
if (!source)
|
|
20460
|
+
throw new Error(`runtime-topology-relation-endpoint-missing: ${relation.id} (${relation.source})`);
|
|
20461
|
+
if (!isRuntimeTopologyNodeKind(source.kind))
|
|
20462
|
+
linkedNodeIds.add(source.id);
|
|
20463
|
+
}
|
|
20464
|
+
return {
|
|
20465
|
+
nodesById,
|
|
20466
|
+
namespaces,
|
|
20467
|
+
rootNamespaceIds: namespaces.filter((namespace) => namespace.parentId === undefined).map((namespace) => namespace.node.id),
|
|
20468
|
+
processes,
|
|
20469
|
+
mountStacks,
|
|
20470
|
+
unmountedMounts: mounts.filter((mount) => !mounterByMountId.has(mount.node.id)),
|
|
20471
|
+
spawns: relations.filter((relation) => relation.kind === "spawns"),
|
|
20472
|
+
mounts: relations.filter((relation) => relation.kind === "mounts"),
|
|
20473
|
+
deployments,
|
|
20474
|
+
linkedNodes: nodes.filter((node) => linkedNodeIds.has(node.id))
|
|
20475
|
+
};
|
|
20476
|
+
}
|
|
20477
|
+
function runtimeTopologyNamespaceType(node) {
|
|
20478
|
+
const value = node.namespace;
|
|
20479
|
+
const type = value !== null && typeof value === "object" && !Array.isArray(value) ? value.type : undefined;
|
|
20480
|
+
if (typeof type !== "string" || !RUNTIME_TOPOLOGY_NAMESPACE_TYPES.includes(type)) {
|
|
20481
|
+
throw new Error(`runtime-topology-namespace-invalid: ${node.id}`);
|
|
20482
|
+
}
|
|
20483
|
+
return type;
|
|
20484
|
+
}
|
|
20485
|
+
function runtimeTopologyMount(node) {
|
|
20486
|
+
const value = node.mount;
|
|
20487
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
20488
|
+
throw new Error(`runtime-topology-mount-invalid: ${node.id}`);
|
|
20489
|
+
const { source, target, mode, order } = value;
|
|
20490
|
+
if (source !== undefined && (typeof source !== "string" || source === "") || typeof target !== "string" || !RUNTIME_TOPOLOGY_MOUNT_TARGET_PATTERN.test(target) || mode !== "ro" && mode !== "rw" || typeof order !== "number" || !Number.isInteger(order) || order < 0) {
|
|
20491
|
+
throw new Error(`runtime-topology-mount-invalid: ${node.id}`);
|
|
20492
|
+
}
|
|
20493
|
+
return { node, ...source === undefined ? {} : { source }, target, mode, order };
|
|
20494
|
+
}
|
|
20495
|
+
function runtimeTopologyMountStackId(mounterId) {
|
|
20496
|
+
return `mounts__${mermaidId(mounterId)}`;
|
|
20497
|
+
}
|
|
20498
|
+
function runtimeTopologyMountLabel(mount) {
|
|
20499
|
+
return `${mount.node.name} (${mount.target}, ${mount.mode}, order ${mount.order})`;
|
|
20500
|
+
}
|
|
20501
|
+
function exportRuntimeTopologyMermaid(model) {
|
|
20502
|
+
const view = runtimeTopologyView(model);
|
|
20503
|
+
const namespaceById = new Map(view.namespaces.map((namespace) => [namespace.node.id, namespace]));
|
|
20504
|
+
const nodeLine = (indent, node, label = node.name) => `${indent}${mermaidId(node.id)}["${escapeMermaid(label)}"]`;
|
|
20505
|
+
const lines = ["%% Generated by ArchContext. Native model remains source of truth.", "flowchart TB"];
|
|
20506
|
+
const pushNamespace = (namespaceId, depth) => {
|
|
20507
|
+
const namespace = namespaceById.get(namespaceId);
|
|
20508
|
+
const indent = " ".repeat(depth);
|
|
20509
|
+
lines.push(`${indent}subgraph ${mermaidId(namespaceId)}["${escapeMermaid(`${namespace.node.name} (${namespace.type})`)}"]`);
|
|
20510
|
+
for (const processId of namespace.processIds)
|
|
20511
|
+
lines.push(nodeLine(`${indent} `, view.nodesById.get(processId)));
|
|
20512
|
+
for (const childId of namespace.namespaceIds)
|
|
20513
|
+
pushNamespace(childId, depth + 1);
|
|
20514
|
+
lines.push(`${indent}end`);
|
|
20515
|
+
};
|
|
20516
|
+
for (const namespaceId of view.rootNamespaceIds)
|
|
20517
|
+
pushNamespace(namespaceId, 1);
|
|
20518
|
+
for (const topologyProcess of view.processes)
|
|
20519
|
+
if (topologyProcess.namespaceId === undefined)
|
|
20520
|
+
lines.push(nodeLine(" ", topologyProcess.node));
|
|
20521
|
+
for (const stack of view.mountStacks) {
|
|
20522
|
+
const mounter = view.nodesById.get(stack.mounterId);
|
|
20523
|
+
lines.push(` subgraph ${runtimeTopologyMountStackId(stack.mounterId)}["${escapeMermaid(`Mounts: ${mounter.name}`)}"]`, " direction TB");
|
|
20524
|
+
for (const mount of stack.mounts)
|
|
20525
|
+
lines.push(nodeLine(" ", mount.node, runtimeTopologyMountLabel(mount)));
|
|
20526
|
+
for (let index = 1;index < stack.mounts.length; index += 1) {
|
|
20527
|
+
lines.push(` ${mermaidId(stack.mounts[index - 1].node.id)} ~~~ ${mermaidId(stack.mounts[index].node.id)}`);
|
|
20528
|
+
}
|
|
20529
|
+
lines.push(" end");
|
|
20530
|
+
}
|
|
20531
|
+
for (const mount of view.unmountedMounts)
|
|
20532
|
+
lines.push(nodeLine(" ", mount.node, runtimeTopologyMountLabel(mount)));
|
|
20533
|
+
for (const node of view.linkedNodes)
|
|
20534
|
+
lines.push(nodeLine(" ", node));
|
|
20535
|
+
for (const relation of [...view.spawns, ...view.mounts, ...view.deployments].sort((left, right) => compareStrings3(left.id, right.id))) {
|
|
20536
|
+
lines.push(` ${mermaidId(relation.source)} -->|"${escapeMermaid(relation.kind)}"| ${mermaidId(relation.target)}`);
|
|
20537
|
+
}
|
|
20538
|
+
const content = `${lines.join(`
|
|
20539
|
+
`)}
|
|
20540
|
+
`;
|
|
20541
|
+
return {
|
|
20542
|
+
format: "mermaid",
|
|
20543
|
+
digest: digestJson({ format: "mermaid", content }),
|
|
20544
|
+
files: [{ path: RUNTIME_TOPOLOGY_DIAGRAM_PATH, content }]
|
|
20545
|
+
};
|
|
20546
|
+
}
|
|
20547
|
+
function renderRuntimeTopologySummary(model, summaryPath) {
|
|
20548
|
+
const view = runtimeTopologyView(model);
|
|
20549
|
+
const cell = (value) => value.replaceAll("|", "\\|");
|
|
20550
|
+
const named = (id) => {
|
|
20551
|
+
const node = view.nodesById.get(id);
|
|
20552
|
+
return `\`${id}\`${node ? ` ${cell(node.name)}` : ""}`;
|
|
20553
|
+
};
|
|
20554
|
+
const list = (ids) => ids.length === 0 ? "none" : ids.map((id) => `\`${id}\``).join(", ");
|
|
20555
|
+
const lines = [
|
|
20556
|
+
"# Runtime Topology",
|
|
20557
|
+
"",
|
|
20558
|
+
"The architecture model declares this topology. No code facts verify it: archctx never reads the configuration it describes. A change to a file a node lists in `source.include` marks this page and its diagram stale.",
|
|
20559
|
+
"",
|
|
20560
|
+
`- Diagram: [Mermaid](${posix.relative(posix.dirname(summaryPath), RUNTIME_TOPOLOGY_DIAGRAM_PATH)})`,
|
|
20561
|
+
"",
|
|
20562
|
+
"## Namespaces",
|
|
20563
|
+
""
|
|
20564
|
+
];
|
|
20565
|
+
if (view.namespaces.length === 0)
|
|
20566
|
+
lines.push("- No namespaces recorded.");
|
|
20567
|
+
else {
|
|
20568
|
+
lines.push("| Namespace | Type | Parent | Namespaces | Processes |", "| --- | --- | --- | --- | --- |");
|
|
20569
|
+
for (const namespace of view.namespaces) {
|
|
20570
|
+
lines.push(`| ${named(namespace.node.id)} | ${namespace.type} | ${namespace.parentId === undefined ? "none" : `\`${namespace.parentId}\``} | ${list(namespace.namespaceIds)} | ${list(namespace.processIds)} |`);
|
|
20571
|
+
}
|
|
20572
|
+
}
|
|
20573
|
+
lines.push("", "## Processes", "");
|
|
20574
|
+
if (view.processes.length === 0)
|
|
20575
|
+
lines.push("- No processes recorded.");
|
|
20576
|
+
else {
|
|
20577
|
+
lines.push("| Process | Namespace | Spawns | Deployed here |", "| --- | --- | --- | --- |");
|
|
20578
|
+
for (const topologyProcess of view.processes) {
|
|
20579
|
+
const id = topologyProcess.node.id;
|
|
20580
|
+
const spawned = view.spawns.filter((relation) => relation.source === id).map((relation) => relation.target);
|
|
20581
|
+
const deployed = view.deployments.filter((relation) => relation.target === id).map((relation) => relation.source);
|
|
20582
|
+
lines.push(`| ${named(id)} | ${topologyProcess.namespaceId === undefined ? "none" : `\`${topologyProcess.namespaceId}\``} | ${list([...new Set(spawned)].sort(compareStrings3))} | ${list([...new Set(deployed)].sort(compareStrings3))} |`);
|
|
20583
|
+
}
|
|
20584
|
+
}
|
|
20585
|
+
lines.push("", "## Mount Stacks", "");
|
|
20586
|
+
const mountRows = (stack) => [
|
|
20587
|
+
"| Order | Mount | Source | Target | Mode |",
|
|
20588
|
+
"| --- | --- | --- | --- | --- |",
|
|
20589
|
+
...stack.map((mount) => `| ${mount.order} | ${named(mount.node.id)} | ${mount.source === undefined ? "none" : `\`${cell(mount.source)}\``} | \`${cell(mount.target)}\` | ${mount.mode} |`)
|
|
20590
|
+
];
|
|
20591
|
+
if (view.mountStacks.length === 0 && view.unmountedMounts.length === 0)
|
|
20592
|
+
lines.push("- No mounts recorded.");
|
|
20593
|
+
for (const [index, stack] of view.mountStacks.entries()) {
|
|
20594
|
+
if (index > 0)
|
|
20595
|
+
lines.push("");
|
|
20596
|
+
lines.push(`### ${named(stack.mounterId)}`, "", ...mountRows(stack.mounts));
|
|
20597
|
+
}
|
|
20598
|
+
if (view.unmountedMounts.length > 0) {
|
|
20599
|
+
if (view.mountStacks.length > 0)
|
|
20600
|
+
lines.push("");
|
|
20601
|
+
lines.push("### Not mounted", "", ...mountRows(view.unmountedMounts));
|
|
20602
|
+
}
|
|
20603
|
+
const otherDeployments = view.deployments.filter((relation) => view.nodesById.get(relation.target)?.kind !== "process");
|
|
20604
|
+
if (otherDeployments.length > 0) {
|
|
20605
|
+
lines.push("", "## Other Deployments", "");
|
|
20606
|
+
for (const relation of otherDeployments)
|
|
20607
|
+
lines.push(`- \`${relation.source}\` -> \`${relation.target}\` — ${relation.intent}`);
|
|
20608
|
+
}
|
|
20609
|
+
lines.push("", "## Source Anchors", "", "| Node | Source |", "| --- | --- |");
|
|
20610
|
+
for (const node of [...view.nodesById.values()].filter((entry) => isRuntimeTopologyNodeKind(entry.kind)).sort((left, right) => compareStrings3(left.id, right.id))) {
|
|
20611
|
+
const include = nativeNodeSource(node)?.include ?? [];
|
|
20612
|
+
lines.push(`| ${named(node.id)} | ${include.length === 0 ? "model-authored, no source anchor" : include.map((pattern) => `\`${cell(pattern)}\``).join(", ")} |`);
|
|
20613
|
+
}
|
|
20614
|
+
return `${lines.join(`
|
|
20615
|
+
`)}
|
|
20616
|
+
`;
|
|
20617
|
+
}
|
|
20187
20618
|
function loadNativeModelFromArchContext(root) {
|
|
20188
20619
|
return nativeModelFromYamlObjects({
|
|
20189
20620
|
nodes: readYamlObjects(resolve6(root, ".archcontext/model/nodes")),
|
|
@@ -20202,6 +20633,7 @@ function nativeModelFromYamlObjects(input) {
|
|
|
20202
20633
|
throw new Error(`architecture-node-schema-version-unsupported: ${node.id || "unknown"} (${String(node.schemaVersion)})`);
|
|
20203
20634
|
}
|
|
20204
20635
|
assertArchitectureNodeSourceV2(node);
|
|
20636
|
+
nativeNodeLayer(node);
|
|
20205
20637
|
}
|
|
20206
20638
|
const flows = input.flows;
|
|
20207
20639
|
for (const flow of flows)
|
|
@@ -20386,8 +20818,15 @@ function renderTargetGeneratedBody(target, model, input) {
|
|
|
20386
20818
|
return exportDocumentationStructurizrWorkspace(model).files[0].content;
|
|
20387
20819
|
if (target.type === "diagram-likec4")
|
|
20388
20820
|
return exportDocumentationLikeC4Model(model).files[0].content;
|
|
20821
|
+
if (target.type === "diagram-runtime-topology")
|
|
20822
|
+
return exportRuntimeTopologyMermaid(model).files[0].content;
|
|
20823
|
+
if (target.type === "runtime-topology-summary")
|
|
20824
|
+
return renderRuntimeTopologySummary(model, target.path);
|
|
20389
20825
|
return "";
|
|
20390
20826
|
}
|
|
20827
|
+
function architectureIndexLink(path) {
|
|
20828
|
+
return path.replace(/^docs\/architecture\//, "");
|
|
20829
|
+
}
|
|
20391
20830
|
function renderArchitectureIndex(model, layout2) {
|
|
20392
20831
|
const lines = [
|
|
20393
20832
|
"# Architecture Index",
|
|
@@ -20412,6 +20851,7 @@ function renderArchitectureIndex(model, layout2) {
|
|
|
20412
20851
|
"- [Mermaid](diagrams/architecture.mmd)",
|
|
20413
20852
|
"- [Structurizr JSON](diagrams/architecture.structurizr.json)",
|
|
20414
20853
|
"- [LikeC4](diagrams/architecture.likec4)",
|
|
20854
|
+
...layout2.targets.flatMap((target) => target.type === "diagram-runtime-topology" ? [`- [Runtime topology Mermaid](${architectureIndexLink(target.path)})`] : target.type === "runtime-topology-summary" ? [`- [Runtime topology](${architectureIndexLink(target.path)})`] : []),
|
|
20415
20855
|
"- [Decision index](decisions/index.md)",
|
|
20416
20856
|
"- [Architecture changelog](changelog.md)"
|
|
20417
20857
|
];
|
|
@@ -20865,26 +21305,6 @@ function isManagedArchitectureDocumentationMarkdownPath(path) {
|
|
|
20865
21305
|
function pathSegment2(id) {
|
|
20866
21306
|
return stableId(id).replace(/\./g, "-");
|
|
20867
21307
|
}
|
|
20868
|
-
function documentationLikeC4Kind(kind) {
|
|
20869
|
-
if (kind === "external-system")
|
|
20870
|
-
return "external_system";
|
|
20871
|
-
if (kind === "datastore")
|
|
20872
|
-
return "datastore";
|
|
20873
|
-
if (["capability", "module", "component", "interface"].includes(kind))
|
|
20874
|
-
return kind;
|
|
20875
|
-
return "component";
|
|
20876
|
-
}
|
|
20877
|
-
function documentationStructurizrElementType(kind) {
|
|
20878
|
-
if (kind === "capability")
|
|
20879
|
-
return "Software System";
|
|
20880
|
-
if (kind === "module")
|
|
20881
|
-
return "Container";
|
|
20882
|
-
if (kind === "datastore")
|
|
20883
|
-
return "Database";
|
|
20884
|
-
if (kind === "external-system")
|
|
20885
|
-
return "External System";
|
|
20886
|
-
return "Component";
|
|
20887
|
-
}
|
|
20888
21308
|
function escapeDsl(value) {
|
|
20889
21309
|
return value.replace(/"/g, "'");
|
|
20890
21310
|
}
|
|
@@ -21548,7 +21968,7 @@ function sameOwners(left, right) {
|
|
|
21548
21968
|
|
|
21549
21969
|
// packages/core/module-statistics/src/index.ts
|
|
21550
21970
|
function evaluateDependencyConstraints(input) {
|
|
21551
|
-
const constraints = [...input.constraints].sort((left, right) =>
|
|
21971
|
+
const constraints = [...input.constraints].sort((left, right) => compare2(left.id, right.id));
|
|
21552
21972
|
const certification = certifyCodeFacts(input.codeFacts, input.worktreeDigest, input.truncated, input.importNodeLimitExceeded ?? false);
|
|
21553
21973
|
if (constraints.length === 0) {
|
|
21554
21974
|
return {
|
|
@@ -21605,7 +22025,7 @@ function evaluateDependencyConstraints(input) {
|
|
|
21605
22025
|
reasonCodes.add("unresolved-import");
|
|
21606
22026
|
if (input.workspacePackagesResolved === false)
|
|
21607
22027
|
reasonCodes.add("workspace-resolution-failed");
|
|
21608
|
-
const sortedViolations = [...violations.values()].sort((left, right) =>
|
|
22028
|
+
const sortedViolations = [...violations.values()].sort((left, right) => compare2(left.constraintId, right.constraintId) || compare2(left.fromPath, right.fromPath) || compare2(left.toPath, right.toPath));
|
|
21609
22029
|
return {
|
|
21610
22030
|
schemaVersion: "archcontext.dependency-constraint-evaluation/v1",
|
|
21611
22031
|
status: sortedViolations.length > 0 ? "violated" : reasonCodes.size > 0 ? "undetermined" : "pass",
|
|
@@ -21682,7 +22102,7 @@ function measureCapabilityDependencies(input) {
|
|
|
21682
22102
|
const cellKey = JSON.stringify([from.capability, to.capability]);
|
|
21683
22103
|
let cell = cells.get(cellKey);
|
|
21684
22104
|
if (cell === undefined) {
|
|
21685
|
-
const relationIds = [...new Set(declaringRelations.get(cellKey) ?? [])].sort(
|
|
22105
|
+
const relationIds = [...new Set(declaringRelations.get(cellKey) ?? [])].sort(compare2);
|
|
21686
22106
|
cell = { from: from.capability, to: to.capability, imports: 0, calls: null, declared: from.capability === to.capability || relationIds.length > 0, relationIds };
|
|
21687
22107
|
cells.set(cellKey, cell);
|
|
21688
22108
|
}
|
|
@@ -21690,14 +22110,14 @@ function measureCapabilityDependencies(input) {
|
|
|
21690
22110
|
attributedEdges.push({ fromPath: edge.from, toPath: edge.to, fromCapability: from.capability, toCapability: to.capability });
|
|
21691
22111
|
}
|
|
21692
22112
|
const edgeLimit = input.edgeLimit ?? CAPABILITY_DEPENDENCIES_EDGE_LIMIT;
|
|
21693
|
-
attributedEdges.sort((left, right) =>
|
|
22113
|
+
attributedEdges.sort((left, right) => compare2(left.fromPath, right.fromPath) || compare2(left.toPath, right.toPath));
|
|
21694
22114
|
const listed = attributedEdges.slice(0, edgeLimit);
|
|
21695
22115
|
return {
|
|
21696
22116
|
schemaVersion: CAPABILITY_DEPENDENCIES_SCHEMA_VERSION,
|
|
21697
22117
|
ownershipRule: "parent-chain",
|
|
21698
22118
|
filter: { from: input.from ?? null, into: input.into ?? null },
|
|
21699
|
-
capabilities: input.nodes.filter((node) => node.kind === "capability").map((node) => node.id).sort(
|
|
21700
|
-
matrix: [...cells.values()].sort((left, right) =>
|
|
22119
|
+
capabilities: input.nodes.filter((node) => node.kind === "capability").map((node) => node.id).sort(compare2),
|
|
22120
|
+
matrix: [...cells.values()].sort((left, right) => compare2(left.from, right.from) || compare2(left.to, right.to)),
|
|
21701
22121
|
imports: {
|
|
21702
22122
|
coverage: certification.coverage,
|
|
21703
22123
|
reasonCodes: [...certification.reasonCodes].sort(),
|
|
@@ -21733,7 +22153,7 @@ function isRepositoryLocalSpecifier(specifier, workspacePackages) {
|
|
|
21733
22153
|
return true;
|
|
21734
22154
|
return workspacePackages.some((workspacePackage) => specifier === workspacePackage.name || specifier.startsWith(`${workspacePackage.name}/`));
|
|
21735
22155
|
}
|
|
21736
|
-
function
|
|
22156
|
+
function compare2(left, right) {
|
|
21737
22157
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
21738
22158
|
}
|
|
21739
22159
|
function measureModuleStatistics(input) {
|
|
@@ -21818,7 +22238,7 @@ function modelDigest(model, constraints) {
|
|
|
21818
22238
|
nodes: [...model.nodes].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
21819
22239
|
relations: [...model.relations].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
21820
22240
|
flows: [...model.flows ?? []].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
21821
|
-
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) =>
|
|
22241
|
+
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) => compare2(left.id, right.id)) }
|
|
21822
22242
|
});
|
|
21823
22243
|
}
|
|
21824
22244
|
function countViolationsByNode(evaluation) {
|
|
@@ -22715,6 +23135,29 @@ function listGitVisibleFiles(root, pathspec) {
|
|
|
22715
23135
|
}
|
|
22716
23136
|
}).sort((left, right) => left.localeCompare(right));
|
|
22717
23137
|
}
|
|
23138
|
+
function ignoredRepositoryPaths(root, paths) {
|
|
23139
|
+
if (paths.length === 0)
|
|
23140
|
+
return [];
|
|
23141
|
+
const result = spawnSync2("git", ["check-ignore", "--verbose", "--no-index", "-z", "--stdin"], {
|
|
23142
|
+
cwd: root,
|
|
23143
|
+
encoding: "utf8",
|
|
23144
|
+
input: paths.map((path) => `${path}\x00`).join(""),
|
|
23145
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
23146
|
+
});
|
|
23147
|
+
if (result.status !== 0 && result.status !== 1) {
|
|
23148
|
+
const detail = result.error?.message ?? (result.stderr.trim() || `git check-ignore exited ${result.status}`);
|
|
23149
|
+
throw new GitVisibleFilesUnavailableError(root, detail);
|
|
23150
|
+
}
|
|
23151
|
+
const fields = result.stdout.split("\x00");
|
|
23152
|
+
const ignored = [];
|
|
23153
|
+
for (let index = 0;index + 3 < fields.length; index += 4) {
|
|
23154
|
+
const [source, line, pattern, path] = fields.slice(index, index + 4);
|
|
23155
|
+
if (pattern.startsWith("!"))
|
|
23156
|
+
continue;
|
|
23157
|
+
ignored.push({ path, source, line: Number(line), pattern });
|
|
23158
|
+
}
|
|
23159
|
+
return ignored;
|
|
23160
|
+
}
|
|
22718
23161
|
function readHeadSha(root) {
|
|
22719
23162
|
try {
|
|
22720
23163
|
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
@@ -26083,7 +26526,7 @@ function buildObservations(snapshot, structure, model) {
|
|
|
26083
26526
|
evidence: evidence.evidenceGap()
|
|
26084
26527
|
});
|
|
26085
26528
|
}
|
|
26086
|
-
return drafts.map((draft, index) => ({ draft, order: draft.kind === "cycle" ? index : 0 })).sort((left, right) =>
|
|
26529
|
+
return drafts.map((draft, index) => ({ draft, order: draft.kind === "cycle" ? index : 0 })).sort((left, right) => compare3(left.draft.kind, right.draft.kind) || left.order - right.order || compare3(left.draft.subjectSelectorId, right.draft.subjectSelectorId)).map(({ draft }) => ({
|
|
26087
26530
|
kind: draft.kind,
|
|
26088
26531
|
subjectSelectorId: draft.subjectSelectorId,
|
|
26089
26532
|
affectedNodeIds: draft.affectedNodeIds,
|
|
@@ -26273,13 +26716,13 @@ function signalIdFor(draft) {
|
|
|
26273
26716
|
}
|
|
26274
26717
|
function modelDigest2(model, constraints) {
|
|
26275
26718
|
return digestJson({
|
|
26276
|
-
nodes: [...model.nodes].sort((left, right) =>
|
|
26277
|
-
relations: [...model.relations].sort((left, right) =>
|
|
26278
|
-
flows: [...model.flows ?? []].sort((left, right) =>
|
|
26279
|
-
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) =>
|
|
26719
|
+
nodes: [...model.nodes].sort((left, right) => compare3(left.id, right.id)),
|
|
26720
|
+
relations: [...model.relations].sort((left, right) => compare3(left.id, right.id)),
|
|
26721
|
+
flows: [...model.flows ?? []].sort((left, right) => compare3(left.id, right.id)),
|
|
26722
|
+
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) => compare3(left.id, right.id)) }
|
|
26280
26723
|
});
|
|
26281
26724
|
}
|
|
26282
|
-
function
|
|
26725
|
+
function compare3(left, right) {
|
|
26283
26726
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
26284
26727
|
}
|
|
26285
26728
|
function isNumber(value) {
|
|
@@ -26301,17 +26744,49 @@ var architecture_node_schema_default = {
|
|
|
26301
26744
|
type: "object",
|
|
26302
26745
|
additionalProperties: false,
|
|
26303
26746
|
required: ["schemaVersion", "id", "kind", "name", "status", "summary"],
|
|
26747
|
+
allOf: [
|
|
26748
|
+
{
|
|
26749
|
+
if: { properties: { kind: { const: "namespace" } }, required: ["kind"] },
|
|
26750
|
+
then: { required: ["namespace"] },
|
|
26751
|
+
else: { not: { required: ["namespace"] } }
|
|
26752
|
+
},
|
|
26753
|
+
{
|
|
26754
|
+
if: { properties: { kind: { const: "mount" } }, required: ["kind"] },
|
|
26755
|
+
then: { required: ["mount"] },
|
|
26756
|
+
else: { not: { required: ["mount"] } }
|
|
26757
|
+
}
|
|
26758
|
+
],
|
|
26304
26759
|
properties: {
|
|
26305
26760
|
schemaVersion: { const: "archcontext.node/v2" },
|
|
26306
26761
|
id: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
26307
26762
|
kind: {
|
|
26308
26763
|
type: "string",
|
|
26309
|
-
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment"]
|
|
26764
|
+
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment", "process", "namespace", "mount"]
|
|
26310
26765
|
},
|
|
26311
26766
|
name: { type: "string" },
|
|
26312
26767
|
status: { type: "string", enum: ["active", "planned", "deprecated", "removed"] },
|
|
26313
26768
|
parent: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
26769
|
+
layer: { type: "string", pattern: "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" },
|
|
26314
26770
|
summary: { type: "string" },
|
|
26771
|
+
namespace: {
|
|
26772
|
+
type: "object",
|
|
26773
|
+
additionalProperties: false,
|
|
26774
|
+
required: ["type"],
|
|
26775
|
+
properties: {
|
|
26776
|
+
type: { type: "string", enum: ["host", "container", "pod", "kubernetes", "pid", "mount", "net", "user"] }
|
|
26777
|
+
}
|
|
26778
|
+
},
|
|
26779
|
+
mount: {
|
|
26780
|
+
type: "object",
|
|
26781
|
+
additionalProperties: false,
|
|
26782
|
+
required: ["target", "mode", "order"],
|
|
26783
|
+
properties: {
|
|
26784
|
+
source: { type: "string", minLength: 1 },
|
|
26785
|
+
target: { type: "string", pattern: "^/([^/\\u0000]+(/[^/\\u0000]+)*)?$" },
|
|
26786
|
+
mode: { type: "string", enum: ["ro", "rw"] },
|
|
26787
|
+
order: { type: "integer", minimum: 0 }
|
|
26788
|
+
}
|
|
26789
|
+
},
|
|
26315
26790
|
responsibilities: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
26316
26791
|
source: {
|
|
26317
26792
|
type: "object",
|
|
@@ -26400,7 +26875,7 @@ var architecture_relation_schema_default = {
|
|
|
26400
26875
|
id: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
26401
26876
|
kind: {
|
|
26402
26877
|
type: "string",
|
|
26403
|
-
enum: ["depends-on", "calls", "publishes", "subscribes", "reads", "writes", "implements", "deployed-on"]
|
|
26878
|
+
enum: ["depends-on", "calls", "publishes", "subscribes", "reads", "writes", "implements", "deployed-on", "spawns", "runs-in", "mounts"]
|
|
26404
26879
|
},
|
|
26405
26880
|
source: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
26406
26881
|
target: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
@@ -27108,6 +27583,22 @@ function createDefaultProjectionTargetManifest() {
|
|
|
27108
27583
|
ownership: "generated",
|
|
27109
27584
|
format: "likec4"
|
|
27110
27585
|
},
|
|
27586
|
+
{
|
|
27587
|
+
id: "projection_rule.diagram.runtime-topology",
|
|
27588
|
+
targetType: "diagram-runtime-topology",
|
|
27589
|
+
scope: { kind: "diagram", id: "runtime-topology" },
|
|
27590
|
+
pathTemplate: "docs/architecture/diagrams/runtime-topology.mmd",
|
|
27591
|
+
ownership: "generated",
|
|
27592
|
+
format: "mermaid"
|
|
27593
|
+
},
|
|
27594
|
+
{
|
|
27595
|
+
id: "projection_rule.runtime-topology.summary",
|
|
27596
|
+
targetType: "runtime-topology-summary",
|
|
27597
|
+
scope: { kind: "diagram", id: "runtime-topology" },
|
|
27598
|
+
pathTemplate: "docs/architecture/runtime-topology.md",
|
|
27599
|
+
ownership: "generated",
|
|
27600
|
+
format: "markdown"
|
|
27601
|
+
},
|
|
27111
27602
|
{
|
|
27112
27603
|
id: "projection_rule.agent-context.claude",
|
|
27113
27604
|
targetType: "agent-context",
|
|
@@ -27253,10 +27744,19 @@ class YamlModelStore {
|
|
|
27253
27744
|
}
|
|
27254
27745
|
}
|
|
27255
27746
|
const files = listModelFiles(workspace.root);
|
|
27747
|
+
const topologyRecords = { nodes: [], relations: [] };
|
|
27748
|
+
const topologyRecordPaths = new Map;
|
|
27256
27749
|
for (const file of files) {
|
|
27257
27750
|
if (file.path.startsWith(".archcontext/model/") && /\.ya?ml$/.test(file.path)) {
|
|
27258
27751
|
try {
|
|
27259
27752
|
const value = parseJsonOrStableYaml(file.body, file.path);
|
|
27753
|
+
for (const [directory, records] of [["nodes", topologyRecords.nodes], ["relations", topologyRecords.relations]]) {
|
|
27754
|
+
if (!file.path.startsWith(`.archcontext/model/${directory}/`) || !isJsonRecord2(value))
|
|
27755
|
+
continue;
|
|
27756
|
+
records.push(value);
|
|
27757
|
+
if (typeof value.id === "string")
|
|
27758
|
+
topologyRecordPaths.set(value.id, file.path);
|
|
27759
|
+
}
|
|
27260
27760
|
if (file.path.startsWith(".archcontext/model/nodes/")) {
|
|
27261
27761
|
const { issues } = validateJsonSchema(architecture_node_schema_default, value);
|
|
27262
27762
|
for (const issue of issues) {
|
|
@@ -27277,6 +27777,9 @@ class YamlModelStore {
|
|
|
27277
27777
|
errors.push(`${file.path}: expected archcontext.flow/v1, got ${file.schemaVersion || "missing"}`);
|
|
27278
27778
|
}
|
|
27279
27779
|
}
|
|
27780
|
+
for (const issue of runtimeTopologyModelIssues(topologyRecords)) {
|
|
27781
|
+
errors.push(`${topologyRecordPaths.get(issue.entityId) ?? issue.entityId}: ${issue.field}: ${issue.invariant}`);
|
|
27782
|
+
}
|
|
27280
27783
|
const adr = validateAdrAppliesTo(files);
|
|
27281
27784
|
const constraints = readDependencyConstraints(files);
|
|
27282
27785
|
const reviewPolicy = readReviewPolicy(files);
|
|
@@ -27297,6 +27800,21 @@ class YamlModelStore {
|
|
|
27297
27800
|
return { digest: digestJson(changeSet), summary: "ChangeSet preview generated" };
|
|
27298
27801
|
}
|
|
27299
27802
|
}
|
|
27803
|
+
var ARCHITECTURE_MODEL_COMMITTED_PATHS = Object.freeze([
|
|
27804
|
+
".archcontext/manifest.yaml",
|
|
27805
|
+
".archcontext/product.yaml",
|
|
27806
|
+
".archcontext/model/nodes/",
|
|
27807
|
+
".archcontext/model/relations/",
|
|
27808
|
+
".archcontext/model/flows/",
|
|
27809
|
+
".archcontext/decisions/",
|
|
27810
|
+
".archcontext/policies/",
|
|
27811
|
+
".archcontext/practices/",
|
|
27812
|
+
".archcontext/projections/"
|
|
27813
|
+
]);
|
|
27814
|
+
function architectureModelIgnoreProbePaths(root) {
|
|
27815
|
+
const directories = ARCHITECTURE_MODEL_COMMITTED_PATHS.filter((path) => path.endsWith("/")).map((path) => path.slice(0, -1));
|
|
27816
|
+
return [...new Set([...ARCHITECTURE_MODEL_COMMITTED_PATHS, ...collectArchContextFiles(root, directories)])];
|
|
27817
|
+
}
|
|
27300
27818
|
function listModelFiles(root) {
|
|
27301
27819
|
const paths = collectArchContextFiles(root, [
|
|
27302
27820
|
".archcontext/manifest.yaml",
|
|
@@ -27381,6 +27899,9 @@ function extractSchemaVersion(body, path) {
|
|
|
27381
27899
|
return "archcontext.generated/v1";
|
|
27382
27900
|
return "";
|
|
27383
27901
|
}
|
|
27902
|
+
function isJsonRecord2(value) {
|
|
27903
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
27904
|
+
}
|
|
27384
27905
|
function withTrailingNewline(body) {
|
|
27385
27906
|
return body.endsWith(`
|
|
27386
27907
|
`) ? body : `${body}
|
|
@@ -31318,13 +31839,10 @@ class HttpContext7Transport {
|
|
|
31318
31839
|
url.searchParams.set("libraryName", input.libraryName);
|
|
31319
31840
|
url.searchParams.set("query", input.query);
|
|
31320
31841
|
url.searchParams.set("fast", input.fast ? "true" : "false");
|
|
31321
|
-
const
|
|
31842
|
+
const { payload } = await fetchJsonWithTimeout(url, {
|
|
31322
31843
|
method: "GET",
|
|
31323
31844
|
headers: authHeaders(input.apiKey)
|
|
31324
|
-
}, input.timeoutMs);
|
|
31325
|
-
if (!response.ok)
|
|
31326
|
-
throw httpStatusError("Context7 search failed", response);
|
|
31327
|
-
const { payload } = await parseJsonResponse(response, "Context7 search");
|
|
31845
|
+
}, input.timeoutMs, "Context7 search");
|
|
31328
31846
|
return parseSearchResponse(payload);
|
|
31329
31847
|
}
|
|
31330
31848
|
async getContext(input) {
|
|
@@ -31333,13 +31851,10 @@ class HttpContext7Transport {
|
|
|
31333
31851
|
url.searchParams.set("query", input.query);
|
|
31334
31852
|
url.searchParams.set("type", "json");
|
|
31335
31853
|
url.searchParams.set("fast", "true");
|
|
31336
|
-
const
|
|
31854
|
+
const { payload } = await fetchJsonWithTimeout(url, {
|
|
31337
31855
|
method: "GET",
|
|
31338
31856
|
headers: authHeaders(input.apiKey)
|
|
31339
|
-
}, input.timeoutMs);
|
|
31340
|
-
if (!response.ok)
|
|
31341
|
-
throw httpStatusError("Context7 context fetch failed", response);
|
|
31342
|
-
const { payload } = await parseJsonResponse(response, "Context7 context fetch");
|
|
31857
|
+
}, input.timeoutMs, "Context7 context fetch");
|
|
31343
31858
|
return parseDocumentationResponse(payload).slice(0, input.maxResults);
|
|
31344
31859
|
}
|
|
31345
31860
|
}
|
|
@@ -31437,11 +31952,14 @@ function normalizeSourceUri(value) {
|
|
|
31437
31952
|
return;
|
|
31438
31953
|
}
|
|
31439
31954
|
}
|
|
31440
|
-
async function
|
|
31955
|
+
async function fetchJsonWithTimeout(input, init, timeoutMs, label) {
|
|
31441
31956
|
const controller = new AbortController;
|
|
31442
31957
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
31443
31958
|
try {
|
|
31444
|
-
|
|
31959
|
+
const response = await withLocalEgress2("context7", () => fetch(input, { ...init, redirect: "error", signal: controller.signal }));
|
|
31960
|
+
if (!response.ok)
|
|
31961
|
+
throw httpStatusError(`${label} failed`, response);
|
|
31962
|
+
return await parseJsonResponse(response, label);
|
|
31445
31963
|
} catch (error) {
|
|
31446
31964
|
if (isAbortError(error)) {
|
|
31447
31965
|
throw new Context7ProviderError("timeout", "Context7 provider request timed out", { retryable: true, cause: error });
|
|
@@ -32674,6 +33192,11 @@ async function runArchitectureDocsProjectionCommand(input, cwd, daemon) {
|
|
|
32674
33192
|
try {
|
|
32675
33193
|
const subcommand = input.action ?? "plan";
|
|
32676
33194
|
const root = findRepositoryRoot(cwd);
|
|
33195
|
+
if (subcommand === "adopt") {
|
|
33196
|
+
const ignored = architectureModelIgnoredEnvelope("docs.adopt", root);
|
|
33197
|
+
if (ignored)
|
|
33198
|
+
return ignored;
|
|
33199
|
+
}
|
|
32677
33200
|
let profile;
|
|
32678
33201
|
let projection;
|
|
32679
33202
|
let acceptedChange;
|
|
@@ -32789,6 +33312,23 @@ async function runArchitectureDocsProjectionCommand(input, cwd, daemon) {
|
|
|
32789
33312
|
throw publicOperationFailure(error);
|
|
32790
33313
|
}
|
|
32791
33314
|
}
|
|
33315
|
+
function ignoredArchitectureModelPaths(root) {
|
|
33316
|
+
const ignored = ignoredRepositoryPaths(root, architectureModelIgnoreProbePaths(root));
|
|
33317
|
+
const directories = ignored.filter((entry) => entry.path.endsWith("/")).map((entry) => entry.path);
|
|
33318
|
+
return ignored.filter((entry) => entry.path.endsWith("/") || !directories.some((directory) => entry.path.startsWith(directory)));
|
|
33319
|
+
}
|
|
33320
|
+
function architectureModelIgnoredEnvelope(requestId, root) {
|
|
33321
|
+
const ignored = ignoredArchitectureModelPaths(root);
|
|
33322
|
+
if (ignored.length === 0)
|
|
33323
|
+
return;
|
|
33324
|
+
const paths = ignored.map((entry) => entry.path).slice(0, 8);
|
|
33325
|
+
return publicFailureEnvelope(requestId, "AC_PRECONDITION_FAILED", "architecture-model-ignored", {
|
|
33326
|
+
pathCount: ignored.length,
|
|
33327
|
+
paths,
|
|
33328
|
+
commands: [`git check-ignore --verbose --no-index -- ${paths.join(" ")}`],
|
|
33329
|
+
omittedPathCount: ignored.length - paths.length
|
|
33330
|
+
});
|
|
33331
|
+
}
|
|
32792
33332
|
function committedArchitectureDocsProfile(root) {
|
|
32793
33333
|
return existsSync9(resolve13(root, ARCHITECTURE_DOCS_PROJECTION_MANIFEST_PATH)) ? loadArchitectureDocumentationProfile(root) : "default";
|
|
32794
33334
|
}
|
|
@@ -33099,6 +33639,11 @@ async function runProjectionProtocolCommand(invocation, cwd, daemon) {
|
|
|
33099
33639
|
if (replay)
|
|
33100
33640
|
return replay;
|
|
33101
33641
|
}
|
|
33642
|
+
if (request.mode === "adopt") {
|
|
33643
|
+
const ignored = architectureModelIgnoredEnvelope("projection.run", root);
|
|
33644
|
+
if (ignored)
|
|
33645
|
+
return ignored;
|
|
33646
|
+
}
|
|
33102
33647
|
let observed;
|
|
33103
33648
|
let acceptedChange = request.acceptedChange;
|
|
33104
33649
|
let declinedUnprovableNodeIds;
|
|
@@ -34663,6 +35208,7 @@ init_projection_recovery();
|
|
|
34663
35208
|
init_projection_writer();
|
|
34664
35209
|
init_projection_manifest();
|
|
34665
35210
|
init_refactor();
|
|
35211
|
+
init_runtime_topology();
|
|
34666
35212
|
init_schema();
|
|
34667
35213
|
init_failure();
|
|
34668
35214
|
init_validator();
|
|
@@ -47072,7 +47618,7 @@ function upgradeExistingLocalStoreTarget2(paths, integrityCheck) {
|
|
|
47072
47618
|
var RUNTIME_AGENT_JOB_STATUSES = ["queued", "running", "succeeded", "failed", "cancelled", "superseded", "expired"];
|
|
47073
47619
|
var CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION = "archcontext.changeset-model-transition/v1";
|
|
47074
47620
|
function assertChangeSetModelTransition(value, label = "changeset-model-transition-invalid") {
|
|
47075
|
-
if (!
|
|
47621
|
+
if (!isJsonRecord3(value) || Object.keys(value).sort().join(",") !== "after,before,schemaVersion" || value.schemaVersion !== CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION || typeof value.before !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value.before) || typeof value.after !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value.after)) {
|
|
47076
47622
|
throw new Error(label);
|
|
47077
47623
|
}
|
|
47078
47624
|
return { schemaVersion: CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION, before: value.before, after: value.after };
|
|
@@ -51360,10 +51906,10 @@ function readChangeSetJournalTaskSessionId(metadataJson, journalId) {
|
|
|
51360
51906
|
} catch {
|
|
51361
51907
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51362
51908
|
}
|
|
51363
|
-
if (!
|
|
51909
|
+
if (!isJsonRecord3(metadata))
|
|
51364
51910
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51365
51911
|
const reason = metadata.reason;
|
|
51366
|
-
if (!
|
|
51912
|
+
if (!isJsonRecord3(reason) || typeof reason.taskSessionId !== "string" || reason.taskSessionId === "") {
|
|
51367
51913
|
throw new Error(`changeset-journal-reason-malformed: ${journalId}`);
|
|
51368
51914
|
}
|
|
51369
51915
|
return reason.taskSessionId;
|
|
@@ -51396,7 +51942,7 @@ function committedChangeSetJournalFiles(filesJson, journalId) {
|
|
|
51396
51942
|
if (!Array.isArray(parsed))
|
|
51397
51943
|
throw new Error(`changeset-journal-files-malformed: ${journalId}`);
|
|
51398
51944
|
return parsed.map((entry) => {
|
|
51399
|
-
if (!
|
|
51945
|
+
if (!isJsonRecord3(entry) || typeof entry.path !== "string" || typeof entry.operation !== "string") {
|
|
51400
51946
|
throw new Error(`changeset-journal-file-malformed: ${journalId}`);
|
|
51401
51947
|
}
|
|
51402
51948
|
if (typeof entry.bodyHash !== "string" || entry.bodyHash === "") {
|
|
@@ -51416,7 +51962,7 @@ function committedChangeSetProjectionOwned(metadataJson, journalId) {
|
|
|
51416
51962
|
} catch {
|
|
51417
51963
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51418
51964
|
}
|
|
51419
|
-
return
|
|
51965
|
+
return isJsonRecord3(metadata) && metadata.projectionOwned === true;
|
|
51420
51966
|
}
|
|
51421
51967
|
function committedChangeSetModelTransition(metadataJson, journalId) {
|
|
51422
51968
|
let metadata;
|
|
@@ -51425,7 +51971,7 @@ function committedChangeSetModelTransition(metadataJson, journalId) {
|
|
|
51425
51971
|
} catch {
|
|
51426
51972
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51427
51973
|
}
|
|
51428
|
-
if (!
|
|
51974
|
+
if (!isJsonRecord3(metadata))
|
|
51429
51975
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51430
51976
|
if (metadata.modelTransition === undefined)
|
|
51431
51977
|
return;
|
|
@@ -51439,7 +51985,7 @@ function readChangeSetJournalMetadata(db, journalId) {
|
|
|
51439
51985
|
}
|
|
51440
51986
|
function withChangeSetJournalArchitectureLedger(metadata, patch) {
|
|
51441
51987
|
const existing = metadata.architectureLedger;
|
|
51442
|
-
const architectureLedger =
|
|
51988
|
+
const architectureLedger = isJsonRecord3(existing) ? { ...existing, ...patch } : {
|
|
51443
51989
|
schemaVersion: "archcontext.changeset-ledger-recovery/v1",
|
|
51444
51990
|
...patch
|
|
51445
51991
|
};
|
|
@@ -51461,12 +52007,12 @@ function changeSetJournalPlannedLedgerEvent(metadata) {
|
|
|
51461
52007
|
const architectureLedger = metadata.architectureLedger;
|
|
51462
52008
|
if (architectureLedger === undefined)
|
|
51463
52009
|
return;
|
|
51464
|
-
if (!
|
|
52010
|
+
if (!isJsonRecord3(architectureLedger))
|
|
51465
52011
|
throw new Error("changeset-ledger-recovery-metadata-malformed");
|
|
51466
52012
|
const plannedEvent = architectureLedger.plannedEvent;
|
|
51467
52013
|
if (plannedEvent === undefined)
|
|
51468
52014
|
return;
|
|
51469
|
-
if (!
|
|
52015
|
+
if (!isJsonRecord3(plannedEvent))
|
|
51470
52016
|
throw new Error("changeset-ledger-recovery-planned-event-malformed");
|
|
51471
52017
|
const event = plannedEvent;
|
|
51472
52018
|
try {
|
|
@@ -51476,7 +52022,7 @@ function changeSetJournalPlannedLedgerEvent(metadata) {
|
|
|
51476
52022
|
}
|
|
51477
52023
|
return event;
|
|
51478
52024
|
}
|
|
51479
|
-
function
|
|
52025
|
+
function isJsonRecord3(value) {
|
|
51480
52026
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51481
52027
|
}
|
|
51482
52028
|
function recoverJournalFiles(root, files) {
|
|
@@ -59359,11 +59905,13 @@ class ArchctxDaemon {
|
|
|
59359
59905
|
return okEnvelope("status", status);
|
|
59360
59906
|
const repositoryId = repositoryFingerprint(root);
|
|
59361
59907
|
const session = this.sessions.get(repositoryId);
|
|
59908
|
+
const ignoredModelPaths = ignoredArchitectureModelPaths(root);
|
|
59362
59909
|
return okEnvelope("status", {
|
|
59363
59910
|
...status,
|
|
59364
59911
|
repositoryId,
|
|
59365
59912
|
headSha: session?.workspace.headSha ?? readHeadSha(root),
|
|
59366
|
-
worktreeDigest: computeWorktreeDigest(root, listGitVisibleFiles(root))
|
|
59913
|
+
worktreeDigest: computeWorktreeDigest(root, listGitVisibleFiles(root)),
|
|
59914
|
+
...ignoredModelPaths.length === 0 ? {} : { architectureModelIgnoredPaths: ignoredModelPaths }
|
|
59367
59915
|
});
|
|
59368
59916
|
} catch (error) {
|
|
59369
59917
|
throw publicOperationFailure(error);
|
|
@@ -60078,26 +60626,19 @@ function exportLikeC4Model(model) {
|
|
|
60078
60626
|
const normalized = normalizeNativeModel(model);
|
|
60079
60627
|
const lines = [
|
|
60080
60628
|
"// Generated by ArchContext. Native model remains source of truth.",
|
|
60081
|
-
|
|
60082
|
-
" element capability",
|
|
60083
|
-
" element module",
|
|
60084
|
-
" element component",
|
|
60085
|
-
" element interface",
|
|
60086
|
-
" element datastore",
|
|
60087
|
-
" element external_system",
|
|
60088
|
-
"}",
|
|
60629
|
+
...likeC4SpecificationLines(normalized.nodes),
|
|
60089
60630
|
"",
|
|
60090
60631
|
"model {"
|
|
60091
60632
|
];
|
|
60092
60633
|
for (const node of normalized.nodes) {
|
|
60093
60634
|
lines.push(` // archctx-node ${JSON.stringify(node)}`);
|
|
60094
|
-
lines.push(` ${likec4Id(node.id)} = ${
|
|
60635
|
+
lines.push(` ${likec4Id(node.id)} = ${likeC4ElementKind(node.kind)} "${escapeDsl2(node.name)}"`);
|
|
60095
60636
|
}
|
|
60096
60637
|
for (const relation of normalized.relations) {
|
|
60097
60638
|
lines.push(` // archctx-relation ${JSON.stringify(relation)}`);
|
|
60098
60639
|
lines.push(` ${likec4Id(relation.source)} -> ${likec4Id(relation.target)} "${escapeDsl2(relation.kind)}"`);
|
|
60099
60640
|
}
|
|
60100
|
-
lines.push("}", "",
|
|
60641
|
+
lines.push("}", "", ...likeC4IndexViewLines(architectureLayerGroups(normalized), likec4Id));
|
|
60101
60642
|
const content = `${lines.join(`
|
|
60102
60643
|
`)}
|
|
60103
60644
|
`;
|
|
@@ -60127,15 +60668,6 @@ function importLikeC4InitialModel(content) {
|
|
|
60127
60668
|
function likec4Id(id) {
|
|
60128
60669
|
return stableId(id).replace(/-/g, "_").replace(/\./g, "_").replace(/::/g, "__");
|
|
60129
60670
|
}
|
|
60130
|
-
function likec4Kind(kind) {
|
|
60131
|
-
if (kind === "external-system")
|
|
60132
|
-
return "external_system";
|
|
60133
|
-
if (kind === "datastore")
|
|
60134
|
-
return "datastore";
|
|
60135
|
-
if (["capability", "module", "component", "interface"].includes(kind))
|
|
60136
|
-
return kind;
|
|
60137
|
-
return "component";
|
|
60138
|
-
}
|
|
60139
60671
|
function escapeDsl2(value) {
|
|
60140
60672
|
return value.replace(/"/g, "'");
|
|
60141
60673
|
}
|
|
@@ -60144,17 +60676,22 @@ function escapeDsl2(value) {
|
|
|
60144
60676
|
init_src();
|
|
60145
60677
|
function exportStructurizrWorkspace(model, name = "ArchContext Export") {
|
|
60146
60678
|
const normalized = normalizeNativeModel(model);
|
|
60679
|
+
const { layerByNodeId } = architectureLayerGroups(normalized);
|
|
60147
60680
|
const workspace = {
|
|
60148
60681
|
schemaVersion: "archcontext.structurizr-export/v1",
|
|
60149
60682
|
name,
|
|
60150
60683
|
model: {
|
|
60151
|
-
elements: normalized.nodes.map((node) =>
|
|
60152
|
-
|
|
60153
|
-
|
|
60154
|
-
|
|
60155
|
-
|
|
60156
|
-
|
|
60157
|
-
|
|
60684
|
+
elements: normalized.nodes.map((node) => {
|
|
60685
|
+
const layer = layerByNodeId.get(node.id);
|
|
60686
|
+
return {
|
|
60687
|
+
id: node.id,
|
|
60688
|
+
name: node.name,
|
|
60689
|
+
type: architectureStructurizrElementType(node.kind),
|
|
60690
|
+
tags: [node.kind],
|
|
60691
|
+
...layer === undefined ? {} : { group: layer },
|
|
60692
|
+
archcontextNative: node
|
|
60693
|
+
};
|
|
60694
|
+
}),
|
|
60158
60695
|
relationships: normalized.relations.map((relation) => ({
|
|
60159
60696
|
id: relation.id,
|
|
60160
60697
|
sourceId: relation.source,
|
|
@@ -60198,17 +60735,6 @@ function importStructurizrInitialModel(content) {
|
|
|
60198
60735
|
warnings: ["Structurizr import is initialization-only; Native ArchContext remains source of truth."]
|
|
60199
60736
|
};
|
|
60200
60737
|
}
|
|
60201
|
-
function structurizrElementType(kind) {
|
|
60202
|
-
if (kind === "capability")
|
|
60203
|
-
return "Software System";
|
|
60204
|
-
if (kind === "module")
|
|
60205
|
-
return "Container";
|
|
60206
|
-
if (kind === "datastore")
|
|
60207
|
-
return "Database";
|
|
60208
|
-
if (kind === "external-system")
|
|
60209
|
-
return "External System";
|
|
60210
|
-
return "Component";
|
|
60211
|
-
}
|
|
60212
60738
|
|
|
60213
60739
|
// packages/surfaces/mcp-local/src/tool-schemas.ts
|
|
60214
60740
|
var text2 = { type: "string", minLength: 1 };
|
|
@@ -60782,17 +61308,49 @@ var architecture_node_schema_default2 = {
|
|
|
60782
61308
|
type: "object",
|
|
60783
61309
|
additionalProperties: false,
|
|
60784
61310
|
required: ["schemaVersion", "id", "kind", "name", "status", "summary"],
|
|
61311
|
+
allOf: [
|
|
61312
|
+
{
|
|
61313
|
+
if: { properties: { kind: { const: "namespace" } }, required: ["kind"] },
|
|
61314
|
+
then: { required: ["namespace"] },
|
|
61315
|
+
else: { not: { required: ["namespace"] } }
|
|
61316
|
+
},
|
|
61317
|
+
{
|
|
61318
|
+
if: { properties: { kind: { const: "mount" } }, required: ["kind"] },
|
|
61319
|
+
then: { required: ["mount"] },
|
|
61320
|
+
else: { not: { required: ["mount"] } }
|
|
61321
|
+
}
|
|
61322
|
+
],
|
|
60785
61323
|
properties: {
|
|
60786
61324
|
schemaVersion: { const: "archcontext.node/v2" },
|
|
60787
61325
|
id: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
60788
61326
|
kind: {
|
|
60789
61327
|
type: "string",
|
|
60790
|
-
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment"]
|
|
61328
|
+
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment", "process", "namespace", "mount"]
|
|
60791
61329
|
},
|
|
60792
61330
|
name: { type: "string" },
|
|
60793
61331
|
status: { type: "string", enum: ["active", "planned", "deprecated", "removed"] },
|
|
60794
61332
|
parent: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
61333
|
+
layer: { type: "string", pattern: "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" },
|
|
60795
61334
|
summary: { type: "string" },
|
|
61335
|
+
namespace: {
|
|
61336
|
+
type: "object",
|
|
61337
|
+
additionalProperties: false,
|
|
61338
|
+
required: ["type"],
|
|
61339
|
+
properties: {
|
|
61340
|
+
type: { type: "string", enum: ["host", "container", "pod", "kubernetes", "pid", "mount", "net", "user"] }
|
|
61341
|
+
}
|
|
61342
|
+
},
|
|
61343
|
+
mount: {
|
|
61344
|
+
type: "object",
|
|
61345
|
+
additionalProperties: false,
|
|
61346
|
+
required: ["target", "mode", "order"],
|
|
61347
|
+
properties: {
|
|
61348
|
+
source: { type: "string", minLength: 1 },
|
|
61349
|
+
target: { type: "string", pattern: "^/([^/\\u0000]+(/[^/\\u0000]+)*)?$" },
|
|
61350
|
+
mode: { type: "string", enum: ["ro", "rw"] },
|
|
61351
|
+
order: { type: "integer", minimum: 0 }
|
|
61352
|
+
}
|
|
61353
|
+
},
|
|
60796
61354
|
responsibilities: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
60797
61355
|
source: {
|
|
60798
61356
|
type: "object",
|