yarramate 0.20.0 → 0.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/visual/protocol-contract.d.ts +13 -1
- package/dist/adapters/visual/protocol-contract.js +1 -1
- package/dist/adapters/visual/protocol.d.ts +11 -0
- package/dist/adapters/visual/protocol.js +12 -0
- package/dist/adapters/visual/request.js +1 -3
- package/dist/adapters/visual/session-server.js +55 -1
- package/dist/adapters/visual/wire.d.ts +7 -0
- package/dist/adapters/visual-graph-entry.d.ts +1 -0
- package/dist/adapters/visual-graph-entry.js +1 -0
- package/dist/apply-command.js +132 -13
- package/dist/compiler.d.ts +1 -19
- package/dist/compiler.js +2 -35
- package/dist/graph-claims.d.ts +18 -0
- package/dist/graph-claims.js +35 -0
- package/dist/graph-projection.d.ts +4 -3
- package/dist/graph-projection.js +1 -1
- package/dist/notation/archimate.d.ts +71 -0
- package/dist/notation/archimate.js +183 -0
- package/dist/operations.d.ts +18 -0
- package/dist/subject-references.d.ts +92 -0
- package/dist/subject-references.js +226 -0
- package/dist/visual-app/assets/index-CRd1Khma.js +354 -0
- package/dist/visual-app/assets/{index-dwA9AOr5.css → index-tZSHlU-a.css} +1 -1
- package/dist/visual-app/index.html +2 -2
- package/docs/CONSUMING-YARRAMATE.md +26 -1
- package/package.json +9 -1
- package/schema/yarramate-apply-result.schema.json +4 -0
- package/schema/yarramate-operations.schema.json +54 -3
- package/schema/yarramate-visual-event.schema.json +11 -1
- package/schema/yarramate-visual-graph.schema.json +27 -2
- package/schema/yarramate-visual-session-descriptor.schema.json +1 -1
- package/schema/yarramate-visual-session-started.schema.json +1 -1
- package/schema/yarramate-visual-status.schema.json +1 -1
- package/dist/visual-app/assets/index-6oSR9RGM.js +0 -354
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import type { CanvasGraph } from "../../graph-projection.js";
|
|
12
12
|
import type { YarramateApplyResult, YarramateOperation } from "../../operations.js";
|
|
13
13
|
import type { ProjectionDefinition, ProjectionQuery } from "../../projection.js";
|
|
14
|
-
export declare const VISUAL_PROTOCOL_VERSION: "yarramate/visual-protocol/
|
|
14
|
+
export declare const VISUAL_PROTOCOL_VERSION: "yarramate/visual-protocol/v3";
|
|
15
15
|
export declare const VISUAL_LIMITS: {
|
|
16
16
|
readonly messageBytes: number;
|
|
17
17
|
readonly modelBytes: number;
|
|
@@ -124,6 +124,18 @@ export interface VisualLayoutPositions {
|
|
|
124
124
|
}
|
|
125
125
|
export interface VisualChangesetCommitPayload {
|
|
126
126
|
readonly operations: readonly YarramateOperation[];
|
|
127
|
+
/**
|
|
128
|
+
* What the browser believed each targeted document held when the rows were
|
|
129
|
+
* staged — sha256 keyed by manifest-relative path, pinned at staging time and
|
|
130
|
+
* never refreshed while rows remain staged. The runtime refuses the batch when
|
|
131
|
+
* a pin no longer matches the file, so a same-field overwrite of a write the
|
|
132
|
+
* reviewer never saw cannot land silently (ADR 0093).
|
|
133
|
+
*
|
|
134
|
+
* Required, not optional: a browser that omits it is exactly the browser that
|
|
135
|
+
* cannot detect the conflict, which is why this field is what makes the
|
|
136
|
+
* protocol `v3`.
|
|
137
|
+
*/
|
|
138
|
+
readonly sourceDigests: Readonly<Record<string, string>>;
|
|
127
139
|
}
|
|
128
140
|
export interface VisualLayoutSavePayload {
|
|
129
141
|
readonly projectionId: string;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { type ParseResult, type VisualBrowserInput, type VisualDiagnosticResult, type VisualEvent, type VisualHandoff, type VisualModel, type VisualResponse, type VisualSessionDescriptor, type VisualSessionRequest, type VisualSessionStarted, type VisualStatus } from './protocol-contract.js';
|
|
2
2
|
export * from './protocol-contract.js';
|
|
3
|
+
/**
|
|
4
|
+
* The one way this adapter mints a source digest.
|
|
5
|
+
*
|
|
6
|
+
* `visual-model/v1` requires a canonical model to record the digests it was
|
|
7
|
+
* derived from (`YMVS112`) and pins their shape to 64 lowercase hex characters,
|
|
8
|
+
* so the value lives beside the validator that enforces it: the request builder
|
|
9
|
+
* mints them for the initial model, the session server re-mints them on every
|
|
10
|
+
* recompile and checks a commit's pins against the files on disk, and all three
|
|
11
|
+
* are the same hash by construction rather than by three matching literals.
|
|
12
|
+
*/
|
|
13
|
+
export declare const digestOf: (source: string) => string;
|
|
3
14
|
export declare const parseVisualModel: (input: unknown) => ParseResult<VisualModel>;
|
|
4
15
|
export declare const parseVisualSessionRequest: (input: unknown) => ParseResult<VisualSessionRequest>;
|
|
5
16
|
export declare const parseVisualSessionStarted: (input: unknown) => ParseResult<VisualSessionStarted>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { posix } from 'node:path';
|
|
2
3
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
3
4
|
import { describeSchemaViolation, readableSchemaErrors, } from '../../source-document.js';
|
|
@@ -57,6 +58,17 @@ ajv.addSchema([
|
|
|
57
58
|
operationsSchema,
|
|
58
59
|
applyResultSchema,
|
|
59
60
|
]);
|
|
61
|
+
/**
|
|
62
|
+
* The one way this adapter mints a source digest.
|
|
63
|
+
*
|
|
64
|
+
* `visual-model/v1` requires a canonical model to record the digests it was
|
|
65
|
+
* derived from (`YMVS112`) and pins their shape to 64 lowercase hex characters,
|
|
66
|
+
* so the value lives beside the validator that enforces it: the request builder
|
|
67
|
+
* mints them for the initial model, the session server re-mints them on every
|
|
68
|
+
* recompile and checks a commit's pins against the files on disk, and all three
|
|
69
|
+
* are the same hash by construction rather than by three matching literals.
|
|
70
|
+
*/
|
|
71
|
+
export const digestOf = (source) => createHash('sha256').update(source).digest('hex');
|
|
60
72
|
// Diagnostics report the document they came from rather than a source file,
|
|
61
73
|
// because visual protocol documents arrive as parsed JSON over the wire.
|
|
62
74
|
const documentPaths = new WeakMap();
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { readFileSync } from 'node:fs';
|
|
3
2
|
import { resolve } from 'node:path';
|
|
4
3
|
import { loadProjection } from '../../projection.js';
|
|
5
4
|
import { loadWorkspaceManifest } from '../../workspace.js';
|
|
6
|
-
import { parseVisualSessionRequest } from './protocol.js';
|
|
5
|
+
import { digestOf, parseVisualSessionRequest } from './protocol.js';
|
|
7
6
|
import { buildVisualModelGraph } from './session-store.js';
|
|
8
7
|
/**
|
|
9
8
|
* The one manifest a session can serve. `startVisualServer` resolves exactly
|
|
@@ -21,7 +20,6 @@ const requestDiagnostic = (code, message, path = MANIFEST_PATH) => ({
|
|
|
21
20
|
line: 1,
|
|
22
21
|
column: 1,
|
|
23
22
|
});
|
|
24
|
-
const digestOf = (source) => createHash('sha256').update(source).digest('hex');
|
|
25
23
|
/**
|
|
26
24
|
* Builds the `yarramate/visual-session-request/v1` document that `start`
|
|
27
25
|
* consumes, from the workspace on disk.
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import Ajv2020Module from "ajv/dist/2020.js";
|
|
8
8
|
import { WebSocketServer } from "ws";
|
|
9
9
|
import { parse, stringify } from "yaml";
|
|
10
|
-
import { VISUAL_LIMITS, VISUAL_PROTOCOL_VERSION, parseVisualBrowserInput, parseVisualResponse, parseVisualSessionStarted, parseVisualStatus, visualBrowserInputType, } from "./protocol.js";
|
|
10
|
+
import { VISUAL_LIMITS, VISUAL_PROTOCOL_VERSION, digestOf, parseVisualBrowserInput, parseVisualResponse, parseVisualSessionStarted, parseVisualStatus, visualBrowserInputType, } from "./protocol.js";
|
|
11
11
|
import { appendTerminalEvent, appendVisualEvent, appendVisualResponse, createVisualSession, isActionableVisualEvent, recoverVisualSession, removeVisualSession, writeVisualSessionDescriptor, } from "./session-store.js";
|
|
12
12
|
import { loadProjection, evaluateProjection, } from "../../projection.js";
|
|
13
13
|
import { compileWorkspaceWithProfileContext, } from "../../compiler.js";
|
|
@@ -415,6 +415,7 @@ export const startVisualServer = async (options) => {
|
|
|
415
415
|
documents: [],
|
|
416
416
|
vocabulary: { conceptKinds: [], relationshipKinds: [] },
|
|
417
417
|
layouts,
|
|
418
|
+
sourceDigests: request.initialModel.sourceDigests,
|
|
418
419
|
};
|
|
419
420
|
const capabilities = {
|
|
420
421
|
chat: request.chatEnabled,
|
|
@@ -470,6 +471,9 @@ export const startVisualServer = async (options) => {
|
|
|
470
471
|
documents: resolvedWorkspace.documents,
|
|
471
472
|
vocabulary: { conceptKinds, relationshipKinds },
|
|
472
473
|
layouts: rendered.layouts,
|
|
474
|
+
// Minted from the bytes this compile just read, so what the browser
|
|
475
|
+
// renders and what it can later claim it rendered are the same read.
|
|
476
|
+
sourceDigests: Object.fromEntries(sources.map(({ path, source }) => [path, digestOf(source)])),
|
|
473
477
|
};
|
|
474
478
|
return true;
|
|
475
479
|
}
|
|
@@ -1055,6 +1059,56 @@ export const startVisualServer = async (options) => {
|
|
|
1055
1059
|
// the agent anything, so it is answered here directly rather than
|
|
1056
1060
|
// through the pending queue a poll would drain. This never runs
|
|
1057
1061
|
// `git commit` - the user reverts a landed batch with `git revert`.
|
|
1062
|
+
// A batch states what it expected each document it touches to hold, and
|
|
1063
|
+
// that expectation is checked against the files before anything is
|
|
1064
|
+
// written. Without it `applyOperations` below would read the workspace
|
|
1065
|
+
// at commit time and do exactly as told, so a row staged against a
|
|
1066
|
+
// value some other writer has since replaced overwrites that writer
|
|
1067
|
+
// silently - the one path left where this adapter loses a write it
|
|
1068
|
+
// reports as landed (ADR 0093).
|
|
1069
|
+
//
|
|
1070
|
+
// Every targeted document that exists is checked, not just every pin
|
|
1071
|
+
// sent: a batch that vouches for nothing would otherwise buy back the
|
|
1072
|
+
// unconditional write by omission, and a precondition nobody has to
|
|
1073
|
+
// state is decoration.
|
|
1074
|
+
const pins = event.payload.sourceDigests;
|
|
1075
|
+
const refused = [];
|
|
1076
|
+
for (const path of new Set(event.payload.operations.map((operation) => operation.document))) {
|
|
1077
|
+
let held;
|
|
1078
|
+
try {
|
|
1079
|
+
held = digestOf(readFileSync(resolve(options.cwd, path), "utf8"));
|
|
1080
|
+
}
|
|
1081
|
+
catch {
|
|
1082
|
+
// Not there to read: `apply` creates it, or something removed it.
|
|
1083
|
+
held = undefined;
|
|
1084
|
+
}
|
|
1085
|
+
const pinned = pins[path];
|
|
1086
|
+
if (held === undefined) {
|
|
1087
|
+
if (pinned !== undefined) {
|
|
1088
|
+
refused.push(serverDiagnostic("YMVS312", `Document "${path}" no longer exists; these edits were staged against it`));
|
|
1089
|
+
}
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
if (pinned === undefined) {
|
|
1093
|
+
refused.push(serverDiagnostic("YMVS313", `Document "${path}" is edited without stating what it held when the edit was staged`));
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (pinned !== held) {
|
|
1097
|
+
refused.push(serverDiagnostic("YMVS312", `Document "${path}" changed after these edits were staged`));
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (refused.length > 0) {
|
|
1101
|
+
// Preserve-and-refresh: the rows stay staged in the browser exactly as
|
|
1102
|
+
// a refused apply already leaves them, and the fresh model follows so
|
|
1103
|
+
// the reviewer re-reads the value before deciding what to do with it.
|
|
1104
|
+
sendFrame(socket, {
|
|
1105
|
+
kind: "apply-result",
|
|
1106
|
+
result: { ok: false, diagnostics: refused },
|
|
1107
|
+
});
|
|
1108
|
+
if (recompileWorkspace())
|
|
1109
|
+
broadcast({ kind: "model", model: rendered });
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1058
1112
|
const operationsSource = stringify({
|
|
1059
1113
|
format: "yarramate/operations/v1",
|
|
1060
1114
|
operations: event.payload.operations,
|
|
@@ -25,6 +25,13 @@ export interface VisualRenderedModel {
|
|
|
25
25
|
readonly layouts: {
|
|
26
26
|
readonly [projectionId: string]: VisualLayoutPositions;
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* The sha256 of every workspace source this graph was compiled from, keyed by
|
|
30
|
+
* manifest-relative path — the same map `visual-model/v1` already requires of
|
|
31
|
+
* a canonical model (`YMVS112`), forwarded rather than dropped so the browser
|
|
32
|
+
* can state what it rendered when it asks for a commit.
|
|
33
|
+
*/
|
|
34
|
+
readonly sourceDigests: Readonly<Record<string, string>>;
|
|
28
35
|
}
|
|
29
36
|
/**
|
|
30
37
|
* One line of the conversation, as plain text.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { projectGraphForCanvas, type CanvasGraph, type CanvasNode, type CanvasEdge, } from '../graph-projection.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { projectGraphForCanvas, } from '../graph-projection.js';
|
package/dist/apply-command.js
CHANGED
|
@@ -2,16 +2,19 @@ import { readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { isMap, isScalar, isSeq, parseDocument, stringify, } from 'yaml';
|
|
4
4
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
5
|
+
import { loadAdapterMapping } from './adapter-mapping.js';
|
|
5
6
|
import { diagnosticJson, humanDiagnostics, usage, } from './cli-support.js';
|
|
6
7
|
import { compileWorkspace } from './compiler.js';
|
|
7
8
|
import { evaluateEvidence, loadEvidence } from './evidence.js';
|
|
9
|
+
import { loadProjection } from './projection.js';
|
|
8
10
|
import { loadSourceDocument, locateSourcePath, } from './source-document.js';
|
|
11
|
+
import { declaredStateIds, rewriteSubjectReferences, scanSubjectReferences, } from './subject-references.js';
|
|
9
12
|
import { loadWorkspaceManifest } from './workspace.js';
|
|
10
13
|
import operationsSchema from '../schema/yarramate-operations.schema.json' with { type: 'json'
|
|
11
14
|
};
|
|
12
15
|
const Ajv2020 = Ajv2020Module.default;
|
|
13
16
|
// `discriminator` routes a batch entry to the single branch its `op` names, so
|
|
14
|
-
// one malformed operation reports one fault instead of
|
|
17
|
+
// one malformed operation reports one fault instead of ten near-misses.
|
|
15
18
|
const validateOperations = new Ajv2020({
|
|
16
19
|
allErrors: true,
|
|
17
20
|
discriminator: true,
|
|
@@ -302,6 +305,18 @@ export const applyOperations = (operations, workspace, cwd) => {
|
|
|
302
305
|
// overlay — or an observation aimed at a compiler document — is rejected
|
|
303
306
|
// before anything is touched.
|
|
304
307
|
const workspaceEvidence = new Map(resolvedWorkspace.evidence.map((path) => [resolve(cwd, path), path]));
|
|
308
|
+
// A rename re-points references, and references live in four kinds of file,
|
|
309
|
+
// so the write set is wider than the two above. Projections and adapter
|
|
310
|
+
// mappings are never an operation's own target — they are only ever carried
|
|
311
|
+
// along by a rename — but they are written, so they carry their manifest
|
|
312
|
+
// path for the touched-document list and their group for the walker.
|
|
313
|
+
const referenceFiles = [
|
|
314
|
+
['document', resolvedWorkspace.documents],
|
|
315
|
+
['projection', resolvedWorkspace.projections],
|
|
316
|
+
['evidence', resolvedWorkspace.evidence],
|
|
317
|
+
['adapter-mapping', resolvedWorkspace.adapterMappings],
|
|
318
|
+
].flatMap(([group, paths]) => paths.map((path) => ({ absolute: resolve(cwd, path), path, group })));
|
|
319
|
+
const referenceFileOf = new Map(referenceFiles.map((file) => [file.absolute, file]));
|
|
305
320
|
const candidates = new Map();
|
|
306
321
|
const counts = {
|
|
307
322
|
addedConcepts: 0,
|
|
@@ -310,14 +325,19 @@ export const applyOperations = (operations, workspace, cwd) => {
|
|
|
310
325
|
updatedRelationships: 0,
|
|
311
326
|
deletedConcepts: 0,
|
|
312
327
|
deletedRelationships: 0,
|
|
328
|
+
renamedConcepts: 0,
|
|
329
|
+
renamedRelationships: 0,
|
|
313
330
|
addedObservations: 0,
|
|
314
331
|
updatedObservations: 0,
|
|
315
332
|
deletedObservations: 0,
|
|
316
333
|
};
|
|
317
334
|
const deletions = [];
|
|
318
|
-
|
|
335
|
+
// Addresses this batch moved off, so the residue walk below can prove none of
|
|
336
|
+
// them survived anywhere.
|
|
337
|
+
const renames = [];
|
|
338
|
+
const locateOperation = (index, message, code = 'YM912') => ({
|
|
319
339
|
severity: 'error',
|
|
320
|
-
code
|
|
340
|
+
code,
|
|
321
341
|
message,
|
|
322
342
|
...locateSourcePath(operationsPath, yaml, lineCounter, ['operations', index, 'document'], `/operations/${index}/document`),
|
|
323
343
|
});
|
|
@@ -365,6 +385,72 @@ export const applyOperations = (operations, workspace, cwd) => {
|
|
|
365
385
|
counts.deletedRelationships += 1;
|
|
366
386
|
}
|
|
367
387
|
}
|
|
388
|
+
else if (operation.op === 'rename-concept' ||
|
|
389
|
+
operation.op === 'rename-relationship') {
|
|
390
|
+
const collection = operation.op === 'rename-concept' ? 'concepts' : 'relationships';
|
|
391
|
+
const id = operation.op === 'rename-concept'
|
|
392
|
+
? operation.concept.id
|
|
393
|
+
: operation.relationship.id;
|
|
394
|
+
if (itemMap(source, collection, id) === undefined) {
|
|
395
|
+
return failed([
|
|
396
|
+
locate(`Operation ${index} renames "${id}", which does not exist in ${operation.document}`),
|
|
397
|
+
]);
|
|
398
|
+
}
|
|
399
|
+
// A rename that does not move the address would report every reference to
|
|
400
|
+
// it as residue below, which reads as a rewrite fault rather than what it
|
|
401
|
+
// is. Nothing would be written either, so `renamedConcepts: 1` over an
|
|
402
|
+
// empty document list would be a false receipt.
|
|
403
|
+
if (operation.to === id) {
|
|
404
|
+
return failed([
|
|
405
|
+
locate(`Operation ${index} renames "${id}" to itself, so no address moves`),
|
|
406
|
+
]);
|
|
407
|
+
}
|
|
408
|
+
// A state shares the `document#local` spelling with a subject but not the
|
|
409
|
+
// id space. A collision on either end would make one address name two
|
|
410
|
+
// things, so it is refused rather than re-pointed by guess.
|
|
411
|
+
const states = declaredStateIds(source);
|
|
412
|
+
const collision = states.includes(id)
|
|
413
|
+
? id
|
|
414
|
+
: states.includes(operation.to)
|
|
415
|
+
? operation.to
|
|
416
|
+
: undefined;
|
|
417
|
+
if (collision !== undefined) {
|
|
418
|
+
return failed([
|
|
419
|
+
locate(`Operation ${index} renames "${id}" to "${operation.to}", but ${operation.document} declares a state "${collision}" — one address would name two things`),
|
|
420
|
+
]);
|
|
421
|
+
}
|
|
422
|
+
const { documentId } = scanSubjectReferences(source, 'document');
|
|
423
|
+
const rename = {
|
|
424
|
+
from: `${documentId}#${id}`,
|
|
425
|
+
to: `${documentId}#${operation.to}`,
|
|
426
|
+
};
|
|
427
|
+
// Total within the workspace: the declaration and every declarative
|
|
428
|
+
// reference to it move in this one batch, so nothing is left addressing an
|
|
429
|
+
// id that stopped existing. Staged text is the input, so a second rename
|
|
430
|
+
// in the same batch reads the first one's result.
|
|
431
|
+
for (const file of referenceFiles) {
|
|
432
|
+
const before = candidates.get(file.absolute) ?? readFileSync(file.absolute, 'utf8');
|
|
433
|
+
const rewrite = rewriteSubjectReferences(before, file.group, rename);
|
|
434
|
+
if (!rewrite.ok) {
|
|
435
|
+
return failed([
|
|
436
|
+
locate(`Operation ${index} cannot move "${rename.from}": ${file.path} holds ${rewrite.aliases.length === 1 ? 'an alias' : 'aliases'} at ${rewrite.aliases.join(', ')}, which the rewrite cannot re-point`),
|
|
437
|
+
]);
|
|
438
|
+
}
|
|
439
|
+
if (rewrite.source !== before) {
|
|
440
|
+
candidates.set(file.absolute, rewrite.source);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
// The target document's own declaration moved in that same walk, so the
|
|
444
|
+
// staged text is the authority from here on.
|
|
445
|
+
source = candidates.get(absolute) ?? source;
|
|
446
|
+
renames.push({ index, from: rename.from });
|
|
447
|
+
if (operation.op === 'rename-concept') {
|
|
448
|
+
counts.renamedConcepts += 1;
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
counts.renamedRelationships += 1;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
368
454
|
else if (operation.op === 'add-observation') {
|
|
369
455
|
const address = observationAddress(operation.observation);
|
|
370
456
|
const matches = byObservation(operation.observation);
|
|
@@ -604,11 +690,50 @@ export const applyOperations = (operations, workspace, cwd) => {
|
|
|
604
690
|
if (!evaluation.ok)
|
|
605
691
|
return failed(evaluation.diagnostics);
|
|
606
692
|
}
|
|
693
|
+
// Totality is checked, not trusted: no file this batch touched may still name
|
|
694
|
+
// an address a rename moved off. A splice that landed text re-parsing to the
|
|
695
|
+
// old value refuses here rather than shipping a reference to an id that
|
|
696
|
+
// stopped existing. A position the enumeration omits is invisible to this
|
|
697
|
+
// walk - the schema-derived completeness test is what covers that.
|
|
698
|
+
if (renames.length > 0) {
|
|
699
|
+
const movedFrom = new Map(renames.map(({ from, index }) => [from, index]));
|
|
700
|
+
const residue = referenceFiles.flatMap((file) => {
|
|
701
|
+
const source = candidates.get(file.absolute);
|
|
702
|
+
if (source === undefined)
|
|
703
|
+
return [];
|
|
704
|
+
return scanSubjectReferences(source, file.group)
|
|
705
|
+
.hits.filter((hit) => movedFrom.has(hit.address))
|
|
706
|
+
.map((hit) => {
|
|
707
|
+
const index = movedFrom.get(hit.address);
|
|
708
|
+
return locateOperation(index, `Operation ${index} moved "${hit.address}", but ${file.path} still names it at ${hit.pointer}`, 'YM913');
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
if (residue.length > 0)
|
|
712
|
+
return failed(residue);
|
|
713
|
+
}
|
|
714
|
+
// Projections and adapter mappings are not `compileWorkspace` input, so a
|
|
715
|
+
// rewrite that produced an unreadable address is caught here rather than by
|
|
716
|
+
// the next command to read the file.
|
|
717
|
+
for (const file of referenceFiles) {
|
|
718
|
+
const source = candidates.get(file.absolute);
|
|
719
|
+
if (source === undefined)
|
|
720
|
+
continue;
|
|
721
|
+
if (file.group === 'projection') {
|
|
722
|
+
const loaded = loadProjection({ path: file.path, source });
|
|
723
|
+
if (!loaded.ok)
|
|
724
|
+
return failed(loaded.diagnostics);
|
|
725
|
+
}
|
|
726
|
+
else if (file.group === 'adapter-mapping') {
|
|
727
|
+
const loaded = loadAdapterMapping({ path: file.path, source });
|
|
728
|
+
if (!loaded.ok)
|
|
729
|
+
return failed(loaded.diagnostics);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
607
732
|
for (const [absolute, source] of candidates) {
|
|
608
733
|
writeFileSync(absolute, source, 'utf8');
|
|
609
734
|
}
|
|
610
735
|
const touched = [...candidates.keys()]
|
|
611
|
-
.map((absolute) =>
|
|
736
|
+
.map((absolute) => referenceFileOf.get(absolute).path)
|
|
612
737
|
.sort();
|
|
613
738
|
return {
|
|
614
739
|
ok: true,
|
|
@@ -658,15 +783,9 @@ export function runApplyCommand(options, cwd) {
|
|
|
658
783
|
stderr: '',
|
|
659
784
|
};
|
|
660
785
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
result.applied.updatedRelationships +
|
|
665
|
-
result.applied.deletedConcepts +
|
|
666
|
-
result.applied.deletedRelationships +
|
|
667
|
-
result.applied.addedObservations +
|
|
668
|
-
result.applied.updatedObservations +
|
|
669
|
-
result.applied.deletedObservations;
|
|
786
|
+
// Every counter, summed by iteration rather than by hand, so a new
|
|
787
|
+
// operation kind cannot silently report zero work.
|
|
788
|
+
const applied = Object.values(result.applied).reduce((total, count) => total + count, 0);
|
|
670
789
|
return {
|
|
671
790
|
exitCode: 0,
|
|
672
791
|
stdout: `Applied ${applied} operation${applied === 1 ? '' : 's'} to ${result.documents.join(', ')}\n`,
|
package/dist/compiler.d.ts
CHANGED
|
@@ -108,24 +108,7 @@ export type IncrementalCompilationResult = ({
|
|
|
108
108
|
readonly incremental: boolean;
|
|
109
109
|
readonly cache: CompilationCache;
|
|
110
110
|
};
|
|
111
|
-
export
|
|
112
|
-
export declare const attestationClaimValue: (attestation: {
|
|
113
|
-
readonly by: string;
|
|
114
|
-
readonly on: string;
|
|
115
|
-
readonly recordedBy?: string;
|
|
116
|
-
}) => string;
|
|
117
|
-
export interface AttestationClaimParts {
|
|
118
|
-
readonly by: string;
|
|
119
|
-
readonly on: string;
|
|
120
|
-
readonly recordedBy?: string;
|
|
121
|
-
}
|
|
122
|
-
export declare const parseAttestationClaimValue: (value: string) => AttestationClaimParts | undefined;
|
|
123
|
-
export interface ConstraintExpectsParts {
|
|
124
|
-
readonly provider: string;
|
|
125
|
-
readonly key: string;
|
|
126
|
-
readonly value: string;
|
|
127
|
-
}
|
|
128
|
-
export declare const parseConstraintExpectsValue: (value: string) => ConstraintExpectsParts | undefined;
|
|
111
|
+
export { ATTESTATION_PREDICATE_PREFIX, attestationClaimValue, parseAttestationClaimValue, parseConstraintExpectsValue, type AttestationClaimParts, type ConstraintExpectsParts, } from './graph-claims.js';
|
|
129
112
|
interface ResolvedPosition {
|
|
130
113
|
readonly line: number;
|
|
131
114
|
readonly col: number;
|
|
@@ -142,4 +125,3 @@ export declare const compileWorkspaceWithProfileContext: (sources: readonly Work
|
|
|
142
125
|
* composed value per current source and drops sources that left the workspace.
|
|
143
126
|
*/
|
|
144
127
|
export declare const compileWorkspaceIncremental: (sources: readonly WorkspaceSource[], previous?: CompilationCache) => IncrementalCompilationResult;
|
|
145
|
-
export {};
|
package/dist/compiler.js
CHANGED
|
@@ -6,6 +6,7 @@ import documentSchema from '../schema/yarramate-document.schema.json' with { typ
|
|
|
6
6
|
};
|
|
7
7
|
import profileSchema from '../schema/yarramate-profile.schema.json' with { type: 'json'
|
|
8
8
|
};
|
|
9
|
+
import { ATTESTATION_PREDICATE_PREFIX, attestationClaimValue } from './graph-claims.js';
|
|
9
10
|
const coreProfile = 'yarramate/core@0.1';
|
|
10
11
|
const require = createRequire(import.meta.url);
|
|
11
12
|
const ajv2020Module = require('ajv/dist/2020.js');
|
|
@@ -39,41 +40,7 @@ const presenceClaimId = (subject, state) => `${subject}~present-in-${Buffer.from
|
|
|
39
40
|
const aliasClaimId = (subject, alias) => `${subject}~alias-${Buffer.from(alias, 'utf8').toString('hex')}`;
|
|
40
41
|
const distinctFromClaimId = (subject, other) => `${subject}~distinct-from-${Buffer.from(other, 'utf8').toString('hex')}`;
|
|
41
42
|
const supersedesClaimId = (subject, predecessor) => `${subject}~supersedes-${Buffer.from(predecessor, 'utf8').toString('hex')}`;
|
|
42
|
-
export
|
|
43
|
-
// An attestation claim packs the authority, the date it was given, and
|
|
44
|
-
// the recorder when a machine held the pen. A reference carries no
|
|
45
|
-
// spaces and the date is fixed width, so the three parse back out of one
|
|
46
|
-
// value unambiguously wherever a reader needs them.
|
|
47
|
-
export const attestationClaimValue = (attestation) => attestation.recordedBy === undefined
|
|
48
|
-
? `${attestation.by} ${attestation.on}`
|
|
49
|
-
: `${attestation.by} ${attestation.on} ${attestation.recordedBy}`;
|
|
50
|
-
export const parseAttestationClaimValue = (value) => {
|
|
51
|
-
const match = /^(\S+) ([0-9]{4}-[0-9]{2}-[0-9]{2})(?: (.+))?$/.exec(value);
|
|
52
|
-
if (match === null)
|
|
53
|
-
return undefined;
|
|
54
|
-
const recordedBy = match[3];
|
|
55
|
-
return {
|
|
56
|
-
by: match[1],
|
|
57
|
-
on: match[2],
|
|
58
|
-
...(recordedBy === undefined ? {} : { recordedBy }),
|
|
59
|
-
};
|
|
60
|
-
};
|
|
61
|
-
// Mirrors the compiler's own write-side encoding (ADR 0075): provider and
|
|
62
|
-
// key admit no whitespace, so the first two spaces delimit them and
|
|
63
|
-
// everything after the second space is the expected value verbatim, spaces
|
|
64
|
-
// included. This is the sole authority for decoding the value written at
|
|
65
|
-
// the constraint's `expects` claim — reconciliation.ts delegates here
|
|
66
|
-
// rather than mirroring the regex itself.
|
|
67
|
-
export const parseConstraintExpectsValue = (value) => {
|
|
68
|
-
const match = /^(\S+) (\S+) ([\s\S]+)$/.exec(value);
|
|
69
|
-
if (match === null)
|
|
70
|
-
return undefined;
|
|
71
|
-
return {
|
|
72
|
-
provider: match[1],
|
|
73
|
-
key: match[2],
|
|
74
|
-
value: match[3],
|
|
75
|
-
};
|
|
76
|
-
};
|
|
43
|
+
export { ATTESTATION_PREDICATE_PREFIX, attestationClaimValue, parseAttestationClaimValue, parseConstraintExpectsValue, } from './graph-claims.js';
|
|
77
44
|
const describeAspect = (aspect) => aspect.replace('-', ' ');
|
|
78
45
|
// Candidate order is the policy-matrix declaration order: the resolved kind
|
|
79
46
|
// map inserts core policies first, then extension kinds as declared.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const ATTESTATION_PREDICATE_PREFIX = "yarramate/attestation/";
|
|
2
|
+
export declare const attestationClaimValue: (attestation: {
|
|
3
|
+
readonly by: string;
|
|
4
|
+
readonly on: string;
|
|
5
|
+
readonly recordedBy?: string;
|
|
6
|
+
}) => string;
|
|
7
|
+
export interface AttestationClaimParts {
|
|
8
|
+
readonly by: string;
|
|
9
|
+
readonly on: string;
|
|
10
|
+
readonly recordedBy?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const parseAttestationClaimValue: (value: string) => AttestationClaimParts | undefined;
|
|
13
|
+
export interface ConstraintExpectsParts {
|
|
14
|
+
readonly provider: string;
|
|
15
|
+
readonly key: string;
|
|
16
|
+
readonly value: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const parseConstraintExpectsValue: (value: string) => ConstraintExpectsParts | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const ATTESTATION_PREDICATE_PREFIX = 'yarramate/attestation/';
|
|
2
|
+
// An attestation claim packs the authority, the date it was given, and
|
|
3
|
+
// the recorder when a machine held the pen. A reference carries no
|
|
4
|
+
// spaces and the date is fixed width, so the three parse back out of one
|
|
5
|
+
// value unambiguously wherever a reader needs them.
|
|
6
|
+
export const attestationClaimValue = (attestation) => attestation.recordedBy === undefined
|
|
7
|
+
? `${attestation.by} ${attestation.on}`
|
|
8
|
+
: `${attestation.by} ${attestation.on} ${attestation.recordedBy}`;
|
|
9
|
+
export const parseAttestationClaimValue = (value) => {
|
|
10
|
+
const match = /^(\S+) ([0-9]{4}-[0-9]{2}-[0-9]{2})(?: (.+))?$/.exec(value);
|
|
11
|
+
if (match === null)
|
|
12
|
+
return undefined;
|
|
13
|
+
const recordedBy = match[3];
|
|
14
|
+
return {
|
|
15
|
+
by: match[1],
|
|
16
|
+
on: match[2],
|
|
17
|
+
...(recordedBy === undefined ? {} : { recordedBy }),
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
// Mirrors the compiler's own write-side encoding (ADR 0075): provider and
|
|
21
|
+
// key admit no whitespace, so the first two spaces delimit them and
|
|
22
|
+
// everything after the second space is the expected value verbatim, spaces
|
|
23
|
+
// included. This is the sole authority for decoding the value written at
|
|
24
|
+
// the constraint's `expects` claim — reconciliation.ts delegates here
|
|
25
|
+
// rather than mirroring the regex itself.
|
|
26
|
+
export const parseConstraintExpectsValue = (value) => {
|
|
27
|
+
const match = /^(\S+) (\S+) ([\s\S]+)$/.exec(value);
|
|
28
|
+
if (match === null)
|
|
29
|
+
return undefined;
|
|
30
|
+
return {
|
|
31
|
+
provider: match[1],
|
|
32
|
+
key: match[2],
|
|
33
|
+
value: match[3],
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ResolvedProfileContext, SemanticGraph } from './compiler.js';
|
|
2
|
+
import type { Aspect, Layer } from './profile.js';
|
|
2
3
|
export interface CanvasNode {
|
|
3
4
|
readonly id: string;
|
|
4
5
|
readonly localId: string;
|
|
5
6
|
readonly document: string;
|
|
6
7
|
readonly kind: string;
|
|
7
8
|
readonly kindLabel: string;
|
|
8
|
-
readonly layer:
|
|
9
|
-
readonly aspect:
|
|
9
|
+
readonly layer: Layer | null;
|
|
10
|
+
readonly aspect: Aspect | null;
|
|
10
11
|
readonly name: string;
|
|
11
12
|
readonly description: string | null;
|
|
12
13
|
readonly aka: readonly string[];
|
package/dist/graph-projection.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ATTESTATION_PREDICATE_PREFIX, parseAttestationClaimValue, parseConstraintExpectsValue, } from './
|
|
1
|
+
import { ATTESTATION_PREDICATE_PREFIX, parseAttestationClaimValue, parseConstraintExpectsValue, } from './graph-claims.js';
|
|
2
2
|
import { kindLabelOf } from './kind-label.js';
|
|
3
3
|
const CONCEPT_KIND_PREDICATE = 'yarramate/concept/kind';
|
|
4
4
|
const CONCEPT_NAME_PREDICATE = 'yarramate/concept/name';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { layers, type Aspect, type Layer, type RelationshipKind } from '../profile.js';
|
|
2
|
+
export declare const ICON_SIZE = 14;
|
|
3
|
+
export declare const LAYER_COLORS: {
|
|
4
|
+
readonly motivation: {
|
|
5
|
+
readonly fill: '#CCCCFF';
|
|
6
|
+
readonly border: '#8F8FE0';
|
|
7
|
+
};
|
|
8
|
+
readonly strategy: {
|
|
9
|
+
readonly fill: '#F5DEAA';
|
|
10
|
+
readonly border: '#C9A355';
|
|
11
|
+
};
|
|
12
|
+
readonly business: {
|
|
13
|
+
readonly fill: '#FFFF99';
|
|
14
|
+
readonly border: '#C9C355';
|
|
15
|
+
};
|
|
16
|
+
readonly application: {
|
|
17
|
+
readonly fill: '#CCFFFF';
|
|
18
|
+
readonly border: '#4FB8B8';
|
|
19
|
+
};
|
|
20
|
+
readonly technology: {
|
|
21
|
+
readonly fill: '#CCFFCC';
|
|
22
|
+
readonly border: '#5FAE5F';
|
|
23
|
+
};
|
|
24
|
+
readonly implementation: {
|
|
25
|
+
readonly fill: '#FFE0E0';
|
|
26
|
+
readonly border: '#D89999';
|
|
27
|
+
};
|
|
28
|
+
readonly physical: {
|
|
29
|
+
readonly fill: '#F0F0F0';
|
|
30
|
+
readonly border: '#999999';
|
|
31
|
+
};
|
|
32
|
+
readonly composite: {
|
|
33
|
+
readonly fill: '#F0F0F0';
|
|
34
|
+
readonly border: '#999999';
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export interface ShapeMeta {
|
|
38
|
+
readonly shape: 'rectangle' | 'round-rectangle' | 'octagon';
|
|
39
|
+
/** Passive-structure's ArchiMate header-stripe convention. */
|
|
40
|
+
readonly accent?: 'top-band';
|
|
41
|
+
/** Composite (grouping/location/junction) convention. */
|
|
42
|
+
readonly borderStyle?: 'dashed';
|
|
43
|
+
}
|
|
44
|
+
export declare const ASPECT_SHAPES: Record<Aspect, ShapeMeta>;
|
|
45
|
+
export interface ConceptNotation extends ShapeMeta {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly notation: string;
|
|
48
|
+
readonly layer: Layer;
|
|
49
|
+
readonly aspect: Aspect;
|
|
50
|
+
readonly glyph: string | null;
|
|
51
|
+
readonly colors: {
|
|
52
|
+
readonly fill: string;
|
|
53
|
+
readonly border: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export declare const CONCEPT_NOTATION: readonly ConceptNotation[];
|
|
57
|
+
export declare function conceptNotationOf(kindLabel: string): ConceptNotation | null;
|
|
58
|
+
export declare function kindGlyphDataUriOf(kindLabel: string): string | null;
|
|
59
|
+
export interface ArrowNotation {
|
|
60
|
+
readonly shape: 'none' | 'diamond' | 'triangle' | 'circle' | 'vee';
|
|
61
|
+
readonly fill?: 'filled' | 'hollow';
|
|
62
|
+
}
|
|
63
|
+
export interface RelationshipNotation {
|
|
64
|
+
readonly id: RelationshipKind;
|
|
65
|
+
readonly lineStyle: 'solid' | 'dotted' | 'dashed';
|
|
66
|
+
readonly sourceArrow: ArrowNotation;
|
|
67
|
+
readonly targetArrow: ArrowNotation;
|
|
68
|
+
}
|
|
69
|
+
export declare const RELATIONSHIP_NOTATION: readonly RelationshipNotation[];
|
|
70
|
+
export declare function relationshipNotationOf(kindLabel: string): RelationshipNotation | null;
|
|
71
|
+
export { layers };
|