yarramate 0.21.0 → 0.23.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.
@@ -1,12 +1,13 @@
1
1
  format: yarramate/question-catalogue/v1
2
2
  id: core-enrichment
3
- version: "0.8"
3
+ version: "0.9"
4
4
  profile: yarramate/core@0.1
5
5
  presentation:
6
6
  title: Core enrichment interview
7
7
  description: >-
8
- The guided design path: motivation, business, application, technology,
9
- and implementation waves plus cross-cutting hygiene. Each question
8
+ The guided design path: motivation, interaction, business, application,
9
+ technology, and implementation waves plus cross-cutting hygiene. Each
10
+ question
10
11
  states the decision its answer changes; a question that cannot is
11
12
  deleted, not softened. Adequacy is enforced by linkage depth and
12
13
  attestation, never by reading words. Versioning is additive within a
@@ -18,6 +19,12 @@ waves:
18
19
  - id: motivation
19
20
  name: Motivation
20
21
  description: Why the system exists and what constrains it.
22
+ - id: interaction
23
+ name: Interaction
24
+ description: >-
25
+ Load-bearing hops: the behavior a component is assigned to, the
26
+ serving/triggering/flow mechanism, payload, trust, reliability, and
27
+ capacity. Hygiene waits.
21
28
  - id: business
22
29
  name: Business
23
30
  description: Who acts, what is served, and what information matters.
@@ -333,6 +340,398 @@ questions:
333
340
  attestation with topic "adequacy" (revoke by deleting it; Git
334
341
  reviews both).
335
342
 
