yarramate 1.20.0 → 1.22.0
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/dist/adapters/likec4-prepare.js +1 -1
- package/dist/adapters/visual/protocol-contract.d.ts +15 -0
- package/dist/adapters/visual/session-server.js +12 -6
- package/dist/adapters/visual/workspace-model.d.ts +4 -4
- package/dist/adapters/visual/workspace-model.js +17 -5
- package/dist/ask-command.js +2 -2
- package/dist/check-command.js +12 -1
- package/dist/export-command.js +4 -1
- package/dist/fold-tree.d.ts +21 -8
- package/dist/fold-tree.js +118 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/projection.d.ts +63 -4
- package/dist/projection.js +179 -9
- package/dist/schema-validators.generated.js +346 -264
- package/dist/subject-references.js +8 -0
- package/dist/visual-app/assets/index-C0nYrzna.js +395 -0
- package/dist/visual-app/index.html +1 -1
- package/dist/visual-app-lib/editor.js +27981 -27699
- package/dist/visual-app-lib/types/adapters/visual/protocol-contract.d.ts +15 -0
- package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +4 -4
- package/dist/visual-app-lib/types/fold-tree.d.ts +21 -8
- package/dist/visual-app-lib/types/projection.d.ts +63 -4
- package/dist/visual-app-lib/types/visual-app/constraint-rows.d.ts +70 -0
- package/dist/visual-app-lib/types/visual-app/context-menu-model.d.ts +23 -0
- package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +13 -2
- package/dist/visual-app-lib/types/visual-app/query-fields.d.ts +3 -2
- package/dist/visual-app-lib/types/visual-app/query-panel.d.ts +10 -3
- package/dist/visual-app-lib/types/visual-app/session-client.d.ts +7 -1
- package/dist/visual-app-lib/types/visual-app/state.d.ts +2 -0
- package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +8 -1
- package/package.json +1 -1
- package/schema/yarramate-projection.schema.json +13 -0
- package/dist/visual-app/assets/index-EegLnUfX.js +0 -394
|
@@ -141,7 +141,7 @@ export function prepareLikeC4Export(input) {
|
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
-
const projectionResult = evaluateProjection(compilation.graph, projection.projection, compilation.profileContext);
|
|
144
|
+
const projectionResult = evaluateProjection(compilation.graph, projection.projection, compilation.profileContext, compilation.patternMemberships);
|
|
145
145
|
const comparison = input.comparison === undefined
|
|
146
146
|
? undefined
|
|
147
147
|
: compareArchitectureStates(compilation.graph, input.comparison.from, input.comparison.to);
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* `node:path`, or the schema documents into its bundle.
|
|
10
10
|
*/
|
|
11
11
|
import type { CanvasGraph } from "../../graph-projection.js";
|
|
12
|
+
import type { NestingKind } from "../../nesting.js";
|
|
12
13
|
import type { YarramateApplyResult, YarramateOperation } from "../../operations.js";
|
|
13
14
|
import type { ProjectionDefinition, ProjectionExclusion, ProjectionQuery } from "../../projection.js";
|
|
14
15
|
/**
|
|
@@ -163,6 +164,20 @@ export interface VisualKindOption {
|
|
|
163
164
|
}
|
|
164
165
|
export interface VisualFilterQueryPayload {
|
|
165
166
|
readonly query: ProjectionQuery;
|
|
167
|
+
/**
|
|
168
|
+
* The nesting the canvas is drawing with, when the browser knows it (#473
|
|
169
|
+
* phase 2).
|
|
170
|
+
*
|
|
171
|
+
* Only `query.instances` reads it, and it must: the closure of an instance IS
|
|
172
|
+
* the containment tree, so evaluating it under a different nesting than the
|
|
173
|
+
* canvas answers a different question. On the ApertureX reference that is 15
|
|
174
|
+
* subjects against 2.
|
|
175
|
+
*
|
|
176
|
+
* Optional, so an older browser and every filter that names no instance keep
|
|
177
|
+
* working unchanged; absent, the evaluator falls back to the default nesting
|
|
178
|
+
* exactly as it did before.
|
|
179
|
+
*/
|
|
180
|
+
readonly nesting?: readonly NestingKind[];
|
|
166
181
|
}
|
|
167
182
|
export interface VisualFilterResultPayload {
|
|
168
183
|
readonly query: ProjectionQuery;
|
|
@@ -690,9 +690,15 @@ export const startVisualServer = async (options) => {
|
|
|
690
690
|
if (!started.ok)
|
|
691
691
|
standingDiagnostics = started.diagnostics;
|
|
692
692
|
}
|
|
693
|
-
const filterMatchedIds = (query) => compiledWorkspace === undefined
|
|
693
|
+
const filterMatchedIds = (query, nesting) => compiledWorkspace === undefined
|
|
694
694
|
? []
|
|
695
|
-
: matchedIdsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext
|
|
695
|
+
: matchedIdsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext,
|
|
696
|
+
// A view's query can name `instances`, and the facet resolves to the
|
|
697
|
+
// instance alone without these (ADR 0144).
|
|
698
|
+
compiledWorkspace.patternMemberships,
|
|
699
|
+
// And it resolves the WRONG closure without the nesting the canvas is
|
|
700
|
+
// drawing with, which is a wrong number rather than a missing one.
|
|
701
|
+
nesting);
|
|
696
702
|
/**
|
|
697
703
|
* Why a query dropped what it dropped, as the editor's "excluded, and why"
|
|
698
704
|
* list reads it (#248).
|
|
@@ -704,9 +710,9 @@ export const startVisualServer = async (options) => {
|
|
|
704
710
|
* the reason the editor shows and the set the canvas draws cannot come from
|
|
705
711
|
* two readings of the same query.
|
|
706
712
|
*/
|
|
707
|
-
const filterExclusions = (query) => compiledWorkspace === undefined
|
|
713
|
+
const filterExclusions = (query, nesting) => compiledWorkspace === undefined
|
|
708
714
|
? []
|
|
709
|
-
: exclusionsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext);
|
|
715
|
+
: exclusionsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext, compiledWorkspace.patternMemberships, nesting);
|
|
710
716
|
let listening = false;
|
|
711
717
|
let bootstrapSpent = false;
|
|
712
718
|
let agentAttached = false;
|
|
@@ -1243,8 +1249,8 @@ export const startVisualServer = async (options) => {
|
|
|
1243
1249
|
kind: "filter-result",
|
|
1244
1250
|
result: {
|
|
1245
1251
|
query: event.payload.query,
|
|
1246
|
-
matchedIds: filterMatchedIds(event.payload.query),
|
|
1247
|
-
excluded: filterExclusions(event.payload.query),
|
|
1252
|
+
matchedIds: filterMatchedIds(event.payload.query, event.payload.nesting),
|
|
1253
|
+
excluded: filterExclusions(event.payload.query, event.payload.nesting),
|
|
1248
1254
|
},
|
|
1249
1255
|
});
|
|
1250
1256
|
return;
|
|
@@ -2,7 +2,7 @@ import type { Diagnostic } from "../../compiler.js";
|
|
|
2
2
|
import type { PendingWrite, SourceStore } from "../../source-store.js";
|
|
3
3
|
import type { ResolvedWorkspace } from "../../workspace.js";
|
|
4
4
|
import type { VisualDiagnostic, VisualViewOperation } from "./protocol-contract.js";
|
|
5
|
-
import type { ProjectionDefinition, ProjectionExclusion, ProjectionQuery } from "../../projection.js";
|
|
5
|
+
import type { NestingKind, ProjectionDefinition, ProjectionExclusion, ProjectionQuery } from "../../projection.js";
|
|
6
6
|
import type { ResolvedProfileContext, SemanticGraph } from "../../compiler.js";
|
|
7
7
|
import { type CataloguePatternMembership, type CataloguePatternVacancy } from "../../interrogate-command.js";
|
|
8
8
|
import type { VisualKindOption, VisualViewSummary } from "./protocol-contract.js";
|
|
@@ -41,7 +41,7 @@ export declare const kindOptionsOf: (lineages: ReadonlyMap<string, readonly stri
|
|
|
41
41
|
* subject count. A view over three components with two relationships between
|
|
42
42
|
* them would read as five, and the reviewer counting boxes would find three.
|
|
43
43
|
*/
|
|
44
|
-
export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext) => number;
|
|
44
|
+
export declare const conceptCountOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => number;
|
|
45
45
|
/**
|
|
46
46
|
* Folds one interrogation report into what the canvas draws (#292).
|
|
47
47
|
*
|
|
@@ -109,9 +109,9 @@ export declare const renderedWorkspaceOf: (compiled: {
|
|
|
109
109
|
readonly views: readonly VisualViewSummary[];
|
|
110
110
|
};
|
|
111
111
|
/** Every subject a query draws, concepts and relationships alike. */
|
|
112
|
-
export declare const matchedIdsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext) => readonly string[];
|
|
112
|
+
export declare const matchedIdsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => readonly string[];
|
|
113
113
|
/** Every concept a query dropped, and the facet that dropped it (#248). */
|
|
114
|
-
export declare const exclusionsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext) => readonly ProjectionExclusion[];
|
|
114
|
+
export declare const exclusionsOf: (graph: SemanticGraph, query: ProjectionQuery, profileContext: ResolvedProfileContext, memberships?: readonly CataloguePatternMembership[], nesting?: readonly NestingKind[]) => readonly ProjectionExclusion[];
|
|
115
115
|
/**
|
|
116
116
|
* One saved view, as the rail reads it. `subjectCount` is the caller's,
|
|
117
117
|
* because counting needs a compiled graph and a session builds its first list
|
|
@@ -45,7 +45,7 @@ export const kindOptionsOf = (lineages) => [...lineages.keys()].map((id) => ({
|
|
|
45
45
|
* subject count. A view over three components with two relationships between
|
|
46
46
|
* them would read as five, and the reviewer counting boxes would find three.
|
|
47
47
|
*/
|
|
48
|
-
export const conceptCountOf = (graph, query, profileContext) => evaluateProjection(graph, adHoc(query), profileContext).subjects.filter(({ type }) => type === "concept").length;
|
|
48
|
+
export const conceptCountOf = (graph, query, profileContext, memberships, nesting) => evaluateProjection(graph, adHoc(query, nesting), profileContext, memberships).subjects.filter(({ type }) => type === "concept").length;
|
|
49
49
|
export const interrogationOverlayOf = (compiled,
|
|
50
50
|
/**
|
|
51
51
|
* The catalogue, or the composed SET a workspace carries (#345, ADR 0129).
|
|
@@ -118,7 +118,14 @@ dismissed = []) => {
|
|
|
118
118
|
export const renderedWorkspaceOf = (compiled, views, metadata, catalogue, dismissed) => {
|
|
119
119
|
const refreshedViews = views.map((view) => ({
|
|
120
120
|
...view,
|
|
121
|
-
subjectCount: conceptCountOf(compiled.graph, view.query, compiled.profileContext
|
|
121
|
+
subjectCount: conceptCountOf(compiled.graph, view.query, compiled.profileContext,
|
|
122
|
+
// Without these an `instances` view counts 1 and the rail says so, which
|
|
123
|
+
// is a wrong number rather than a missing one (ADR 0144).
|
|
124
|
+
compiled.patternMemberships,
|
|
125
|
+
// Each view's OWN nesting, because each view's closure is its own. A rail
|
|
126
|
+
// sitting beside the canvas must not count a different tree than the
|
|
127
|
+
// canvas draws.
|
|
128
|
+
view.presentation?.nesting),
|
|
122
129
|
}));
|
|
123
130
|
const interrogation = catalogue === undefined
|
|
124
131
|
? undefined
|
|
@@ -148,18 +155,23 @@ export const renderedWorkspaceOf = (compiled, views, metadata, catalogue, dismis
|
|
|
148
155
|
};
|
|
149
156
|
};
|
|
150
157
|
/** Every subject a query draws, concepts and relationships alike. */
|
|
151
|
-
export const matchedIdsOf = (graph, query, profileContext) => evaluateProjection(graph, adHoc(query), profileContext).subjects.map(({ id }) => id);
|
|
158
|
+
export const matchedIdsOf = (graph, query, profileContext, memberships, nesting) => evaluateProjection(graph, adHoc(query, nesting), profileContext, memberships).subjects.map(({ id }) => id);
|
|
152
159
|
/** Every concept a query dropped, and the facet that dropped it (#248). */
|
|
153
|
-
export const exclusionsOf = (graph, query, profileContext) => explainProjection(graph, adHoc(query), profileContext);
|
|
160
|
+
export const exclusionsOf = (graph, query, profileContext, memberships, nesting) => explainProjection(graph, adHoc(query, nesting), profileContext, memberships);
|
|
154
161
|
/**
|
|
155
162
|
* A query on its own is not a projection, and every evaluator here wants one.
|
|
156
163
|
* The id is a placeholder that never reaches a document.
|
|
157
164
|
*/
|
|
158
|
-
const adHoc = (query) => ({
|
|
165
|
+
const adHoc = (query, nesting) => ({
|
|
159
166
|
format: "yarramate/projection/v1",
|
|
160
167
|
id: "ad-hoc",
|
|
161
168
|
version: "0",
|
|
162
169
|
query,
|
|
170
|
+
// `query.instances` resolves its closure through the view's nesting, so an
|
|
171
|
+
// ad-hoc projection that dropped the nesting would answer a DIFFERENT
|
|
172
|
+
// question than the canvas is drawing: 2 subjects against 15 on the
|
|
173
|
+
// ApertureX reference, with nothing to say it had (#473 phase 2).
|
|
174
|
+
...(nesting === undefined ? {} : { presentation: { nesting } }),
|
|
163
175
|
});
|
|
164
176
|
/**
|
|
165
177
|
* One saved view, as the rail reads it. `subjectCount` is the caller's,
|
package/dist/ask-command.js
CHANGED
|
@@ -784,7 +784,7 @@ export function runAskCommand(options, cwd) {
|
|
|
784
784
|
});
|
|
785
785
|
if (!loaded.ok)
|
|
786
786
|
continue;
|
|
787
|
-
const membership = evaluateProjection(graph, loaded.projection, compilation.profileContext);
|
|
787
|
+
const membership = evaluateProjection(graph, loaded.projection, compilation.profileContext, compilation.patternMemberships);
|
|
788
788
|
for (const subject of membership.subjects) {
|
|
789
789
|
covered.add(subject.id);
|
|
790
790
|
}
|
|
@@ -857,7 +857,7 @@ export function runAskCommand(options, cwd) {
|
|
|
857
857
|
});
|
|
858
858
|
if (!loaded.ok)
|
|
859
859
|
return failed(loaded.diagnostics);
|
|
860
|
-
const evaluated = evaluateProjection(graph, loaded.projection, compilation.profileContext);
|
|
860
|
+
const evaluated = evaluateProjection(graph, loaded.projection, compilation.profileContext, compilation.patternMemberships);
|
|
861
861
|
const result = {
|
|
862
862
|
format: 'yarramate/ask-result/v1',
|
|
863
863
|
workspace: workspace.id,
|
package/dist/check-command.js
CHANGED
|
@@ -214,7 +214,18 @@ export function runCheckCommand(options, cwd) {
|
|
|
214
214
|
// does not build would bury the real failure under its consequences.
|
|
215
215
|
const referenceDiagnostics = result.ok
|
|
216
216
|
? loadedProjections.flatMap(({ source, loaded }) => loaded.ok
|
|
217
|
-
? projectionReferenceDiagnostics(source, loaded.projection, result.graph, result.profileContext
|
|
217
|
+
? projectionReferenceDiagnostics(source, loaded.projection, result.graph, result.profileContext,
|
|
218
|
+
// Instance-hood from BOTH lists. A membership row exists only
|
|
219
|
+
// for a BOUND slot, so an instance whose slots are all empty
|
|
220
|
+
// has none - and judging it by bindings alone would call a real
|
|
221
|
+
// instance "not an instance" on the day it was authored, before
|
|
222
|
+
// anything was wired into it. The honest question is not "did
|
|
223
|
+
// it bind anything" but "does the model know it as an
|
|
224
|
+
// instance" (rule 2).
|
|
225
|
+
new Set([
|
|
226
|
+
...(result.patternMemberships ?? []).map(({ instance }) => instance),
|
|
227
|
+
...(result.patternVacancies ?? []).map(({ instance }) => instance),
|
|
228
|
+
]))
|
|
218
229
|
: [])
|
|
219
230
|
: [];
|
|
220
231
|
const optionalDiagnostics = sortDiagnostics([
|
package/dist/export-command.js
CHANGED
|
@@ -250,7 +250,10 @@ export function runExportCommand(options, cwd) {
|
|
|
250
250
|
});
|
|
251
251
|
if (!loadedProjection.ok)
|
|
252
252
|
return failed(loadedProjection.diagnostics);
|
|
253
|
-
result = evaluateProjection(compilation.graph, loadedProjection.projection, compilation.profileContext
|
|
253
|
+
result = evaluateProjection(compilation.graph, loadedProjection.projection, compilation.profileContext,
|
|
254
|
+
// An AUTHORED projection can name `instances`, and the facet resolves
|
|
255
|
+
// to the instance alone without these (ADR 0144).
|
|
256
|
+
compilation.patternMemberships);
|
|
254
257
|
}
|
|
255
258
|
if (kind === 'xlsx') {
|
|
256
259
|
// A workbook an architect can work in (#355). It takes a PROJECTION,
|
package/dist/fold-tree.d.ts
CHANGED
|
@@ -110,15 +110,28 @@ export declare function nestingTree(edges: readonly FoldEdge[], nesting: readonl
|
|
|
110
110
|
/**
|
|
111
111
|
* The containment tree: what a view nests, plus what a pattern owns.
|
|
112
112
|
*
|
|
113
|
-
* A slot member joins the tree only when all
|
|
114
|
-
* a different way of getting the answer wrong:
|
|
113
|
+
* A slot member joins the tree only when all of these hold, and each condition
|
|
114
|
+
* is a different way of getting the answer wrong:
|
|
115
115
|
*
|
|
116
|
-
* - **
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* the
|
|
120
|
-
*
|
|
121
|
-
*
|
|
116
|
+
* - **Held inside one box.** A member's HOLDERS are every instance whose slots
|
|
117
|
+
* name it. One holder puts the member in that holder. Several put it in their
|
|
118
|
+
* lowest common ancestor, which is the level at which the holders diverge and
|
|
119
|
+
* therefore the innermost box that contains all of them. Holders with no
|
|
120
|
+
* common ancestor leave the member outside, because there is no one box it
|
|
121
|
+
* sits within and a single-parent tree would have to pick.
|
|
122
|
+
*
|
|
123
|
+
* This AMENDS ADR 0143's "Exclusive" rule, which kept every shared subject
|
|
124
|
+
* outside (#473 phase 3, ADR 0145, Nabeel's decision of 2026-09-05). The
|
|
125
|
+
* original reasoning was that two owners force a silent choice; it is only
|
|
126
|
+
* true when the owners sit in different boxes. Where both already sit under
|
|
127
|
+
* one box there is nothing to choose, and the old rule left 14 of the
|
|
128
|
+
* reference Landscape's 30 data objects outside the single application whose
|
|
129
|
+
* own parts were the things binding them.
|
|
130
|
+
* - **`owned` or `unwired`, never `context` alone.** A context slot names
|
|
131
|
+
* something the instance USES and does not contain — the upstream API it
|
|
132
|
+
* calls, the plane it runs on. Folding those would swallow half the landscape
|
|
133
|
+
* into whichever box happened to reference it. At least one binding must be
|
|
134
|
+
* `owned` or `unwired` for the member to fold at all.
|
|
122
135
|
* - **Not a ruling.** See {@link RULING_CORE_KINDS}.
|
|
123
136
|
*
|
|
124
137
|
* A view's own nesting wins where both apply: the view is the more specific
|
package/dist/fold-tree.js
CHANGED
|
@@ -127,6 +127,18 @@ export function nestingTree(edges, nesting, coreKindOf) {
|
|
|
127
127
|
* a straight-line ancestor of a cycle is still validly nested under its own
|
|
128
128
|
* non-cyclic parent. Mutates `parentOf` and returns what it removed.
|
|
129
129
|
*/
|
|
130
|
+
/** Whether `id` sits anywhere inside `ancestor` in the tree built so far. */
|
|
131
|
+
const isDescendantOf = (id, ancestor, parentOf) => {
|
|
132
|
+
const seen = new Set([id]);
|
|
133
|
+
let current = parentOf.get(id);
|
|
134
|
+
while (current !== undefined && !seen.has(current)) {
|
|
135
|
+
if (current === ancestor)
|
|
136
|
+
return true;
|
|
137
|
+
seen.add(current);
|
|
138
|
+
current = parentOf.get(current);
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
};
|
|
130
142
|
function unnestCycles(parentOf) {
|
|
131
143
|
const cycleMembers = new Set();
|
|
132
144
|
for (const start of parentOf.keys()) {
|
|
@@ -149,18 +161,59 @@ function unnestCycles(parentOf) {
|
|
|
149
161
|
parentOf.delete(id);
|
|
150
162
|
return [...cycleMembers];
|
|
151
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* The lowest node that contains every one of `ids`, counting each id as an
|
|
166
|
+
* ancestor of itself, or `undefined` when they do not share one.
|
|
167
|
+
*
|
|
168
|
+
* "Counting each id as an ancestor of itself" is the part that matters: where
|
|
169
|
+
* one holder already sits inside another, the answer is the outer holder rather
|
|
170
|
+
* than something above them both.
|
|
171
|
+
*/
|
|
172
|
+
const lowestCommonAncestor = (ids, parentOf) => {
|
|
173
|
+
const chainOf = (id) => {
|
|
174
|
+
const chain = [];
|
|
175
|
+
const seen = new Set();
|
|
176
|
+
let current = id;
|
|
177
|
+
while (current !== undefined && !seen.has(current)) {
|
|
178
|
+
seen.add(current);
|
|
179
|
+
chain.push(current);
|
|
180
|
+
current = parentOf.get(current);
|
|
181
|
+
}
|
|
182
|
+
return chain;
|
|
183
|
+
};
|
|
184
|
+
const [first, ...rest] = ids;
|
|
185
|
+
if (first === undefined)
|
|
186
|
+
return undefined;
|
|
187
|
+
const others = rest.map((id) => new Set(chainOf(id)));
|
|
188
|
+
// Walking the first chain from the node OUTWARDS makes the first hit the
|
|
189
|
+
// lowest by construction.
|
|
190
|
+
return chainOf(first).find((candidate) => others.every((chain) => chain.has(candidate)));
|
|
191
|
+
};
|
|
152
192
|
/**
|
|
153
193
|
* The containment tree: what a view nests, plus what a pattern owns.
|
|
154
194
|
*
|
|
155
|
-
* A slot member joins the tree only when all
|
|
156
|
-
* a different way of getting the answer wrong:
|
|
195
|
+
* A slot member joins the tree only when all of these hold, and each condition
|
|
196
|
+
* is a different way of getting the answer wrong:
|
|
157
197
|
*
|
|
158
|
-
* - **
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* the
|
|
162
|
-
*
|
|
163
|
-
*
|
|
198
|
+
* - **Held inside one box.** A member's HOLDERS are every instance whose slots
|
|
199
|
+
* name it. One holder puts the member in that holder. Several put it in their
|
|
200
|
+
* lowest common ancestor, which is the level at which the holders diverge and
|
|
201
|
+
* therefore the innermost box that contains all of them. Holders with no
|
|
202
|
+
* common ancestor leave the member outside, because there is no one box it
|
|
203
|
+
* sits within and a single-parent tree would have to pick.
|
|
204
|
+
*
|
|
205
|
+
* This AMENDS ADR 0143's "Exclusive" rule, which kept every shared subject
|
|
206
|
+
* outside (#473 phase 3, ADR 0145, Nabeel's decision of 2026-09-05). The
|
|
207
|
+
* original reasoning was that two owners force a silent choice; it is only
|
|
208
|
+
* true when the owners sit in different boxes. Where both already sit under
|
|
209
|
+
* one box there is nothing to choose, and the old rule left 14 of the
|
|
210
|
+
* reference Landscape's 30 data objects outside the single application whose
|
|
211
|
+
* own parts were the things binding them.
|
|
212
|
+
* - **`owned` or `unwired`, never `context` alone.** A context slot names
|
|
213
|
+
* something the instance USES and does not contain — the upstream API it
|
|
214
|
+
* calls, the plane it runs on. Folding those would swallow half the landscape
|
|
215
|
+
* into whichever box happened to reference it. At least one binding must be
|
|
216
|
+
* `owned` or `unwired` for the member to fold at all.
|
|
164
217
|
* - **Not a ruling.** See {@link RULING_CORE_KINDS}.
|
|
165
218
|
*
|
|
166
219
|
* A view's own nesting wins where both apply: the view is the more specific
|
|
@@ -178,22 +231,64 @@ export function foldTree(input) {
|
|
|
178
231
|
else
|
|
179
232
|
instances.add(membership.instance);
|
|
180
233
|
}
|
|
181
|
-
|
|
234
|
+
// Whether ANY of a member's bindings is one the instance holds it out by. A
|
|
235
|
+
// member bound only through context slots never folds, however many hold it.
|
|
236
|
+
const heldOutSomewhere = new Set();
|
|
182
237
|
for (const membership of input.memberships) {
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// A
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
238
|
+
if (membership.wiring !== 'context')
|
|
239
|
+
heldOutSomewhere.add(membership.member);
|
|
240
|
+
}
|
|
241
|
+
const parentOf = new Map(fromNesting.parentOf);
|
|
242
|
+
const candidates = [
|
|
243
|
+
...new Set(input.memberships
|
|
244
|
+
.map(({ member }) => member)
|
|
245
|
+
.filter((member) =>
|
|
246
|
+
// A view's own nesting already placed it, and the view wins.
|
|
247
|
+
!parentOf.has(member) &&
|
|
248
|
+
heldOutSomewhere.has(member) &&
|
|
249
|
+
!RULING_CORE_KINDS.has(coreKindOf(member)) &&
|
|
250
|
+
// A node the input does not carry cannot be drawn inside anything.
|
|
251
|
+
coreKindById.has(member) &&
|
|
252
|
+
// Something that holds itself is not held by anything.
|
|
253
|
+
instancesOf.get(member)?.has(member) !== true)),
|
|
254
|
+
];
|
|
255
|
+
// Resolved in ROUNDS rather than one pass, because a member's holders may
|
|
256
|
+
// themselves be members whose own parents are decided here. Placing a member
|
|
257
|
+
// before its holders are settled would measure the lowest common ancestor
|
|
258
|
+
// against a tree that is still missing the levels that separate them, and the
|
|
259
|
+
// reference has five-deep chains (spec, mapping, call, client, application),
|
|
260
|
+
// so this is exercised rather than theoretical.
|
|
261
|
+
//
|
|
262
|
+
// A member is settled once it is placed or once it is known to stay outside.
|
|
263
|
+
// Whatever a round cannot decide it hands to the next; when a round decides
|
|
264
|
+
// nothing, what is left is a mutual dependency and stays outside, which is the
|
|
265
|
+
// same answer the cycle guard below would reach for it anyway.
|
|
266
|
+
let pending = candidates;
|
|
267
|
+
while (pending.length > 0) {
|
|
268
|
+
const deferred = [];
|
|
269
|
+
let decided = false;
|
|
270
|
+
for (const member of pending) {
|
|
271
|
+
const holders = [...(instancesOf.get(member) ?? [])];
|
|
272
|
+
if (holders.some((holder) => pending.includes(holder) && holder !== member)) {
|
|
273
|
+
deferred.push(member);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
decided = true;
|
|
277
|
+
const parent = holders.length === 1
|
|
278
|
+
? holders[0]
|
|
279
|
+
: lowestCommonAncestor(holders, parentOf);
|
|
280
|
+
if (parent === undefined || parent === member)
|
|
281
|
+
continue;
|
|
282
|
+
// A member that already contains one of its holders cannot also sit
|
|
283
|
+
// inside it. The cycle guard below is the backstop, not the rule.
|
|
284
|
+
if (holders.some((holder) => isDescendantOf(holder, member, parentOf))) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
parentOf.set(member, parent);
|
|
288
|
+
}
|
|
289
|
+
if (!decided)
|
|
290
|
+
break;
|
|
291
|
+
pending = deferred;
|
|
197
292
|
}
|
|
198
293
|
// Slot membership can close a loop the view's nesting alone did not, so the
|
|
199
294
|
// guard runs again over the combined tree rather than trusting the first.
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { deriveAttestationStaleness } from './attestation-staleness.js';
|
|
|
11
11
|
export { deriveArtifactCoverage, type ArtifactCoverage, type CoverageScopePattern, } from './artifact-coverage.js';
|
|
12
12
|
export { buildRtm, renderRtmMarkdown, type RequirementsTraceabilityMatrix, type RtmAttestation, type RtmContextEntry, type RtmDescopedEntry, type RtmEvidenceVerdict, type RtmLineageEntry, type RtmRealizer, type RtmRow, type RtmSource, } from './rtm.js';
|
|
13
13
|
export type { CompilationCache, CompilationResult, ContextualCompilationResult, IncrementalCompilationResult, ParsedWorkspaceSource, PatternMembership, PatternVacancy, Diagnostic, GraphClaim, GraphSource, SemanticGraph, ResolvedProfileContext, WorkspaceSource, } from './compiler.js';
|
|
14
|
-
export { canonicalProjection, evaluateProjection, explainProjection, loadProjection, renderProjectionMarkdown, type ConceptFacet, type ProjectionExclusion, } from './projection.js';
|
|
14
|
+
export { canonicalProjection, evaluateProjection, explainProjection, instanceClosureOf, loadProjection, renderProjectionMarkdown, type ConceptFacet, type ProjectionExclusion, } from './projection.js';
|
|
15
15
|
export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, type AdapterMapping, type AdapterMappingLoadResult, type AdapterMappingValidationResult, type AdapterMappingsValidationResult, type AdapterSubjectMapping, } from './adapter-mapping.js';
|
|
16
16
|
export type { LifecycleStatus, ProjectionDefinition, ProjectionLoadResult, ProjectionResult, } from './projection.js';
|
|
17
17
|
export { createFileSystemStore, type PendingWrite, type SourceStore, type StoredSource, type WriteConflict, type WriteOutcome, } from './source-store.js';
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { constraintExpectsPredicate, reconcileEvidenceReports, } from './reconci
|
|
|
10
10
|
export { deriveAttestationStaleness } from './attestation-staleness.js';
|
|
11
11
|
export { deriveArtifactCoverage, } from './artifact-coverage.js';
|
|
12
12
|
export { buildRtm, renderRtmMarkdown, } from './rtm.js';
|
|
13
|
-
export { canonicalProjection, evaluateProjection, explainProjection, loadProjection, renderProjectionMarkdown, } from './projection.js';
|
|
13
|
+
export { canonicalProjection, evaluateProjection, explainProjection, instanceClosureOf, loadProjection, renderProjectionMarkdown, } from './projection.js';
|
|
14
14
|
export { loadAdapterMapping, validateAdapterMapping, validateAdapterMappings, } from './adapter-mapping.js';
|
|
15
15
|
export { createFileSystemStore, } from './source-store.js';
|
|
16
16
|
export { applyOperations, landOperations, posixDirectoryOf, } from './apply-command.js';
|
package/dist/projection.d.ts
CHANGED
|
@@ -6,6 +6,25 @@ export interface ProjectionDefinition {
|
|
|
6
6
|
readonly version: string;
|
|
7
7
|
readonly query: {
|
|
8
8
|
readonly subjects?: readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* Pattern instances whose CONTENTS this query wants (#473, ADR 0144).
|
|
11
|
+
*
|
|
12
|
+
* Each id names an instance, and the facet selects that instance together
|
|
13
|
+
* with everything the fold tree would draw inside it - the same closure
|
|
14
|
+
* `presentation.fold: instances` collapses into one box, read through the
|
|
15
|
+
* view's own `nesting`. A view that wanted the box wanted what is in it.
|
|
16
|
+
*
|
|
17
|
+
* This is the ONE facet that adds rather than narrows, and it is why
|
|
18
|
+
* `subjects` and this are read as a single identity facet whose values
|
|
19
|
+
* combine with OR (`docs/PROJECTIONS.md`). Every other field still ANDs
|
|
20
|
+
* over the union: `instances` says which subjects are in play, and `kinds`
|
|
21
|
+
* or `statuses` narrow that set the way they always did.
|
|
22
|
+
*
|
|
23
|
+
* Hand-listing the members instead is what this replaces, and the list goes
|
|
24
|
+
* stale the moment the pattern binds another slot. The names here are the
|
|
25
|
+
* instance, not its parts, so the view follows the model.
|
|
26
|
+
*/
|
|
27
|
+
readonly instances?: readonly string[];
|
|
9
28
|
/**
|
|
10
29
|
* Subjects this query would otherwise select and the author has taken out
|
|
11
30
|
* (#267, ADR 0122). A facet view states a rule, and every interesting rule
|
|
@@ -59,6 +78,17 @@ export interface ProjectionDefinition {
|
|
|
59
78
|
readonly showLifecycle?: boolean;
|
|
60
79
|
readonly showEvidence?: boolean;
|
|
61
80
|
readonly showOwnership?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Whether a bound RULING draws as a row inside its holder rather than as a
|
|
83
|
+
* node of its own (#473 phase 3, ADR 0145).
|
|
84
|
+
*
|
|
85
|
+
* On the reference this takes the whole model from 173 boxes to 91, because
|
|
86
|
+
* 82 of its rulings are bound into slots and every one of them was drawing
|
|
87
|
+
* as a box with a single association edge. Presentation only: the model,
|
|
88
|
+
* the query and the selected set do not move, and turning it off restores
|
|
89
|
+
* the boxes.
|
|
90
|
+
*/
|
|
91
|
+
readonly showConstraints?: boolean;
|
|
62
92
|
/**
|
|
63
93
|
* The folder this view files itself under in an editor's rail: a label the
|
|
64
94
|
* author declares, nested with `/`, never the directory the projection
|
|
@@ -90,6 +120,7 @@ export { DEFAULT_NESTING, type NestingKind } from './nesting.js';
|
|
|
90
120
|
*/
|
|
91
121
|
export { DEFAULT_FOLD, type FoldMode } from './fold-tree.js';
|
|
92
122
|
import type { NestingKind } from './nesting.js';
|
|
123
|
+
import { type FoldMembership } from './fold-tree.js';
|
|
93
124
|
/**
|
|
94
125
|
* Which way a view runs, and the default. Split out for the same reason as the
|
|
95
126
|
* nesting vocabulary above, and re-exported here on the same terms (ADR 0121).
|
|
@@ -118,12 +149,40 @@ export declare function canonicalProjection(projection: ProjectionDefinition): P
|
|
|
118
149
|
* A facet of a query, named the way the query names it. What
|
|
119
150
|
* {@link explainProjection} reports as the reason a subject is not in a view.
|
|
120
151
|
*/
|
|
121
|
-
export type ConceptFacet = 'exclude' | 'states' | 'subjects' | 'documents' | 'kinds' | 'layers' | 'statuses' | 'excludeStatuses' | 'owners' | 'constraints';
|
|
152
|
+
export type ConceptFacet = 'exclude' | 'states' | 'subjects' | 'instances' | 'documents' | 'kinds' | 'layers' | 'statuses' | 'excludeStatuses' | 'owners' | 'constraints';
|
|
122
153
|
/** One subject a query dropped, and the facet that dropped it. */
|
|
123
154
|
export interface ProjectionExclusion {
|
|
124
155
|
readonly id: string;
|
|
125
156
|
readonly facet: ConceptFacet;
|
|
126
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* How a query decides about concepts, built once and shared by the two things
|
|
160
|
+
* that ask.
|
|
161
|
+
*
|
|
162
|
+
* `evaluateProjection` asks whether a subject is in; `explainProjection` asks
|
|
163
|
+
* why one is out. They must never be able to disagree, which is why there is
|
|
164
|
+
* one selector rather than a filter here and a reason-finder somewhere else.
|
|
165
|
+
*/
|
|
166
|
+
/**
|
|
167
|
+
* Which subjects each named pattern instance holds, transitively.
|
|
168
|
+
*
|
|
169
|
+
* The SAME tree the canvas folds, computed from the same module over the same
|
|
170
|
+
* inputs, because a view that names an instance must select exactly what the
|
|
171
|
+
* box would have contained. Two implementations of "what is inside this
|
|
172
|
+
* instance" would be two answers to one question, and the one the reader sees
|
|
173
|
+
* is whichever they happened to open (rule: a test must go through the seam it
|
|
174
|
+
* is testing - so must a second caller).
|
|
175
|
+
*
|
|
176
|
+
* Nesting comes from THIS view's `presentation.nesting`, not the default, for
|
|
177
|
+
* the reason folding reads it too: containment is a property of the view, and a
|
|
178
|
+
* view that nests on composition alone holds less than one that also nests on
|
|
179
|
+
* assignment.
|
|
180
|
+
*
|
|
181
|
+
* Without `memberships` the closure is the named instances ALONE. That is a
|
|
182
|
+
* degradation, not an answer, and it is why `check` refuses rather than
|
|
183
|
+
* quietly returning a smaller view (#447, #450).
|
|
184
|
+
*/
|
|
185
|
+
export declare function instanceClosureOf(graph: SemanticGraph, projection: ProjectionDefinition, profileContext?: ResolvedProfileContext, memberships?: readonly FoldMembership[]): ReadonlyMap<string, string>;
|
|
127
186
|
/** A query facet naming something the model does not have. */
|
|
128
187
|
export interface UnmatchedSelector {
|
|
129
188
|
readonly facet: string;
|
|
@@ -170,7 +229,7 @@ export declare function unmatchedSelectors(graph: SemanticGraph, projection: Pro
|
|
|
170
229
|
* caller so `check`, and anything that adopts this later, refuse in identical
|
|
171
230
|
* words with an identical code.
|
|
172
231
|
*/
|
|
173
|
-
export declare function projectionReferenceDiagnostics(source: WorkspaceSource, projection: ProjectionDefinition, graph: SemanticGraph, profileContext?: ResolvedProfileContext): readonly Diagnostic[];
|
|
232
|
+
export declare function projectionReferenceDiagnostics(source: WorkspaceSource, projection: ProjectionDefinition, graph: SemanticGraph, profileContext?: ResolvedProfileContext, instances?: ReadonlySet<string>): readonly Diagnostic[];
|
|
174
233
|
/**
|
|
175
234
|
* Every concept a query leaves out, and the facet that left it out.
|
|
176
235
|
*
|
|
@@ -186,7 +245,7 @@ export declare function projectionReferenceDiagnostics(source: WorkspaceSource,
|
|
|
186
245
|
* `additionalProperties: false` and this is a question about a query rather
|
|
187
246
|
* than part of what a projection IS.
|
|
188
247
|
*/
|
|
189
|
-
export declare function explainProjection(graph: SemanticGraph, projection: ProjectionDefinition, profileContext?: ResolvedProfileContext): readonly ProjectionExclusion[];
|
|
190
|
-
export declare function evaluateProjection(graph: SemanticGraph, projection: ProjectionDefinition, profileContext?: ResolvedProfileContext): ProjectionResult;
|
|
248
|
+
export declare function explainProjection(graph: SemanticGraph, projection: ProjectionDefinition, profileContext?: ResolvedProfileContext, memberships?: readonly FoldMembership[]): readonly ProjectionExclusion[];
|
|
249
|
+
export declare function evaluateProjection(graph: SemanticGraph, projection: ProjectionDefinition, profileContext?: ResolvedProfileContext, memberships?: readonly FoldMembership[]): ProjectionResult;
|
|
191
250
|
export declare function renderProjectionMarkdown(result: ProjectionResult, profileContext?: ResolvedProfileContext): string;
|
|
192
251
|
export declare function renderBudgetedContext(result: ProjectionResult, budgetTokens: number): string;
|