yarramate 0.22.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.
- package/catalogues/core-enrichment.yaml +402 -3
- package/dist/compiler.js +41 -0
- package/dist/interrogate-command.d.ts +19 -0
- package/dist/interrogate-command.js +77 -16
- package/dist/shipped-profile.d.ts +2 -0
- package/dist/shipped-profile.js +6 -0
- package/package.json +2 -1
- package/profiles/yarramate-policy.yaml +18 -0
- package/schema/yarramate-question-catalogue.schema.json +132 -0
- package/skills/yarramate-architecture/SKILL.md +18 -9
- package/skills/yarramate-architecture/references/native-authoring.md +10 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
format: yarramate/question-catalogue/v1
|
|
2
2
|
id: core-enrichment
|
|
3
|
-
version: "0.
|
|
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,
|
|
9
|
-
and implementation waves plus cross-cutting hygiene. Each
|
|
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
|
package/dist/compiler.js
CHANGED
|
@@ -7,6 +7,7 @@ import documentSchema from '../schema/yarramate-document.schema.json' with { typ
|
|
|
7
7
|
import profileSchema from '../schema/yarramate-profile.schema.json' with { type: 'json'
|
|
8
8
|
};
|
|
9
9
|
import { ATTESTATION_PREDICATE_PREFIX, attestationClaimValue } from './graph-claims.js';
|
|
10
|
+
import { shippedPolicyIdentity, shippedPolicySource, } from './shipped-profile.js';
|
|
10
11
|
const coreProfile = 'yarramate/core@0.1';
|
|
11
12
|
const require = createRequire(import.meta.url);
|
|
12
13
|
const ajv2020Module = require('ajv/dist/2020.js');
|
|
@@ -283,6 +284,46 @@ function compileWorkspaceResolved(parsed) {
|
|
|
283
284
|
const identity = `${value.id}@${value.version}`;
|
|
284
285
|
pendingProfiles.push({ input, value, identity, positionFor });
|
|
285
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
|
+
}
|
|
286
327
|
let unresolvedProfiles = pendingProfiles.sort((left, right) => left.identity.localeCompare(right.identity) ||
|
|
287
328
|
left.input.path.localeCompare(right.input.path));
|
|
288
329
|
while (unresolvedProfiles.length > 0) {
|
|
@@ -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;
|
|
@@ -160,6 +160,54 @@ const relationshipKindMatches = (predicate, selectedKinds, matching, profileCont
|
|
|
160
160
|
profileContext?.relationshipKindLineages
|
|
161
161
|
.get(predicate)
|
|
162
162
|
?.includes(selected) === true));
|
|
163
|
+
const profileIdentityOfKind = (qualifiedKind) => {
|
|
164
|
+
const separator = qualifiedKind.indexOf('#');
|
|
165
|
+
return separator === -1 ? qualifiedKind : qualifiedKind.slice(0, separator);
|
|
166
|
+
};
|
|
167
|
+
const namedKinds = (question) => {
|
|
168
|
+
const kinds = [...(question.subjects?.kinds ?? [])];
|
|
169
|
+
for (const condition of question.trigger) {
|
|
170
|
+
switch (condition.condition) {
|
|
171
|
+
case 'missing-relationship':
|
|
172
|
+
case 'no-subject-of-kind':
|
|
173
|
+
case 'missing-constraint':
|
|
174
|
+
kinds.push(...condition.kinds);
|
|
175
|
+
break;
|
|
176
|
+
case 'missing-linkage':
|
|
177
|
+
case 'has-linkage':
|
|
178
|
+
case 'exists-linkage':
|
|
179
|
+
kinds.push(...condition.kinds, ...condition.counterpartKinds);
|
|
180
|
+
break;
|
|
181
|
+
default:
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return kinds;
|
|
186
|
+
};
|
|
187
|
+
const questionIsApplicable = (question, selectedProfiles) => {
|
|
188
|
+
const selected = new Set(selectedProfiles);
|
|
189
|
+
return namedKinds(question).every((kind) => selected.has(profileIdentityOfKind(kind)));
|
|
190
|
+
};
|
|
191
|
+
const linkageHits = (index, condition, subjectId, profileContext) => {
|
|
192
|
+
const matching = condition.kindMatching ?? 'descendants';
|
|
193
|
+
return index.relationshipClaims.some((claim) => {
|
|
194
|
+
if (!('ref' in claim.object))
|
|
195
|
+
return false;
|
|
196
|
+
if (!relationshipKindMatches(claim.predicate, condition.kinds, matching, profileContext)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const counterparts = [];
|
|
200
|
+
if ((condition.direction === 'outgoing' || condition.direction === 'either') &&
|
|
201
|
+
claim.subject === subjectId) {
|
|
202
|
+
counterparts.push(claim.object.ref);
|
|
203
|
+
}
|
|
204
|
+
if ((condition.direction === 'incoming' || condition.direction === 'either') &&
|
|
205
|
+
claim.object.ref === subjectId) {
|
|
206
|
+
counterparts.push(claim.subject);
|
|
207
|
+
}
|
|
208
|
+
return counterparts.some((counterpart) => kindMatches(index.kindOf.get(counterpart), condition.counterpartKinds, matching, profileContext));
|
|
209
|
+
});
|
|
210
|
+
};
|
|
163
211
|
const conditionHolds = (index, condition, subjectId, profileContext) => {
|
|
164
212
|
switch (condition.condition) {
|
|
165
213
|
case 'missing-claim':
|
|
@@ -184,31 +232,43 @@ const conditionHolds = (index, condition, subjectId, profileContext) => {
|
|
|
184
232
|
return (!index.relationshipClaims.some(({ subject, object }) => subject === subjectId ||
|
|
185
233
|
('ref' in object && object.ref === subjectId)) &&
|
|
186
234
|
!index.referenceClaims.some(({ object }) => 'ref' in object && object.ref === subjectId));
|
|
187
|
-
case 'no-subject-of-kind':
|
|
188
|
-
|
|
235
|
+
case 'no-subject-of-kind': {
|
|
236
|
+
const matching = condition.kindMatching ?? 'descendants';
|
|
237
|
+
return ![...index.concepts].some((id) => kindMatches(index.kindOf.get(id), condition.kinds, matching, profileContext));
|
|
238
|
+
}
|
|
189
239
|
case 'no-state-defined':
|
|
190
240
|
return !index.hasStates;
|
|
191
|
-
case 'missing-linkage':
|
|
241
|
+
case 'missing-linkage':
|
|
192
242
|
// The linkage-depth primitive: the subject lacks a relationship of
|
|
193
243
|
// these kinds, in this direction, whose counterpart is of one of
|
|
194
244
|
// these kinds. Both relationship and counterpart kinds resolve
|
|
195
245
|
// through profile lineage by default, matching the selector rule.
|
|
246
|
+
return !linkageHits(index, condition, subjectId, profileContext);
|
|
247
|
+
case 'has-linkage':
|
|
248
|
+
return (subjectId !== undefined &&
|
|
249
|
+
linkageHits(index, condition, subjectId, profileContext));
|
|
250
|
+
case 'exists-linkage':
|
|
251
|
+
return [...index.concepts].some((id) => linkageHits(index, condition, id, profileContext));
|
|
252
|
+
case 'missing-constraint': {
|
|
196
253
|
const matching = condition.kindMatching ?? 'descendants';
|
|
197
|
-
return !index.
|
|
254
|
+
return !(index.claimsBySubject.get(subjectId) ?? []).some((claim) => claim.predicate === 'yarramate/constraint/requires' &&
|
|
255
|
+
'ref' in claim.object &&
|
|
256
|
+
kindMatches(index.kindOf.get(claim.object.ref), condition.kinds, matching, profileContext));
|
|
257
|
+
}
|
|
258
|
+
case 'missing-flow-content': {
|
|
259
|
+
if (subjectId === undefined)
|
|
260
|
+
return false;
|
|
261
|
+
const matching = 'descendants';
|
|
262
|
+
return index.relationshipClaims.some((claim) => {
|
|
198
263
|
if (!('ref' in claim.object))
|
|
199
264
|
return false;
|
|
200
|
-
if (!relationshipKindMatches(claim.predicate,
|
|
265
|
+
if (!relationshipKindMatches(claim.predicate, ['yarramate/core@0.1#flow'], matching, profileContext)) {
|
|
201
266
|
return false;
|
|
202
267
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
: claim.object.ref === subjectId
|
|
208
|
-
? claim.subject
|
|
209
|
-
: undefined;
|
|
210
|
-
return (counterpart !== undefined &&
|
|
211
|
-
kindMatches(index.kindOf.get(counterpart), condition.counterpartKinds, matching, profileContext));
|
|
268
|
+
if (claim.subject !== subjectId && claim.object.ref !== subjectId) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return !(index.claimsBySubject.get(claim.id) ?? []).some(({ predicate, object }) => predicate === 'yarramate/flow/content' && 'value' in object);
|
|
212
272
|
});
|
|
213
273
|
}
|
|
214
274
|
case 'missing-reference':
|
|
@@ -278,10 +338,11 @@ export function evaluateCatalogue(catalogue, graph, profileContext) {
|
|
|
278
338
|
const index = indexGraph(graph);
|
|
279
339
|
let open = 0;
|
|
280
340
|
let openQuestions = 0;
|
|
341
|
+
const applicableQuestions = catalogue.questions.filter((question) => questionIsApplicable(question, graph.profiles));
|
|
281
342
|
const waves = catalogue.waves.map((wave) => ({
|
|
282
343
|
id: wave.id,
|
|
283
344
|
name: wave.name,
|
|
284
|
-
questions:
|
|
345
|
+
questions: applicableQuestions
|
|
285
346
|
.filter((question) => question.wave === wave.id)
|
|
286
347
|
.map((question) => {
|
|
287
348
|
const base = {
|
|
@@ -325,7 +386,7 @@ export function evaluateCatalogue(catalogue, graph, profileContext) {
|
|
|
325
386
|
format: 'yarramate/interrogation-report/v1',
|
|
326
387
|
catalogue: `${catalogue.id}@${catalogue.version}`,
|
|
327
388
|
summary: {
|
|
328
|
-
questions:
|
|
389
|
+
questions: applicableQuestions.length,
|
|
329
390
|
openQuestions,
|
|
330
391
|
open,
|
|
331
392
|
},
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
export const shippedPolicyIdentity = 'yarramate/policy@0.1';
|
|
5
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
export const shippedPolicySource = readFileSync(join(here, '..', 'profiles', 'yarramate-policy.yaml'), 'utf8');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yarramate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Tool-neutral semantic architecture engine and guided methodology",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"assets/likec4",
|
|
27
27
|
"schema",
|
|
28
28
|
"catalogues",
|
|
29
|
+
"profiles",
|
|
29
30
|
"skills/yarramate-architecture",
|
|
30
31
|
"docs/CONSUMING-YARRAMATE.md"
|
|
31
32
|
],
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
format: yarramate/profile/v1
|
|
2
|
+
id: yarramate/policy
|
|
3
|
+
version: "0.1"
|
|
4
|
+
extends: yarramate/core@0.1
|
|
5
|
+
conceptKinds:
|
|
6
|
+
- id: authentication-constraint
|
|
7
|
+
name: Authentication constraint
|
|
8
|
+
parent: yarramate/core@0.1#constraint
|
|
9
|
+
- id: rate-limit-constraint
|
|
10
|
+
name: Rate-limit constraint
|
|
11
|
+
parent: yarramate/core@0.1#constraint
|
|
12
|
+
- id: reliability-constraint
|
|
13
|
+
name: Reliability constraint
|
|
14
|
+
parent: yarramate/core@0.1#constraint
|
|
15
|
+
- id: mechanism-constraint
|
|
16
|
+
name: Mechanism constraint
|
|
17
|
+
parent: yarramate/core@0.1#constraint
|
|
18
|
+
relationshipKinds: []
|
|
@@ -290,7 +290,139 @@
|
|
|
290
290
|
"enum": [
|
|
291
291
|
"exact",
|
|
292
292
|
"descendants"
|
|
293
|
+
],
|
|
294
|
+
"default": "descendants"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"type": "object",
|
|
300
|
+
"additionalProperties": false,
|
|
301
|
+
"required": [
|
|
302
|
+
"condition",
|
|
303
|
+
"kinds",
|
|
304
|
+
"direction",
|
|
305
|
+
"counterpartKinds"
|
|
306
|
+
],
|
|
307
|
+
"properties": {
|
|
308
|
+
"condition": {
|
|
309
|
+
"const": "has-linkage"
|
|
310
|
+
},
|
|
311
|
+
"kinds": {
|
|
312
|
+
"type": "array",
|
|
313
|
+
"minItems": 1,
|
|
314
|
+
"uniqueItems": true,
|
|
315
|
+
"items": {
|
|
316
|
+
"$ref": "#/$defs/qualifiedKind"
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
"direction": {
|
|
320
|
+
"enum": [
|
|
321
|
+
"outgoing",
|
|
322
|
+
"incoming",
|
|
323
|
+
"either"
|
|
293
324
|
]
|
|
325
|
+
},
|
|
326
|
+
"counterpartKinds": {
|
|
327
|
+
"type": "array",
|
|
328
|
+
"minItems": 1,
|
|
329
|
+
"uniqueItems": true,
|
|
330
|
+
"items": {
|
|
331
|
+
"$ref": "#/$defs/qualifiedKind"
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
"kindMatching": {
|
|
335
|
+
"enum": [
|
|
336
|
+
"exact",
|
|
337
|
+
"descendants"
|
|
338
|
+
],
|
|
339
|
+
"default": "descendants"
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"type": "object",
|
|
345
|
+
"additionalProperties": false,
|
|
346
|
+
"required": [
|
|
347
|
+
"condition",
|
|
348
|
+
"kinds",
|
|
349
|
+
"direction",
|
|
350
|
+
"counterpartKinds"
|
|
351
|
+
],
|
|
352
|
+
"properties": {
|
|
353
|
+
"condition": {
|
|
354
|
+
"const": "exists-linkage"
|
|
355
|
+
},
|
|
356
|
+
"kinds": {
|
|
357
|
+
"type": "array",
|
|
358
|
+
"minItems": 1,
|
|
359
|
+
"uniqueItems": true,
|
|
360
|
+
"items": {
|
|
361
|
+
"$ref": "#/$defs/qualifiedKind"
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
"direction": {
|
|
365
|
+
"enum": [
|
|
366
|
+
"outgoing",
|
|
367
|
+
"incoming",
|
|
368
|
+
"either"
|
|
369
|
+
]
|
|
370
|
+
},
|
|
371
|
+
"counterpartKinds": {
|
|
372
|
+
"type": "array",
|
|
373
|
+
"minItems": 1,
|
|
374
|
+
"uniqueItems": true,
|
|
375
|
+
"items": {
|
|
376
|
+
"$ref": "#/$defs/qualifiedKind"
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
"kindMatching": {
|
|
380
|
+
"enum": [
|
|
381
|
+
"exact",
|
|
382
|
+
"descendants"
|
|
383
|
+
],
|
|
384
|
+
"default": "descendants"
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"type": "object",
|
|
390
|
+
"additionalProperties": false,
|
|
391
|
+
"required": [
|
|
392
|
+
"condition",
|
|
393
|
+
"kinds"
|
|
394
|
+
],
|
|
395
|
+
"properties": {
|
|
396
|
+
"condition": {
|
|
397
|
+
"const": "missing-constraint"
|
|
398
|
+
},
|
|
399
|
+
"kinds": {
|
|
400
|
+
"type": "array",
|
|
401
|
+
"minItems": 1,
|
|
402
|
+
"uniqueItems": true,
|
|
403
|
+
"items": {
|
|
404
|
+
"$ref": "#/$defs/qualifiedKind"
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
"kindMatching": {
|
|
408
|
+
"enum": [
|
|
409
|
+
"exact",
|
|
410
|
+
"descendants"
|
|
411
|
+
],
|
|
412
|
+
"default": "descendants"
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"type": "object",
|
|
418
|
+
"additionalProperties": false,
|
|
419
|
+
"description": "The subject is an endpoint of at least one flow relationship that has no yarramate/flow/content claim.",
|
|
420
|
+
"required": [
|
|
421
|
+
"condition"
|
|
422
|
+
],
|
|
423
|
+
"properties": {
|
|
424
|
+
"condition": {
|
|
425
|
+
"const": "missing-flow-content"
|
|
294
426
|
}
|
|
295
427
|
}
|
|
296
428
|
},
|
|
@@ -140,15 +140,24 @@ yarramate design .yarramate/workspace.yaml
|
|
|
140
140
|
or read catalogue files. Answer one question at a time: questions the
|
|
141
141
|
model or evidence can answer, answer from your authority; questions
|
|
142
142
|
marked `human`, relay verbatim with their materiality. Land each answer
|
|
143
|
-
as one atomic batch (`yarramate apply
|
|
144
|
-
.yarramate/workspace.yaml`),
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
143
|
+
as subjects and relationships in one atomic batch (`yarramate apply
|
|
144
|
+
<operations.yaml> .yarramate/workspace.yaml`), never as description
|
|
145
|
+
alone. Then re-run `design` — the next question is recomputed from the
|
|
146
|
+
model, so the loop is resumable across sessions and agents with no
|
|
147
|
+
handover. Use `--subject <id>` to focus the interview on one element.
|
|
148
|
+
When a step reports many `openSubjects` sharing one question (ownership
|
|
149
|
+
is the classic case), do not interview N times: collect the policy
|
|
150
|
+
answer once — "who owns what, by area" — and land it across every listed
|
|
151
|
+
subject as one apply batch.
|
|
152
|
+
|
|
153
|
+
After motivation, work the `interaction` wave (hops, mechanism, payload,
|
|
154
|
+
trust, reliability, capacity) before ownership and hygiene. Policy
|
|
155
|
+
subjects (`authentication-constraint`, `rate-limit-constraint`, and
|
|
156
|
+
siblings) live in a document that selects `yarramate/policy@0.1`; hops
|
|
157
|
+
bind them with qualified `constraints[].ref`. One batch may create and
|
|
158
|
+
bind. When `design --json` returns a step whose `wave` is not
|
|
159
|
+
`interaction` for the slice in focus, stop, render the brief, and do not
|
|
160
|
+
drain `owner-missing`. The interview is complete when `design` says so.
|
|
152
161
|
5. Create:
|
|
153
162
|
- an alternatives projection for the decision;
|
|
154
163
|
- a bounded target projection for implementation agents.
|
|
@@ -200,6 +200,16 @@ rationale in the description and it becomes the non-goal record.
|
|
|
200
200
|
Ownership is one accountable reference, not approval workflow. Constraints are
|
|
201
201
|
identified references, not a policy engine or free-form metadata bag.
|
|
202
202
|
|
|
203
|
+
Distinct NFR rules (authentication, rate limit, reliability, serving
|
|
204
|
+
protocol) use `yarramate/policy@0.1` kinds, not Core `constraint` alone.
|
|
205
|
+
Select that profile on the document that declares the policy subjects;
|
|
206
|
+
other documents may keep their existing profile and bind with a
|
|
207
|
+
qualified `constraints[].ref`. Do not copy a profile file into the
|
|
208
|
+
workspace. A hop that must not have a rate limit binds a
|
|
209
|
+
`ratelimit-not-applicable` subject of kind `rate-limit-constraint` with
|
|
210
|
+
the reason in its description. Numeric caps go in `expects` on the
|
|
211
|
+
binding.
|
|
212
|
+
|
|
203
213
|
## Attestations
|
|
204
214
|
|
|
205
215
|
```yaml
|