343
+ # ---- interaction ---------------------------------------------------------
344
+ - id: authn-standard-missing
345
+ wave: interaction
346
+ since: "0.9"
347
+ scope: workspace
348
+ trigger:
349
+ - condition: exists-linkage
350
+ kinds:
351
+ - yarramate/core@0.1#serving
352
+ - yarramate/core@0.1#flow
353
+ - yarramate/core@0.1#triggering
354
+ direction: either
355
+ counterpartKinds:
356
+ - yarramate/core@0.1#applicationComponent
357
+ - yarramate/core@0.1#applicationInterface
358
+ - yarramate/core@0.1#businessActor
359
+ - condition: no-subject-of-kind
360
+ kinds:
361
+ - yarramate/policy@0.1#authentication-constraint
362
+ question: >-
363
+ What is the default authentication mechanism for interactions in this
364
+ architecture?
365
+ askPlain: >-
366
+ When one system talks to another here, how do they prove who they
367
+ are? Pick the default now so each hop is not inventing its own.
368
+ materiality: >-
369
+ An unauthenticated hop to another system is a trust-boundary decision
370
+ nobody recorded. Implementers will pick a mechanism in code, and the
371
+ next hop will assume a different one. This question does not cover
372
+ authorization or transport security.
373
+ authority: human
374
+ resolution: >-
375
+ Add an authentication-constraint (the standard, or a not-applicable
376
+ subject with the reason in its description) in a document that
377
+ selects yarramate/policy@0.1.
378
+
379
+ - id: ratelimit-standard-missing
380
+ wave: interaction
381
+ since: "0.9"
382
+ scope: workspace
383
+ trigger:
384
+ - condition: exists-linkage
385
+ kinds:
386
+ - yarramate/core@0.1#serving
387
+ direction: either
388
+ counterpartKinds:
389
+ - yarramate/core@0.1#businessActor
390
+ - condition: no-subject-of-kind
391
+ kinds:
392
+ - yarramate/policy@0.1#rate-limit-constraint
393
+ question: >-
394
+ Where is rate limiting a requirement, and what is the default rule?
395
+ askPlain: >-
396
+ Do caller-facing APIs have a capacity cap? If yes, what is it? If
397
+ no, say so so we do not keep asking per hop.
398
+ materiality: >-
399
+ Caller-facing hops without a capacity rule are sized by whoever
400
+ implements first. An explicit none is a decision; silence is not.
401
+ authority: human
402
+ resolution: >-
403
+ Add a rate-limit-constraint for the default cap, or a
404
+ ratelimit-not-applicable subject. Bind hop-specific values with
405
+ expects on the binding.
406
+
407
+ - id: reliability-standard-missing
408
+ wave: interaction
409
+ since: "0.9"
410
+ scope: workspace
411
+ trigger:
412
+ - condition: exists-linkage
413
+ kinds:
414
+ - yarramate/core@0.1#flow
415
+ - yarramate/core@0.1#triggering
416
+ direction: either
417
+ counterpartKinds:
418
+ - yarramate/core@0.1#applicationComponent
419
+ - yarramate/core@0.1#applicationInterface
420
+ - yarramate/core@0.1#applicationProcess
421
+ - yarramate/core@0.1#applicationFunction
422
+ - yarramate/core@0.1#applicationInteraction
423
+ - yarramate/core@0.1#applicationEvent
424
+ - yarramate/core@0.1#businessActor
425
+ - condition: no-subject-of-kind
426
+ kinds:
427
+ - yarramate/policy@0.1#reliability-constraint
428
+ question: >-
429
+ What is the default delivery, retry, and idempotency rule for
430
+ asynchronous or content-moving interactions?
431
+ askPlain: >-
432
+ When a message or payload does not arrive, do we retry, and what
433
+ makes a retry safe to repeat?
434
+ materiality: >-
435
+ Retry without idempotency duplicates work; no retry without a
436
+ recorded choice leaves timeout behaviour to each implementer.
437
+ authority: human
438
+ resolution: >-
439
+ Add a reliability-constraint (the standard, or not-applicable with
440
+ the reason in its description).
441
+
442
+ - id: hop-unrealised
443
+ wave: interaction
444
+ since: "0.9"
445
+ scope: subject
446
+ subjects:
447
+ kinds:
448
+ - yarramate/core@0.1#applicationComponent
449
+ statuses:
450
+ - planned
451
+ - current
452
+ trigger:
453
+ - condition: has-linkage
454
+ kinds:
455
+ - yarramate/core@0.1#serving
456
+ - yarramate/core@0.1#flow
457
+ - yarramate/core@0.1#triggering
458
+ direction: either
459
+ counterpartKinds:
460
+ - yarramate/core@0.1#applicationComponent
461
+ - yarramate/core@0.1#applicationInterface
462
+ - yarramate/core@0.1#businessActor
463
+ - condition: missing-linkage
464
+ kinds:
465
+ - yarramate/core@0.1#assignment
466
+ direction: outgoing
467
+ counterpartKinds:
468
+ - yarramate/core@0.1#applicationProcess
469
+ - yarramate/core@0.1#applicationFunction
470
+ - yarramate/core@0.1#applicationInteraction
471
+ - yarramate/core@0.1#applicationEvent
472
+ - yarramate/core@0.1#applicationInterface
473
+ question: >-
474
+ {subject.name} participates in an application interaction, but
475
+ nothing it is assigned to is that interaction. What process,
476
+ function, interaction, event, or interface is the hop?
477
+ askPlain: >-
478
+ {subject.name} talks to something else, but the model never names
479
+ the work it does in that talk. What is the actual step?
480
+ materiality: >-
481
+ A component-to-component edge with no assigned behavior is
482
+ inventory. Protocol, trust, payload, and failure have nowhere to
483
+ bind until the hop is a subject.
484
+ authority: either
485
+ resolution: >-
486
+ Add an application process, function, interaction, event, or
487
+ interface, assign this component to it, and relate that behavior
488
+ with serving, triggering, or flow to its counterpart. Do not answer
489
+ by deleting the component or adding a realization instead.
490
+
491
+ - id: interaction-protocol-unbound
492
+ wave: interaction
493
+ since: "0.9"
494
+ scope: subject
495
+ subjects:
496
+ kinds:
497
+ - yarramate/core@0.1#applicationProcess
498
+ - yarramate/core@0.1#applicationFunction
499
+ - yarramate/core@0.1#applicationInteraction
500
+ - yarramate/core@0.1#applicationEvent
501
+ - yarramate/core@0.1#applicationInterface
502
+ statuses:
503
+ - planned
504
+ - current
505
+ trigger:
506
+ - condition: has-linkage
507
+ kinds:
508
+ - yarramate/core@0.1#serving
509
+ direction: either
510
+ counterpartKinds:
511
+ - yarramate/core@0.1#applicationComponent
512
+ - yarramate/core@0.1#applicationInterface
513
+ - yarramate/core@0.1#applicationProcess
514
+ - yarramate/core@0.1#applicationFunction
515
+ - yarramate/core@0.1#applicationInteraction
516
+ - yarramate/core@0.1#applicationEvent
517
+ - yarramate/core@0.1#businessActor
518
+ - condition: missing-constraint
519
+ kinds:
520
+ - yarramate/policy@0.1#mechanism-constraint
521
+ question: >-
522
+ {subject.name} is a serving hop. Which protocol is it — REST/HTTP,
523
+ SOAP, GraphQL, or something else?
524
+ askPlain: >-
525
+ When {subject.name} is called, what is the wire protocol?
526
+ materiality: >-
527
+ Serving names the pattern, not the contract. A REST hop and a SOAP
528
+ hop fail, version, and authenticate differently.
529
+ authority: either
530
+ resolution: >-
531
+ Bind a mechanism-constraint on this behavior (or a not-applicable
532
+ subject if the serving kind is already the whole answer).
533
+
534
+ - id: interaction-content-unknown
535
+ wave: interaction
536
+ since: "0.9"
537
+ scope: subject
538
+ subjects:
539
+ kinds:
540
+ - yarramate/core@0.1#applicationProcess
541
+ - yarramate/core@0.1#applicationFunction
542
+ - yarramate/core@0.1#applicationInteraction
543
+ - yarramate/core@0.1#applicationEvent
544
+ - yarramate/core@0.1#applicationInterface
545
+ statuses:
546
+ - planned
547
+ - current
548
+ trigger:
549
+ - condition: missing-flow-content
550
+ question: >-
551
+ {subject.name} sends a flow with no content. What moves?
552
+ askPlain: >-
553
+ {subject.name} sends something on. What is that something called?
554
+ materiality: >-
555
+ A flow with no content is an unnamed payload. Retry, scanning, and
556
+ the counterpart contract have nothing to name.
557
+ authority: either
558
+ resolution: >-
559
+ Set content on the flow relationship.
560
+
561
+ - id: interaction-contract-unknown
562
+ wave: interaction
563
+ since: "0.9"
564
+ scope: subject
565
+ subjects:
566
+ kinds:
567
+ - yarramate/core@0.1#applicationProcess
568
+ - yarramate/core@0.1#applicationFunction
569
+ - yarramate/core@0.1#applicationInteraction
570
+ - yarramate/core@0.1#applicationEvent
571
+ - yarramate/core@0.1#applicationInterface
572
+ statuses:
573
+ - planned
574
+ - current
575
+ trigger:
576
+ - condition: has-linkage
577
+ kinds:
578
+ - yarramate/core@0.1#serving
579
+ - yarramate/core@0.1#flow
580
+ - yarramate/core@0.1#triggering
581
+ direction: either
582
+ counterpartKinds:
583
+ - yarramate/core@0.1#applicationComponent
584
+ - yarramate/core@0.1#applicationInterface
585
+ - yarramate/core@0.1#applicationProcess
586
+ - yarramate/core@0.1#applicationFunction
587
+ - yarramate/core@0.1#applicationInteraction
588
+ - yarramate/core@0.1#applicationEvent
589
+ - yarramate/core@0.1#businessActor
590
+ - condition: missing-linkage
591
+ kinds:
592
+ - yarramate/core@0.1#access
593
+ direction: outgoing
594
+ counterpartKinds:
595
+ - yarramate/core@0.1#dataObject
596
+ - yarramate/core@0.1#contract
597
+ question: >-
598
+ {subject.name} participates in a hop but accesses no contract or
599
+ data object. What information does it read or write?
600
+ askPlain: >-
601
+ What record or contract is {subject.name} working with?
602
+ materiality: >-
603
+ Schema ownership, identifiers, and classification have nowhere to
604
+ attach until the hop names the information it moves.
605
+ authority: either
606
+ resolution: >-
607
+ Add access from this behavior to a dataObject or contract.
608
+
609
+ - id: interaction-trust-unbound
610
+ wave: interaction
611
+ since: "0.9"
612
+ scope: subject
613
+ subjects:
614
+ kinds:
615
+ - yarramate/core@0.1#applicationProcess
616
+ - yarramate/core@0.1#applicationFunction
617
+ - yarramate/core@0.1#applicationInteraction
618
+ - yarramate/core@0.1#applicationEvent
619
+ - yarramate/core@0.1#applicationInterface
620
+ statuses:
621
+ - planned
622
+ - current
623
+ trigger:
624
+ - condition: has-linkage
625
+ kinds:
626
+ - yarramate/core@0.1#serving
627
+ - yarramate/core@0.1#flow
628
+ - yarramate/core@0.1#triggering
629
+ direction: either
630
+ counterpartKinds:
631
+ - yarramate/core@0.1#applicationComponent
632
+ - yarramate/core@0.1#applicationInterface
633
+ - yarramate/core@0.1#applicationProcess
634
+ - yarramate/core@0.1#applicationFunction
635
+ - yarramate/core@0.1#applicationInteraction
636
+ - yarramate/core@0.1#applicationEvent
637
+ - yarramate/core@0.1#businessActor
638
+ - condition: missing-constraint
639
+ kinds:
640
+ - yarramate/policy@0.1#authentication-constraint
641
+ question: >-
642
+ How is trust established for {subject.name}?
643
+ askPlain: >-
644
+ Who does {subject.name} authenticate as, and with what?
645
+ materiality: >-
646
+ An unauthenticated hop to another system is a trust-boundary
647
+ decision nobody recorded. This is authentication only — not
648
+ authorization or transport security.
649
+ authority: either
650
+ resolution: >-
651
+ Bind an authentication-constraint on this behavior, or a
652
+ not-applicable subject if this hop is deliberately unauthenticated.
653
+
654
+ - id: interaction-reliability-unbound
655
+ wave: interaction
656
+ since: "0.9"
657
+ scope: subject
658
+ subjects:
659
+ kinds:
660
+ - yarramate/core@0.1#applicationProcess
661
+ - yarramate/core@0.1#applicationFunction
662
+ - yarramate/core@0.1#applicationInteraction
663
+ - yarramate/core@0.1#applicationEvent
664
+ - yarramate/core@0.1#applicationInterface
665
+ statuses:
666
+ - planned
667
+ - current
668
+ trigger:
669
+ - condition: has-linkage
670
+ kinds:
671
+ - yarramate/core@0.1#triggering
672
+ - yarramate/core@0.1#flow
673
+ direction: either
674
+ counterpartKinds:
675
+ - yarramate/core@0.1#applicationComponent
676
+ - yarramate/core@0.1#applicationInterface
677
+ - yarramate/core@0.1#applicationProcess
678
+ - yarramate/core@0.1#applicationFunction
679
+ - yarramate/core@0.1#applicationInteraction
680
+ - yarramate/core@0.1#applicationEvent
681
+ - yarramate/core@0.1#businessActor
682
+ - condition: missing-constraint
683
+ kinds:
684
+ - yarramate/policy@0.1#reliability-constraint
685
+ question: >-
686
+ What happens when {subject.name} fails — retry, idempotency,
687
+ dead-letter, compensation?
688
+ askPlain: >-
689
+ If {subject.name} does not finish, do we retry, and what makes a
690
+ retry safe?
691
+ materiality: >-
692
+ A content-moving hop without a reliability rule duplicates or
693
+ drops work at the first timeout.
694
+ authority: either
695
+ resolution: >-
696
+ Bind a reliability-constraint. A named failure process is stronger
697
+ and may come later; the constraint is the 0.9 closer.
698
+
699
+ - id: interaction-capacity-unbound
700
+ wave: interaction
701
+ since: "0.9"
702
+ scope: subject
703
+ subjects:
704
+ kinds:
705
+ - yarramate/core@0.1#applicationProcess
706
+ - yarramate/core@0.1#applicationFunction
707
+ - yarramate/core@0.1#applicationInteraction
708
+ - yarramate/core@0.1#applicationEvent
709
+ - yarramate/core@0.1#applicationInterface
710
+ statuses:
711
+ - planned
712
+ - current
713
+ trigger:
714
+ - condition: has-linkage
715
+ kinds:
716
+ - yarramate/core@0.1#serving
717
+ direction: either
718
+ counterpartKinds:
719
+ - yarramate/core@0.1#businessActor
720
+ - condition: missing-constraint
721
+ kinds:
722
+ - yarramate/policy@0.1#rate-limit-constraint
723
+ question: >-
724
+ What is the capacity rule for {subject.name}?
725
+ askPlain: >-
726
+ How hard may callers hit {subject.name}, or is there no cap?
727
+ materiality: >-
728
+ An Experience-facing hop without a capacity rule is sized by
729
+ whoever implements first. Explicit none is a decision.
730
+ authority: either
731
+ resolution: >-
732
+ Bind a rate-limit-constraint, with expects on the binding for the
733
+ numeric cap, or a not-applicable subject.
734
+
336
735
  # ---- business ------------------------------------------------------------
