yarramate 1.6.0 → 1.7.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.
Files changed (33) hide show
  1. package/dist/adapters/visual/session-server.js +27 -1
  2. package/dist/adapters/visual/workspace-model.d.ts +16 -3
  3. package/dist/adapters/visual/workspace-model.js +12 -5
  4. package/dist/ask-command.js +25 -16
  5. package/dist/catalogue-sources.d.ts +20 -0
  6. package/dist/catalogue-sources.js +39 -0
  7. package/dist/check-command.js +38 -9
  8. package/dist/cli-support.d.ts +7 -0
  9. package/dist/cli-support.js +4 -0
  10. package/dist/design-command.js +21 -9
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/interrogate-command.d.ts +82 -1
  14. package/dist/interrogate-command.js +195 -29
  15. package/dist/interrogation-entry.d.ts +1 -1
  16. package/dist/interrogation-entry.js +1 -1
  17. package/dist/projection.d.ts +47 -0
  18. package/dist/projection.js +126 -0
  19. package/dist/visual-app-lib/editor.js +26771 -26713
  20. package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +16 -3
  21. package/dist/visual-app-lib/types/interrogate-command.d.ts +82 -1
  22. package/dist/visual-app-lib/types/projection.d.ts +47 -0
  23. package/dist/visual-app-lib/types/workspace.d.ts +15 -0
  24. package/dist/workbook-import-entry.d.ts +30 -0
  25. package/dist/workbook-import-entry.js +30 -0
  26. package/dist/workbook.js +15 -0
  27. package/dist/workspace.d.ts +15 -0
  28. package/dist/workspace.js +1 -0
  29. package/docs/CONSUMING-YARRAMATE.md +63 -0
  30. package/package.json +5 -1
  31. package/schema/yarramate-interrogation-report.schema.json +9 -0
  32. package/schema/yarramate-question-catalogue.schema.json +2 -3
  33. package/schema/yarramate-workspace.schema.json +4 -0
@@ -57,10 +57,20 @@ export interface DismissedQuestion {
57
57
  export declare const interrogationOverlayOf: (compiled: {
58
58
  readonly graph: SemanticGraph;
59
59
  readonly profileContext: ResolvedProfileContext;
60
- }, catalogue: {
60
+ },
61
+ /**
62
+ * The catalogue, or the composed SET a workspace carries (#345, ADR 0129).
63
+ * A set rather than one document so the pane asks the same interview the
64
+ * CLI does over the same workspace: an editor showing fewer questions than
65
+ * `design` does over the same files is a disagreement with no symptom.
66
+ */
67
+ catalogue: {
61
68
  readonly path: string;
62
69
  readonly source: string;
63
- },
70
+ } | readonly {
71
+ readonly path: string;
72
+ readonly source: string;
73
+ }[],
64
74
  /**
65
75
  * What the host has already dealt with (#328). Evaluation is unchanged and
66
76
  * the model is untouched: this decides only what the pane draws, because a
@@ -83,7 +93,10 @@ export declare const renderedWorkspaceOf: (compiled: {
83
93
  }, views: readonly VisualViewSummary[], metadata: Omit<VisualRenderedModel, "graph" | "vocabulary" | "interrogation">, catalogue?: {
84
94
  readonly path: string;
85
95
  readonly source: string;
86
- }, dismissed?: readonly DismissedQuestion[]) => {
96
+ } | readonly {
97
+ readonly path: string;
98
+ readonly source: string;
99
+ }[], dismissed?: readonly DismissedQuestion[]) => {
87
100
  readonly model: VisualRenderedModel;
88
101
  readonly views: readonly VisualViewSummary[];
89
102
  };
@@ -188,14 +188,29 @@ export interface InterrogationSummary {
188
188
  export interface InterrogationReport {
189
189
  readonly format: 'yarramate/interrogation-report/v1';
190
190
  readonly workspace: string;
191
+ /** The BASE catalogue, `id@version`. Unchanged in shape by composition. */
191
192
  readonly catalogue: string;
193
+ /**
194
+ * Every contributing catalogue as `id@version`, base first, when more than
195
+ * one contributed (#345, ADR 0129). Optional so that adding it breaks no
196
+ * constructor, and `catalogue` keeps its value shape so it breaks no reader.
197
+ */
198
+ readonly catalogues?: readonly string[];
192
199
  /** {@link INTERROGATION_SEMANTICS_VERSION} at the time of evaluation. */
193
200
  readonly semantics: string;
194
201
  readonly summary: InterrogationSummary;
195
202
  readonly waves: readonly ReportWave[];
196
203
  }
197
204
  export declare const renderQuestion: (template: string, subjectId: string, subjectName: string | undefined, counterparts?: readonly string[]) => string;
198
- export declare function evaluateCatalogue(catalogue: QuestionCatalogue, graph: SemanticGraph, profileContext?: ResolvedProfileContext, evidence?: readonly CatalogueEvidenceObservation[]): Omit<InterrogationReport, 'workspace'>;
205
+ export declare function evaluateCatalogue(catalogue: QuestionCatalogue, graph: SemanticGraph, profileContext?: ResolvedProfileContext, evidence?: readonly CatalogueEvidenceObservation[],
206
+ /**
207
+ * Contributing catalogues, from {@link composeCatalogues}. Composition
208
+ * happens BEFORE evaluation and hands this function an ordinary catalogue,
209
+ * so the only thing evaluation learns about composition is what to name in
210
+ * the report. A fifth optional parameter rather than an options object,
211
+ * because this signature is published and a consumer already calls it.
212
+ */
213
+ catalogues?: readonly string[]): Omit<InterrogationReport, 'workspace'>;
199
214
  export type CatalogueLoadResult = {
200
215
  readonly ok: true;
201
216
  readonly catalogue: QuestionCatalogue;
@@ -203,5 +218,71 @@ export type CatalogueLoadResult = {
203
218
  readonly ok: false;
204
219
  readonly diagnostics: readonly Diagnostic[];
205
220
  };
221
+ /**
222
+ * A catalogue is qualified on the way OUT, never in what an author writes.
223
+ *
224
+ * The authored schema keeps ids local (`^[a-z][a-z0-9-]*$`) and that does not
225
+ * change; a consultant writes `regulator-signoff`, not
226
+ * `consulting#regulator-signoff`. The engine qualifies when it composes, which
227
+ * is the only moment two catalogues can be confused for each other.
228
+ *
229
+ * NO VERSION, and that is the decision rather than an omission (ADR 0129).
230
+ * `core-enrichment` went 1.0 to 1.3 in a single day renaming nothing; a
231
+ * versioned identity would have stranded every stored dismissal in every
232
+ * adopter's database three times that day, for changes that removed no
233
+ * question. Versioned identity is safe for things that are AUTHORED - a
234
+ * document keeps naming the version it was written against and an author
235
+ * updates it deliberately - and unsafe for things that are STORED, because a
236
+ * row in someone's database has no author to update it.
237
+ */
238
+ export declare const qualifiedQuestionId: (catalogueId: string, questionId: string) => string;
239
+ export interface ComposedCatalogue {
240
+ /**
241
+ * The composed catalogue, ready for `evaluateCatalogue`. Structurally a
242
+ * `QuestionCatalogue`, but its question ids are QUALIFIED, so it is a
243
+ * composition result rather than something an author could have written and
244
+ * must never be validated against the authored schema again.
245
+ */
246
+ readonly catalogue: QuestionCatalogue;
247
+ /** Every contributing catalogue as `id@version`, base first. */
248
+ readonly catalogues: readonly string[];
249
+ }
250
+ export type CatalogueCompositionResult = {
251
+ readonly ok: true;
252
+ readonly composed: ComposedCatalogue;
253
+ } | {
254
+ readonly ok: false;
255
+ readonly diagnostics: readonly Diagnostic[];
256
+ };
257
+ /**
258
+ * Compose a base catalogue with the ones a workspace carries (#345, ADR 0129).
259
+ *
260
+ * ADDITIVE. `--catalogue` and `MountOptions.catalogue` replace the base; this
261
+ * adds to it, which is what lets a consultant author a question at any point
262
+ * in an engagement with no product release.
263
+ *
264
+ * A WAVE IS DECLARED EXACTLY ONCE across the resolved set, and any catalogue
265
+ * may contribute questions to a wave it did not declare. That one rule settles
266
+ * three questions composition would otherwise raise. Wave identity: a project
267
+ * catalogue joins a declared wave rather than colliding with it. Ordering:
268
+ * only a declaration places a wave, so the base's order is untouched and new
269
+ * waves append. And the `opensWhen` precedence ADR 0125 made load-bearing does
270
+ * not arise at all, because there is only ever one declarer to ask.
271
+ *
272
+ * QUALIFICATION IS A PROPERTY OF COMPOSITION, NOT OF EVALUATION. A caller
273
+ * that hands `evaluateCatalogue` a catalogue directly still gets local ids,
274
+ * exactly as before this existed. Route through here even for ONE catalogue -
275
+ * which is what every CLI verb does - and ids are qualified from the start, so
276
+ * they do not change later when a workspace first carries a question of its
277
+ * own. That transition is the one thing an adopter keying stored judgments on
278
+ * a question id must not experience, and composing unconditionally is how it
279
+ * is avoided.
280
+ *
281
+ * ID COLLISIONS DISSOLVE rather than being resolved. Two catalogues may both
282
+ * carry `outcome-missing`; qualified, they are two different questions. There
283
+ * is no merge rule, no last-wins and no refusal, because there is nothing to
284
+ * merge.
285
+ */
286
+ export declare function composeCatalogues(sources: readonly WorkspaceSource[], profileContext?: ResolvedProfileContext): CatalogueCompositionResult;
206
287
  export declare function loadQuestionCatalogue(catalogueSource: WorkspaceSource, profileContext?: ResolvedProfileContext): CatalogueLoadResult;
207
288
  export declare function renderInterrogationReport(report: InterrogationReport): string;
@@ -108,6 +108,53 @@ export interface ProjectionExclusion {
108
108
  readonly id: string;
109
109
  readonly facet: ConceptFacet;
110
110
  }
111
+ /** A query facet naming something the model does not have. */
112
+ export interface UnmatchedSelector {
113
+ readonly facet: string;
114
+ readonly value: string;
115
+ /** The closest name the facet does offer, when one is close enough to be a likely typo. */
116
+ readonly nearest?: string;
117
+ }
118
+ /**
119
+ * Every value in a projection query that names nothing.
120
+ *
121
+ * A projection is a DOCUMENT, and a query holds references the same way a
122
+ * relationship does. YarraMate refuses a relationship pointing at a concept
123
+ * that does not exist; it did not refuse a query naming a state that does not
124
+ * exist, and the symptom is silent. `states: [target-stat]` selects no state,
125
+ * which selects no subject, which exports a clean empty artifact with exit 0.
126
+ * Someone hands that to a client.
127
+ *
128
+ * Checked at `check`, not at `export`, because the typo is in a file rather
129
+ * than in an invocation: CI catches it, and every verb over the same
130
+ * projection inherits the guard instead of each growing its own.
131
+ *
132
+ * ONLY FACETS WITH A CLOSED NAMESPACE ARE CHECKED. `statuses` and
133
+ * `excludeStatuses` are schema enums, refused upstream before this runs.
134
+ * Everything else names something: `owners` and `constraints` are REFS to
135
+ * concepts, which the compiler itself proves by refusing an unresolved owner
136
+ * with YM304, so their namespace is the subject list like `subjects`.
137
+ *
138
+ * Each namespace is derived the way the FILTER derives it, so the check cannot
139
+ * drift from what it guards: `documents` reads the same provenance the
140
+ * `documents` facet compares against, and `states` is the same
141
+ * `yarramate/state/type` scan `conceptSelector` runs.
142
+ *
143
+ * A kind whose profile is not loaded is DORMANT rather than wrong, the same
144
+ * distinction #351 drew for question catalogues, so the kind facets are
145
+ * checked only when a profile context is present.
146
+ *
147
+ * An empty RESULT is not reported here and must not be. A query whose every
148
+ * name resolves and which selects nothing is a real answer to a real question:
149
+ * a target state nobody has populated yet is empty, correctly.
150
+ */
151
+ export declare function unmatchedSelectors(graph: SemanticGraph, projection: ProjectionDefinition, profileContext?: ResolvedProfileContext): readonly UnmatchedSelector[];
152
+ /**
153
+ * {@link unmatchedSelectors} as diagnostics, built HERE rather than at each
154
+ * caller so `check`, and anything that adopts this later, refuse in identical
155
+ * words with an identical code.
156
+ */
157
+ export declare function projectionReferenceDiagnostics(source: WorkspaceSource, projection: ProjectionDefinition, graph: SemanticGraph, profileContext?: ResolvedProfileContext): readonly Diagnostic[];
111
158
  /**
112
159
  * Every concept a query leaves out, and the facet that left it out.
113
160
  *
@@ -7,6 +7,7 @@ export interface WorkspaceManifest {
7
7
  readonly projections: readonly string[];
8
8
  readonly adapterMappings: readonly string[];
9
9
  readonly patterns?: readonly string[];
10
+ readonly questions?: readonly string[];
10
11
  readonly evidence?: readonly string[];
11
12
  readonly contracts?: readonly string[];
12
13
  }
@@ -17,6 +18,20 @@ export interface ResolvedWorkspace {
17
18
  readonly projections: readonly string[];
18
19
  readonly adapterMappings: readonly string[];
19
20
  readonly patterns: readonly string[];
21
+ /**
22
+ * Question catalogues the workspace itself carries (#345, ADR 0129).
23
+ * ADDITIVE to the shipped catalogue: `--catalogue` replaces the base, this
24
+ * adds to it, which is what lets a consultant author a question mid
25
+ * engagement without a product release.
26
+ *
27
+ * OPTIONAL in the type although `loadWorkspaceManifest` always populates it,
28
+ * and that is deliberate. `ResolvedWorkspace` is published, and adding
29
+ * `patterns` to it as a required field broke a consumer's production module:
30
+ * a required field is free for readers and a break for CONSTRUCTORS. Six
31
+ * fixtures in this repository construct one, which is the same signal from
32
+ * inside. Read it as `workspace.questions ?? []`.
33
+ */
34
+ readonly questions?: readonly string[];
20
35
  readonly evidence: readonly string[];
21
36
  readonly contracts: readonly string[];
22
37
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `yarramate/workbook/import` — reading an edited workbook back, for a host
3
+ * that has no Node.
4
+ *
5
+ * A SEPARATE subpath from `yarramate/workbook` rather than more exports on it,
6
+ * because the package declares no `sideEffects` field and a bundler must
7
+ * therefore assume every module might have one. A Worker that only GENERATES
8
+ * workbooks would otherwise carry the reader, the merge and the operations
9
+ * emitter it never calls, and small bundles are the reason the xlsx container
10
+ * is hand-written in the first place (#355).
11
+ *
12
+ * Held to the same bar as `./workbook` and `./interrogation` by
13
+ * `test/export-purity.test.ts`: no Node builtins, no `ws`, no session server,
14
+ * no runtime import of the compiler. `workbook-read.ts` imports nothing at
15
+ * all; inflation uses `DecompressionStream('deflate-raw')`, which Workers,
16
+ * browsers and Node 18+ all have.
17
+ *
18
+ * The reader is ASYNC and the writer is not, deliberately. A workbook this
19
+ * package wrote has stored entries and inline strings, but one a person saved
20
+ * from Excel comes back deflated and shared-stringed, and inflation is only
21
+ * offered as a stream.
22
+ *
23
+ * What a host still owns: reading the bytes, evaluating the projection that
24
+ * says what the workbook SHOULD hold now, and applying the operations. This
25
+ * entry turns bytes into sheets, sheets into a merge report, and a merge
26
+ * report into `yarramate/operations/v1`.
27
+ */
28
+ export { readWorkbook, unzipEntries, columnIndexOf, decodeXmlText, type WorkbookRead, type ZipReadFailure, } from './workbook-read.js';
29
+ export { mergeWorkbook, mergeSheet, keySheet, baselineSheets, isDerivedColumn, type KeyedSheet, type CellChange, type Conflict, type MergeReport, } from './workbook-merge.js';
30
+ export { operationsFrom, operationsDocument, type OperationsResult, } from './workbook-operations.js';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `yarramate/workbook/import` — reading an edited workbook back, for a host
3
+ * that has no Node.
4
+ *
5
+ * A SEPARATE subpath from `yarramate/workbook` rather than more exports on it,
6
+ * because the package declares no `sideEffects` field and a bundler must
7
+ * therefore assume every module might have one. A Worker that only GENERATES
8
+ * workbooks would otherwise carry the reader, the merge and the operations
9
+ * emitter it never calls, and small bundles are the reason the xlsx container
10
+ * is hand-written in the first place (#355).
11
+ *
12
+ * Held to the same bar as `./workbook` and `./interrogation` by
13
+ * `test/export-purity.test.ts`: no Node builtins, no `ws`, no session server,
14
+ * no runtime import of the compiler. `workbook-read.ts` imports nothing at
15
+ * all; inflation uses `DecompressionStream('deflate-raw')`, which Workers,
16
+ * browsers and Node 18+ all have.
17
+ *
18
+ * The reader is ASYNC and the writer is not, deliberately. A workbook this
19
+ * package wrote has stored entries and inline strings, but one a person saved
20
+ * from Excel comes back deflated and shared-stringed, and inflation is only
21
+ * offered as a stream.
22
+ *
23
+ * What a host still owns: reading the bytes, evaluating the projection that
24
+ * says what the workbook SHOULD hold now, and applying the operations. This
25
+ * entry turns bytes into sheets, sheets into a merge report, and a merge
26
+ * report into `yarramate/operations/v1`.
27
+ */
28
+ export { readWorkbook, unzipEntries, columnIndexOf, decodeXmlText, } from './workbook-read.js';
29
+ export { mergeWorkbook, mergeSheet, keySheet, baselineSheets, isDerivedColumn, } from './workbook-merge.js';
30
+ export { operationsFrom, operationsDocument, } from './workbook-operations.js';
package/dist/workbook.js CHANGED
@@ -239,6 +239,21 @@ export const buildWorkbookSheets = (result, provenance) => {
239
239
  ['4.', 'Deleting a row does NOT delete anything. Removals are reported, never applied.'],
240
240
  ['5.', 'Send the file back and import it. Your edits are merged; only a field changed on both sides is refused.'],
241
241
  [],
242
+ [],
243
+ ['What this holds, and what it does not'],
244
+ [
245
+ '',
246
+ `A projection is a QUERY, so this workbook is the slice it names: ${conceptRows.length - 1} concepts and ${relationshipRows.length - 1} relationships.`,
247
+ ],
248
+ [
249
+ '',
250
+ 'Everything the model knows about a subject that IS here is here, including anything these columns do not model, on 07 Other Facts.',
251
+ ],
252
+ [
253
+ '',
254
+ 'A subject outside the slice has no row, and importing this file cannot change it. A narrow workbook is safe against a wide model.',
255
+ ],
256
+ [],
242
257
  ['Sheets beginning ~ are machinery. Do not edit them.'],
243
258
  ];
244
259
  const listColumns = [
@@ -7,6 +7,7 @@ export interface WorkspaceManifest {
7
7
  readonly projections: readonly string[];
8
8
  readonly adapterMappings: readonly string[];
9
9
  readonly patterns?: readonly string[];
10
+ readonly questions?: readonly string[];
10
11
  readonly evidence?: readonly string[];
11
12
  readonly contracts?: readonly string[];
12
13
  }
@@ -17,6 +18,20 @@ export interface ResolvedWorkspace {
17
18
  readonly projections: readonly string[];
18
19
  readonly adapterMappings: readonly string[];
19
20
  readonly patterns: readonly string[];
21
+ /**
22
+ * Question catalogues the workspace itself carries (#345, ADR 0129).
23
+ * ADDITIVE to the shipped catalogue: `--catalogue` replaces the base, this
24
+ * adds to it, which is what lets a consultant author a question mid
25
+ * engagement without a product release.
26
+ *
27
+ * OPTIONAL in the type although `loadWorkspaceManifest` always populates it,
28
+ * and that is deliberate. `ResolvedWorkspace` is published, and adding
29
+ * `patterns` to it as a required field broke a consumer's production module:
30
+ * a required field is free for readers and a break for CONSTRUCTORS. Six
31
+ * fixtures in this repository construct one, which is the same signal from
32
+ * inside. Read it as `workspace.questions ?? []`.
33
+ */
34
+ readonly questions?: readonly string[];
20
35
  readonly evidence: readonly string[];
21
36
  readonly contracts: readonly string[];
22
37
  }
package/dist/workspace.js CHANGED
@@ -107,6 +107,7 @@ export function loadWorkspaceManifest(source, cwd) {
107
107
  projections: expand('projections', 'projection', value.projections),
108
108
  adapterMappings: expand('adapterMappings', 'adapter mapping', value.adapterMappings),
109
109
  patterns: expand('patterns', 'pattern', value.patterns ?? []),
110
+ questions: expand('questions', 'question catalogue', value.questions ?? []),
110
111
  evidence: expand('evidence', 'evidence', value.evidence ?? []),
111
112
  contracts: expand('contracts', 'Core contract', value.contracts ?? []),
112
113
  };
@@ -444,6 +444,18 @@ query:
444
444
  - target-state
445
445
  ```
446
446
 
447
+ **Which makes a workbook a SLICE, and it is worth being plain about that.** An
448
+ omitted facet imposes no constraint, so `query: {}` exports the whole model,
449
+ and every facet you name narrows it. For a subject the query selected, nothing
450
+ is dropped: the projection filters claims by subject membership rather than by
451
+ predicate, so every fact about an included subject arrives, and `07 Other
452
+ Facts` catches whatever the named columns do not model. For a subject it did
453
+ not select, there is simply no row, and a missing row is never a deletion, so
454
+ **a narrow workbook cannot damage a wide model on the way back in**. The
455
+ `00 Read Me` sheet says all of this and reports the counts the slice actually
456
+ has, because an FDE handed a filtered workbook with no note will read it as
457
+ the model.
458
+
447
459
  **Reading the sheets.** Column A is always the id and is what the model is
448
460
  keyed on. A column headed `↳ … (auto)` is derived for readability and is
449
461
  ignored on the way back. Kind and status columns are drawn from the compiled
@@ -472,6 +484,57 @@ did not touch merges even if the repository moved on; only a field changed on
472
484
  is reported and never treated as a deletion, and a new row needs its `Document`
473
485
  column filled in.
474
486
 
487
+ **On a host with no Node**, `yarramate/workbook/import` publishes the same
488
+ three steps the CLI runs, so ingesting a workbook is not CLI-only:
489
+
490
+ ```ts
491
+ import {
492
+ readWorkbook,
493
+ baselineSheets,
494
+ mergeWorkbook,
495
+ operationsFrom,
496
+ operationsDocument,
497
+ } from 'yarramate/workbook/import'
498
+
499
+ const read = await readWorkbook(bytes)
500
+ if (!read.ok) return refuse(read.reason)
501
+
502
+ const ancestor = read.sheets.get('~Baseline')
503
+ if (ancestor === undefined) return refuse('not produced by yarramate export xlsx')
504
+
505
+ const report = mergeWorkbook(
506
+ read.sheets,
507
+ baselineSheets(ancestor),
508
+ buildWorkbookSheets(currentResult, provenance),
509
+ )
510
+ if (report.conflicts.length > 0) return refuse(report.conflicts)
511
+
512
+ const { operations, refusals } = operationsFrom(report, read.sheets)
513
+ await apply(operationsDocument(operations))
514
+ ```
515
+
516
+ `readWorkbook` is **async** while the writer is synchronous, and deliberately
517
+ so: a workbook this package wrote has stored entries and inline strings, but
518
+ one a person saved from Excel comes back deflated and shared-stringed, and
519
+ inflation is only offered as a stream (`DecompressionStream('deflate-raw')`,
520
+ present on Workers, in browsers and in Node 18+). Keeping the writer
521
+ synchronous is what stops it infecting a synchronous store on the host side.
522
+
523
+ It is a **separate subpath** from `yarramate/workbook` on purpose. The package
524
+ declares no `sideEffects` field, so a bundler must assume every module might
525
+ have one and cannot shake an unused re-export away; a host that only generates
526
+ workbooks would otherwise carry the reader, the merge and the operations
527
+ emitter it never calls. `test/export-purity.test.ts` holds the two entries
528
+ disjoint as well as pure.
529
+
530
+ The host keeps three jobs: reading the bytes, evaluating the projection that
531
+ says what the model holds **now** (the third argument to `mergeWorkbook`, which
532
+ is what measures repo drift in the same terms the author edited in), and
533
+ applying the operations. Refusal wording is the host's too. The `Conflict`
534
+ values name the sheet, row, column, what the author wrote, what the workspace
535
+ now holds and their common ancestor, which reads like a merge tool rather than
536
+ like something you hand a consultant.
537
+
475
538
  ## MCP server for agent harnesses
476
539
 
477
540
  Harnesses that load MCP servers can connect the bundled read-only adapter:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yarramate",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Tool-neutral semantic architecture engine and guided methodology",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -62,6 +62,10 @@
62
62
  "types": "./dist/workbook-entry.d.ts",
63
63
  "import": "./dist/workbook-entry.js"
64
64
  },
65
+ "./workbook/import": {
66
+ "types": "./dist/workbook-import-entry.d.ts",
67
+ "import": "./dist/workbook-import-entry.js"
68
+ },
65
69
  "./visual-app": {
66
70
  "types": "./dist/visual-app-lib/editor.d.ts",
67
71
  "import": "./dist/visual-app-lib/editor.js"
@@ -24,6 +24,15 @@
24
24
  "type": "string",
25
25
  "minLength": 1
26
26
  },
27
+ "catalogues": {
28
+ "description": "Every catalogue that contributed, as id@version, base first (#345, ADR 0129). Present only when more than one did. OPTIONAL, and `catalogue` keeps naming the base with its existing value shape, because turning that field into an array would break every reader: an optional additive field is free for readers and does not force constructors. Question ids in this report are qualified as catalogue#question and carry NO version, so a catalogue version bump strands no stored judgment keyed on them; the versions live here instead.",
29
+ "type": "array",
30
+ "minItems": 1,
31
+ "items": {
32
+ "type": "string",
33
+ "minLength": 1
34
+ }
35
+ },
27
36
  "semantics": {
28
37
  "type": "string",
29
38
  "minLength": 1,
@@ -44,9 +44,9 @@
44
44
  }
45
45
  },
46
46
  "waves": {
47
- "description": "Ordered interview phases. Array order is interview order. The wave taxonomy is catalogue opinion, not engine semantics.",
47
+ "description": "Ordered interview phases. Array order is interview order. The wave taxonomy is catalogue opinion, not engine semantics. MAY BE EMPTY (#345, ADR 0129): a catalogue that only contributes questions to waves ANOTHER catalogue declared is the ordinary shape of a project catalogue, and requiring a wave here would force it to declare one it does not want - which is refused as a duplicate the moment two catalogues do it. Evaluated alone, a catalogue with no waves and a question naming one is YM911, correctly: nothing declares it.",
48
48
  "type": "array",
49
- "minItems": 1,
49
+ "minItems": 0,
50
50
  "items": {
51
51
  "type": "object",
52
52
  "additionalProperties": false,
@@ -112,7 +112,6 @@
112
112
  "description": "Which subjects a subject-scope question applies to. Field vocabulary mirrors the projection query selector (ADR 0017).",
113
113
  "type": "object",
114
114
  "additionalProperties": false,
115
-
116
115
  "properties": {
117
116
  "kinds": {
118
117
  "type": "array",
@@ -32,6 +32,10 @@
32
32
  "description": "yarramate/pattern/v1 documents: the shape a concept kind promises, and the wiring the compiler mints between an instance and the parts it binds (ADR 0123). Structure, where profiles declare vocabulary. Optional, like evidence and contracts.",
33
33
  "$ref": "#/$defs/patterns"
34
34
  },
35
+ "questions": {
36
+ "description": "yarramate/question-catalogue/v1 documents the WORKSPACE carries: questions true of this engagement and nowhere else (#345, ADR 0129). ADDITIVE to the shipped catalogue rather than replacing it, which is what --catalogue and MountOptions.catalogue do. Question ids are qualified as catalogue#question on the way out, so two catalogues may carry the same local id. A wave is declared exactly once across the resolved set, and any catalogue may contribute questions to a wave it did not declare. Optional, like patterns, evidence and contracts.",
37
+ "$ref": "#/$defs/patterns"
38
+ },
35
39
  "adapterMappings": {
36
40
  "$ref": "#/$defs/patterns"
37
41
  },