337
736
  - id: service-consumer-unknown
338
737
  wave: business
@@ -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';
@@ -108,24 +108,7 @@ export type IncrementalCompilationResult = ({
108
108
  readonly incremental: boolean;
109
109
  readonly cache: CompilationCache;
110
110
  };
111
- export declare const ATTESTATION_PREDICATE_PREFIX = "yarramate/attestation/";
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,8 @@ 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';
10
+ import { shippedPolicyIdentity, shippedPolicySource, } from './shipped-profile.js';
9
11
  const coreProfile = 'yarramate/core@0.1';
10
12
  const require = createRequire(import.meta.url);
11
13
  const ajv2020Module = require('ajv/dist/2020.js');
@@ -39,41 +41,7 @@ const presenceClaimId = (subject, state) => `${subject}~present-in-${Buffer.from
39
41
  const aliasClaimId = (subject, alias) => `${subject}~alias-${Buffer.from(alias, 'utf8').toString('hex')}`;
40
42
  const distinctFromClaimId = (subject, other) => `${subject}~distinct-from-${Buffer.from(other, 'utf8').toString('hex')}`;
41
43
  const supersedesClaimId = (subject, predecessor) => `${subject}~supersedes-${Buffer.from(predecessor, 'utf8').toString('hex')}`;
42
- export const ATTESTATION_PREDICATE_PREFIX = 'yarramate/attestation/';
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
- };
44
+ export { ATTESTATION_PREDICATE_PREFIX, attestationClaimValue, parseAttestationClaimValue, parseConstraintExpectsValue, } from './graph-claims.js';
77
45
  const describeAspect = (aspect) => aspect.replace('-', ' ');
78
46
  // Candidate order is the policy-matrix declaration order: the resolved kind
79
47
  // map inserts core policies first, then extension kinds as declared.
@@ -316,6 +284,46 @@ function compileWorkspaceResolved(parsed) {
316
284
  const identity = `${value.id}@${value.version}`;
317
285
  pendingProfiles.push({ input, value, identity, positionFor });
318
286
  }
287
+ const alreadyDeclaresPolicy = pendingProfiles.some(({ identity }) => identity === shippedPolicyIdentity);
288
+ if (!alreadyDeclaresPolicy) {
289
+ const selected = documentInputs.some(({ entry }) => {
290
+ const value = entry.value;
291
+ return value.profile === shippedPolicyIdentity;
292
+ });
293
+ const extended = pendingProfiles.some(({ value }) => value.extends === shippedPolicyIdentity);
294
+ if (selected || extended) {
295
+ const input = {
296
+ path: 'yarramate:profile:yarramate/policy@0.1',
297
+ source: shippedPolicySource,
298
+ };
299
+ const { entry, fresh } = parseWorkspaceSource(input);
300
+ const value = entry.value;
301
+ if (entry.schemaDiagnostics.length > 0) {
302
+ profileDiagnostics.push(...entry.schemaDiagnostics);
303
+ }
304
+ else if (!validateProfile(value)) {
305
+ for (const error of validateProfile.errors ?? []) {
306
+ profileDiagnostics.push({
307
+ severity: 'error',
308
+ code: 'YM201',
309
+ message: `Profile schema violation: ${describeSchemaViolation(error)}`,
310
+ path: input.path,
311
+ pointer: error.instancePath || '/',
312
+ line: 1,
313
+ column: 1,
314
+ });
315
+ }
316
+ }
317
+ else {
318
+ pendingProfiles.push({
319
+ input,
320
+ value,
321
+ identity: shippedPolicyIdentity,
322
+ positionFor: positionReader(input.source, entry.positions, fresh),
323
+ });
324
+ }
325
+ }
326
+ }
319
327
  let unresolvedProfiles = pendingProfiles.sort((left, right) => left.identity.localeCompare(right.identity) ||
320
328
  left.input.path.localeCompare(right.input.path));
321
329
  while (unresolvedProfiles.length > 0) {
@@ -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 { type ResolvedProfileContext, type SemanticGraph } from './compiler.js';
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: string | null;
9
- readonly aspect: string | null;
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[];
@@ -1,4 +1,4 @@
1
- import { ATTESTATION_PREDICATE_PREFIX, parseAttestationClaimValue, parseConstraintExpectsValue, } from './compiler.js';
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';
@@ -18,6 +18,7 @@ type CatalogueCondition = {
18
18
  } | {
19
19
  readonly condition: 'no-subject-of-kind';
20
20
  readonly kinds: readonly string[];
21
+ readonly kindMatching?: 'exact' | 'descendants';
21
22
  } | {
22
23
  readonly condition: 'no-state-defined';
23
24
  } | {
@@ -26,6 +27,24 @@ type CatalogueCondition = {
26
27
  readonly direction: 'incoming' | 'outgoing';
27
28
  readonly counterpartKinds: readonly string[];
28
29
  readonly kindMatching?: 'exact' | 'descendants';
30
+ } | {
31
+ readonly condition: 'has-linkage';
32
+ readonly kinds: readonly string[];
33
+ readonly direction: 'incoming' | 'outgoing' | 'either';
34
+ readonly counterpartKinds: readonly string[];
35
+ readonly kindMatching?: 'exact' | 'descendants';
36
+ } | {
37
+ readonly condition: 'exists-linkage';
38
+ readonly kinds: readonly string[];
39
+ readonly direction: 'incoming' | 'outgoing' | 'either';
40
+ readonly counterpartKinds: readonly string[];
41
+ readonly kindMatching?: 'exact' | 'descendants';
42
+ } | {
43
+ readonly condition: 'missing-constraint';
44
+ readonly kinds: readonly string[];
45
+ readonly kindMatching?: 'exact' | 'descendants';
46
+ } | {
47
+ readonly condition: 'missing-flow-content';
29
48
  } | {
30
49
  readonly condition: 'missing-reference';
31
50
  readonly predicate: string;