Repository navigation
Expand file tree
/
Copy pathintent_library_codegen.py
More file actions
1367 lines (1223 loc) · 76.4 KB
/
Copy pathintent_library_codegen.py
File metadata and controls
1367 lines (1223 loc) · 76.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
"""intent_library_codegen.py — C1 build-time codegen for the LogCraft Intent library.
Generates the library's C++ entries (constexpr data + derived index) from declaration
files, resolving each entry's ratification state against the twin-generated manifest.
THE BOUNDARY (hard rule): this TOOL is public; the intent DECLARATIONS it
consumes and the C++ it EMITS stay in the private consumer repo. Everything in this
file — including every --selftest fixture — is SYNTHETIC by construction. Never add a
real library entry here: tools and their fixtures migrate together, and a real
declaration beside the tool walks the moat into the public repo.
THE FOUR TEETH this tool carries (the soundness fence, §4):
1. The ratification state is REQUIRED in the output — every generated entry carries
one, resolved here; there is no way to emit an entry without a state.
2. `Ratified` is NOT hand-writable — the declaration grammar has NO ratification
key (rejected below, by name, with the fence cited). Ratified records exist only
in the twin-generated manifest.
3. Ratification is keyed on the declaration's CONTENT HASH — resolution is a
comparison (manifest hash == canonical hash of the current declaration), so an
edit revokes it and a reformat does not.
4. The showcase surface is fail-closed — the generated ratified indices contain
ONLY hash-matched entries; Authored entries are not in them, by construction.
THE LINK FENCE (ADR-17.D11), which is NOT one of the teeth and is deliberately a different
kind of rule. The four teeth are properties of a declaration; this one relates a
declaration to the BUILD that compiles it. A `dialect:` names a canon semantic package
the consuming target must LINK, and the build is the only thing that knows which ones it
links — so it hands that set over as `--linked-dialects` and this tool refuses, by file
and line, any declaration naming a dialect outside it. The relation is CONTAINMENT
(declared ⊆ linked): a linked dialect nobody declares is legitimate. An absent or empty
operand is FATAL rather than a skipped fence.
RATIFICATION IS GRAIN-SCOPED (§4.6, ruled 2026-07-26). A declaration's two halves are
measured independently and can carry OPPOSITE verdicts, so a record names the GRAIN it
measured, resolution is keyed on (name, grain), and the freshness hash of tooth 3 covers
exactly that grain — `record.hash == hash(entry.<grain>)`. Two consequences the whole-
entry hash got wrong: editing a body no longer revokes a structure ratification (the fence
used to punish acting on its own residual compass), and tooth 4 dispatches per grain, so a
structurally-ratified entry cannot license its unmeasured body into the showcase.
DETERMINISM MUSTs (§5.3): output is byte-identical across machines, runs and
toolchains. Concretely: entries and every emitted list iterate in sorted or declared
order (never hash order); scalars are STRINGS end-to-end (this parser types nothing,
so no locale- or stdlib-dependent text<->number conversion exists to diverge); input
and output are ASCII-only, LF-only; the tool version enters the generated header but
NOT the declaration hash (MUST 4: a tool upgrade must not mass-revoke the library);
the hash covers canonicalized semantic content, not file bytes (MUST 3: a reformat
must not revoke ratification).
Grammar note: declaration files use a deliberate STRICT SUBSET of YAML — block maps,
block sequences, one-line flow sequences of scalars, quoted/plain scalars, comments.
No anchors, tags, flow maps, multiline scalars, tabs, duplicate keys, non-ASCII.
The subset keeps parsing bit-stable with zero dependencies on every build host.
"""
from __future__ import annotations
import argparse
import io
import json
import os
import re
import sys
from pathlib import Path
TOOL_VERSION = "1"
INTENT_FILE_SUFFIX = ".intent.yaml"
# ── shared machinery (malf/codegen_common.py) ────────────────────────────────
#
# The strict-YAML subset parser, the canonical hash and the C++ escaping helpers are
# SHARED with dialect_package_codegen.py; the schema, the key sets and the SORT RULE are
# not, and that split is deliberate (DN-17.D19). This tool sorts entries — they are a set
# discovered from the filesystem, and sorting is what makes discovery order-independent.
# The dialect tool must NEVER sort: its rows are content in declared order.
from codegen_common import ( # noqa: E402
CODEGEN_COMMON_VERSION,
DeclarationError,
cpp_escape as _cpp_escape,
cpp_symbol as _cpp_symbol,
canonical_hash,
expect_keys as _expect_keys_common,
fail,
key_line,
parse_subset_yaml,
read_text,
required as _required,
required_scalar as _required_scalar,
)
# ── declaration schema (§2.2) ────────────────────────────────────────────────
_NAME_PATTERN = re.compile(r"[a-z0-9_]+(\.[a-z0-9_]+)+")
_DIALECT_PATTERN = re.compile(r"[a-z_][a-z0-9_]*")
_KIND_PATTERN = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
_FIELD_NAME_PATTERN = re.compile(r"[a-z_][a-z0-9_]*")
_CONSTRAINT_PATTERN = re.compile(r">= (0|[1-9][0-9]*)")
_PLACEHOLDER_PATTERN = re.compile(r"\{([^{}]*)\}")
# Tooth 2, by name: the grammar has NO ratified form. These keys are rejected with the
# fence cited so the error teaches, not just refuses.
_RATIFICATION_KEYS = frozenset({"ratification", "ratified", "soundness", "sound"})
_RATIFICATION_REJECTIONS = {
key: ("a hand-authored declaration can only ever be Authored (soundness fence, "
"tooth 2). Ratification lives in the twin-generated manifest, keyed on this "
"declaration's content hash; there is no way to write it here, and that is "
"the design.")
for key in _RATIFICATION_KEYS
}
_ROLES = ("Categorical", "Identifier", "Numeric")
_CARDINALITIES = ("bounded", "unbounded")
_PAYLOAD_CONTRACTS = ("None", "Declared")
_UNITS = ("count",) # closed; grows deliberately with the next real entry that needs one
def _expect_keys(node: dict, allowed: tuple[str, ...], context: str, source: str) -> None:
_expect_keys_common(node, allowed, context, source, rejected=_RATIFICATION_REJECTIONS)
def validate_declaration(document: dict, entry_name_from_filename: str, source: str) -> dict:
"""Validate one declaration document; return the SEMANTIC declaration (dict).
The returned dict is the canonical content — exactly what the hash covers.
"""
_expect_keys(document, ("intent",), "document root", source)
intent = _required(document, "intent", "document root", source)
if not isinstance(intent, dict):
fail(source, None, "`intent:` must be a mapping")
_expect_keys(intent, ("name", "dialect", "structure", "body"), "intent", source)
name = _required_scalar(intent, "name", "intent", source)
if not _NAME_PATTERN.fullmatch(name):
fail(source, None,
f"intent name {name!r}: must be dotted lowercase (`<family>.<entry>`, "
"[a-z0-9_], at least one dot) — the library identity, globally unique")
if name != entry_name_from_filename:
fail(source, None,
f"intent name {name!r} does not match its file name "
f"(expected `{entry_name_from_filename}{INTENT_FILE_SUFFIX}` to declare "
f"`name: {entry_name_from_filename}`) — one entry, one file (§6)")
declaration: dict[str, object] = {"name": name}
dialect = intent.get("dialect")
if dialect is not None:
if not isinstance(dialect, str) or not _DIALECT_PATTERN.fullmatch(dialect):
fail(source, None, f"`dialect:` must name a canon semantic package ([a-z_]) — got {dialect!r}")
declaration["dialect"] = dialect
structure = intent.get("structure")
body = intent.get("body")
if structure is None and body is None:
fail(source, None,
"a declaration needs a structural half, a body half, or both — the fourth "
"shape (neither) is illegal (§2.1); an entry that declares nothing is not an intent")
if structure is not None:
if not isinstance(structure, dict):
fail(source, None, "`structure:` must be a mapping")
if "child_order" in structure:
fail(source, None,
"structure: `child_order:` is CONSUMED from the canon dialect rows through "
"`kind`, never re-declared here — remove the key")
_expect_keys(structure, ("kind", "payload"), "structure", source)
if dialect is None:
fail(source, None,
"a structural half requires `dialect:` — a structural kind exists only in a "
"canon dialect's vocabulary (§3.2); a dialect-less quantum has no rows to pair with")
kind = _required_scalar(structure, "kind", "structure", source)
if not _KIND_PATTERN.fullmatch(kind):
fail(source, None, f"structure kind {kind!r}: not a valid kind token")
payload = _required_scalar(structure, "payload", "structure", source)
if payload not in _PAYLOAD_CONTRACTS:
fail(source, None,
f"structure payload {payload!r}: the payload CONTRACT is `None` or `Declared` "
"(§2.4) — never a literal value; the binding supplies the value per instance")
declaration["structure"] = {"kind": kind, "payload": payload}
if body is not None:
if not isinstance(body, dict):
fail(source, None, "`body:` must be a mapping")
_expect_keys(body, ("message_template", "fields"), "body", source)
template = _required_scalar(body, "message_template", "body", source)
if not template:
fail(source, None, "body: `message_template:` must be non-empty")
raw_fields = body.get("fields", [])
if not isinstance(raw_fields, list):
fail(source, None, "body: `fields:` must be a sequence")
fields: list[dict] = []
seen_field_names: set[str] = set()
for position, raw_field in enumerate(raw_fields):
if not isinstance(raw_field, dict):
fail(source, None, f"body fields[{position}]: must be a mapping")
fields.append(_validate_field(raw_field, position, seen_field_names, source))
for placeholder in _PLACEHOLDER_PATTERN.findall(template):
if placeholder not in seen_field_names:
fail(source, None,
f"body: message_template placeholder {{{placeholder}}} names no declared "
"field — the template may only bind the declared field contract")
declaration["body"] = {"message_template": template, "fields": fields}
return declaration
def _validate_field(field: dict, position: int, seen_names: set[str], source: str) -> dict:
context = f"body fields[{position}]"
_expect_keys(field, ("name", "role", "domain", "cardinality", "unit", "constraint"),
context, source)
name = _required_scalar(field, "name", context, source)
if not _FIELD_NAME_PATTERN.fullmatch(name):
fail(source, None, f"{context}: field name {name!r} outside [a-z_][a-z0-9_]*")
if name in seen_names:
fail(source, None, f"{context}: duplicate field name {name!r}")
seen_names.add(name)
role = _required_scalar(field, "role", context, source)
if role not in _ROLES:
fail(source, None, f"{context}: role {role!r} is not one of {', '.join(_ROLES)}")
result: dict[str, object] = {"name": name, "role": role}
if role == "Categorical":
domain = _required(field, "domain", context, source)
if not isinstance(domain, list) or not domain:
fail(source, None,
f"{context}: Categorical requires `domain: [v1, v2, ...]` — the closed "
"vocabulary IS the structure (§2.3); an open categorical is an Identifier")
if len(set(domain)) != len(domain):
fail(source, None, f"{context}: domain values must be unique")
for forbidden in ("cardinality", "unit", "constraint"):
if forbidden in field:
fail(source, None, f"{context}: `{forbidden}:` is not a Categorical key")
result["domain"] = list(domain)
elif role == "Identifier":
domain = _required(field, "domain", context, source)
if domain != "open":
fail(source, None,
f"{context}: Identifier requires `domain: open` — a closed identifier "
"vocabulary is a Categorical by another name; declare it as one")
# §2.3 witness ruling (2026-07-21): an open domain is a DECLARED blind spot in the
# fence, and it must also declare its cardinality class, so the twin can still
# classify a cardinality mismatch as structural residual. No default — silence
# is impossible here for the same reason it is impossible for the ratification
# state (tooth-1 posture applied to the schema).
cardinality = _required_scalar(field, "cardinality", context, source)
if cardinality not in _CARDINALITIES:
fail(source, None,
f"{context}: cardinality {cardinality!r} is not one of "
f"{', '.join(_CARDINALITIES)} (declared class of the open domain)")
for forbidden in ("unit", "constraint"):
if forbidden in field:
fail(source, None, f"{context}: `{forbidden}:` is not an Identifier key")
result["domain"] = "open"
result["cardinality"] = cardinality
else: # Numeric
if "domain" in field:
fail(source, None,
f"{context}: Numeric declares `unit:` (+ optional `constraint:`), never a "
"`domain:` — its range is DYNAMICS and lives in the binding (§2.3)")
if "cardinality" in field:
fail(source, None, f"{context}: `cardinality:` is not a Numeric key")
unit = _required_scalar(field, "unit", context, source)
if unit not in _UNITS:
fail(source, None,
f"{context}: unit {unit!r} outside the closed set {{{', '.join(_UNITS)}}} "
"— grow the set deliberately with the entry that needs it, not by default")
result["unit"] = unit
constraint = field.get("constraint")
if constraint is not None:
if not isinstance(constraint, str) or not _CONSTRAINT_PATTERN.fullmatch(constraint):
fail(source, None,
f"{context}: constraint {constraint!r} outside the closed grammar "
"(`>= <non-negative-int>`)")
result["constraint"] = constraint
return result
# ── canonicalize + hash (§4.3 / §5.3 MUST 3) ─────────────────────────────────
# The declaration sub-keys a ratification can rate; the ratification key is `grain_hash`
# below, which covers the named grain's subtree PLUS `dialect:` (§4.6).
GRAINS = ("structure", "body")
def grain_hash(declaration: dict, grain: str) -> str:
"""sha256 over the canonical JSON of ONE grain of the declaration, PLUS the dialect.
§4.6: the freshness hash covers exactly the grain it measured, so editing the body
cannot revoke a structure ratification. Under the whole-entry hash it did — and that
is the fence punishing the very work its own residual compass points at, since the
body is edited precisely BECAUSE the twin measured it wrong.
THE DIALECT IS IN BOTH GRAINS, and leaving it out was a real hole in the first cut of
this function: `dialect:` sits at the declaration's top level, in neither grain subtree,
so `github` -> `jenkins` moved no grain hash at all and a GHA-measured ratification
survived onto a Jenkins declaration. A ratification is evidence gathered in ONE
dialect's corpus; no measurement transfers across that change, at either grain. The
whole-entry hash this replaced happened to cover it — replacing a coarse key with a
precise one must not quietly drop what the coarse one caught.
The entry NAME deliberately stays out: resolution is keyed on (name, grain) before the
hash is ever compared, so the hash has no identifying work to do, and a rename surfaces
as the LOUD stale-record fault rather than as a quiet hash mismatch.
"""
covered = {"dialect": declaration.get("dialect"), grain: declaration.get(grain)}
return canonical_hash(covered)
# ── manifest resolution (§4.2/§4.3 — teeth 2 and 3) ──────────────────────────
_MANIFEST_RECORD_KEYS = ("name", "grain", "declaration_hash", "corpus", "residual", "floor",
"study", "date")
def parse_manifest(text: str, source: str) -> list[dict]:
document = parse_subset_yaml(text, source)
if set(document) != {"ratification"}:
fail(source, None, "manifest root must be exactly `ratification:`")
ratification = document["ratification"]
if not isinstance(ratification, dict) or set(ratification) != {"entries"}:
fail(source, None, "manifest must be `ratification:` -> `entries:`")
entries = ratification["entries"]
if not isinstance(entries, list):
fail(source, None, "manifest `entries:` must be a sequence")
records: list[dict] = []
seen: set[tuple[str, str]] = set()
for position, record in enumerate(entries):
context = f"manifest entries[{position}]"
if not isinstance(record, dict):
fail(source, None, f"{context}: must be a mapping")
for key in record:
if key not in _MANIFEST_RECORD_KEYS:
fail(source, None, f"{context}: unknown key `{key}:`")
for key in _MANIFEST_RECORD_KEYS:
if key not in record or not isinstance(record[key], str) or not record[key]:
# A partial record is an instrument fault, not a warning: the manifest
# is measured evidence, and evidence with missing fields is no evidence.
fail(source, None, f"{context}: missing or empty `{key}:` (instrument fault)")
if record["grain"] not in GRAINS:
fail(source, None,
f"{context}: grain {record['grain']!r} outside the closed set "
f"{{{', '.join(GRAINS)}}} (§4.6)")
# (name, grain) is the key: one record per measured grain, so an entry may carry a
# Structure record and a Body record, and never two of either.
key = (record["name"], record["grain"])
if key in seen:
fail(source, None,
f"{context}: duplicate record for {record['name']!r} at grain "
f"{record['grain']!r}")
seen.add(key)
records.append(dict(record))
return records
def resolve_ratification(entries: dict[str, dict], records: list[dict],
manifest_source: str,
notices: list[str]) -> dict[tuple[str, str], dict | None]:
"""Per (entry name, grain): the matching Ratified record, or None (Authored).
The partition of §4.3 is unchanged in SHAPE and now runs per (name, grain) — §4.6:
* (name, grain) matches a declared grain, hash differs -> that GRAIN was EDITED;
it silently reverts to Authored (by design — an edited declaration is a new,
unmeasured claim). A notice says so; it is not an error. The entry's OTHER grain
is untouched, which is the whole point of the ruling.
* record name matches NO current entry -> a claim about something that no
longer exists: an INSTRUMENT FAULT. The register must fail loudly rather
than quietly assert a dead judgment.
* record names a grain the entry does NOT declare -> the same fault, one level
down. A Body record on a banner-only entry is evidence about a half that does
not exist; it cannot be "stale but harmless", because tooth 4 dispatches on it.
"""
resolution: dict[tuple[str, str], dict | None] = {
(name, grain): None
for name in entries
for grain in GRAINS
if entries[name].get(grain) is not None
}
for record in records:
name, grain = record["name"], record["grain"]
if name not in entries:
fail(manifest_source, None,
f"Ratified record for {name!r} matches no current declaration — a stale "
"record is an instrument fault (§4.3); regenerate the manifest from a "
"measured twin run")
if entries[name].get(grain) is None:
fail(manifest_source, None,
f"Ratified record for {name!r} claims grain {grain!r}, which that "
"declaration does not have — evidence about a half that does not exist is "
"an instrument fault (§4.6), not a stale-but-harmless row")
current_hash = grain_hash(entries[name], grain)
if record["declaration_hash"] == current_hash:
resolution[(name, grain)] = record
else:
notices.append(
f"{name} was Ratified at grain {grain} against "
f"{record['declaration_hash'][:12]}... but that grain's hash is now "
f"{current_hash[:12]}... -- the edit revoked ratification for THAT GRAIN "
"only; it is Authored until the twin re-measures it")
return resolution
# ── the link fence (ADR-17.D11) ──────────────────────────────────────────────
#
# A declaration naming `dialect: X` compiles into `insight::semantic::X::Dialect`. If the
# consuming target links no `insight_semantic_X` package, today's failure is a C++ error at a
# GENERATED line the author never wrote — *no member named `X` in namespace `insight::semantic`*
# — pointing at the projection instead of at the declaration that caused it.
#
# The fence closes that, and the SHAPE of the closure is the ruling. The build knows which dialect
# packages the target links; the declaration does not, and the generator must not guess. So CMake
# derives the set from its ONE list and hands it over as `--linked-dialects`, and the tool refuses
# in its own vocabulary, citing the declaration's file and line.
#
# THE RELATION IS CONTAINMENT, NEVER EQUALITY (declared ⊆ linked). The intent MEDIA import the
# same dialect packages for their own rendering with no declaration involved, so a dialect that is
# linked and undeclared is legitimate and is NOT refused. Closing that direction with an equality
# fence would red on a medium-only dialect.
#
# ABSENT OR EMPTY IS FATAL, NEVER A SKIPPED FENCE. A derived list reaches a tool through a build
# system that can hand it nothing — a renamed variable, a reordered `set()`, a target that stopped
# deriving it — and a fence that quietly disarms on an empty operand is worse than no fence, since
# the build stays green while the guarantee is gone (MEM:conan-malf-build-workflow: an absent input
# fails loudly and a WRONG one quietly; MEM:ci-vendoring-baseline-drift: a derived list needs a
# fatal empty case).
#
# ⚠ THE OPERAND IS A BUILD COORDINATE AND ENTERS NOTHING. Not the hash preimage (`grain_hash`
# covers `dialect:` plus one grain subtree and nothing this tool learns outside the document), and
# not one byte of the emitted file. Both are standing gates rather than intentions: the four
# pinned-value arms in `--selftest`, and the frozen projection byte-compare.
def parse_linked_dialects(raw: str | None) -> frozenset[str]:
"""The `--linked-dialects` operand: a comma-separated set of canon package suffixes."""
if raw is None:
fail("--linked-dialects", None,
"required: the set of canon semantic packages the consuming target LINKS, "
"comma-separated (e.g. `github,jenkins`). It is derived by the build, never "
"guessed here — and it is fatal rather than optional because a fence that "
"disarms when its operand goes missing leaves a green build with no guarantee")
members = [item.strip() for item in raw.split(",") if item.strip()]
if not members:
fail("--linked-dialects", None,
f"empty after parsing {raw!r} — a target that links no dialect package can "
"carry no declaration with a `dialect:`, so an empty set is a build-wiring "
"fault, not a valid configuration")
for member in members:
if not _DIALECT_PATTERN.fullmatch(member):
fail("--linked-dialects", None,
f"{member!r} is not a canon semantic package suffix ([a-z_][a-z0-9_]*) — "
"the operand names packages the way a declaration's `dialect:` does, so a "
"member the grammar could never match would fence nothing")
return frozenset(members)
def _refuse_unlinked_dialect(declaration: dict, text: str, source: str,
linked_dialects: frozenset[str]) -> None:
dialect = declaration.get("dialect")
if dialect is None or dialect in linked_dialects:
return
fail(source, key_line(text, source, "dialect"),
f"declares `dialect: {dialect}`, but logcraft/core links no "
f"`insight_semantic_{dialect}` package — add the conan requirement in "
f"`core/conanfile.py`, the `find_package` in `core/CMakeLists.txt`, and "
f"`import insight.semantic.{dialect};` in `src/scenario/intent_library.cpp`")
# ── discovery (§6 — the index is derived, never hand-maintained) ─────────────
def discover_entries(library_dir: Path, linked_dialects: frozenset[str]) -> dict[str, dict]:
"""Scan the flat library directory; every *.intent.yaml IS an entry (fail-closed)."""
entries: dict[str, dict] = {}
for path in sorted(library_dir.iterdir()):
if not path.name.endswith(INTENT_FILE_SUFFIX):
continue
entry_name = path.name[: -len(INTENT_FILE_SUFFIX)]
text = read_text(path, ascii_only=True)
document = parse_subset_yaml(text, str(path))
if "intent" not in document:
fail(str(path), None,
"a *.intent.yaml in the library directory must declare an `intent:` root "
"— strays are rejected, not skipped (the index is derived by content)")
declaration = validate_declaration(document, entry_name, str(path))
# The link fence runs HERE and not inside `validate_declaration`: that function is the
# GRAMMAR, shared in spirit with the dialect tool and answerable to the declaration alone.
# Which packages a target links is a build coordinate, and mixing the two would make the
# grammar unusable by any caller that does not have a build.
_refuse_unlinked_dialect(declaration, text, str(path), linked_dialects)
name = declaration["name"]
if name in entries:
fail(str(path), None, f"duplicate library identity {name!r} (K1)")
entries[name] = declaration
return entries
# ── C++ emission (deterministic; the shape the api partition defines) ────────
def _emit_field(entry_symbol: str, index: int, field: dict, out: list[str]) -> str:
"""Emit one field's backing array (if any); return the FieldContractView initializer."""
name = field["name"]
role = field["role"]
if role == "Categorical":
array_name = f"k{entry_symbol}Field{index}Domain"
values = ", ".join(f'"{_cpp_escape(v)}"sv' for v in field["domain"])
out.append(f"inline constexpr std::array<std::string_view, {len(field['domain'])}> "
f"{array_name}{{{values}}};")
domain = f"FieldDomain{{ClosedDomain{{std::span<const std::string_view>{{{array_name}}}}}}}"
elif role == "Identifier":
cardinality = "Bounded" if field["cardinality"] == "bounded" else "Unbounded"
domain = f"FieldDomain{{OpenDomain{{CardinalityClass::{cardinality}}}}}"
else: # Numeric
constraint = field.get("constraint")
if constraint is None:
minimum = "std::nullopt"
else:
minimum = f"std::int64_t{{{constraint.removeprefix('>= ')}}}"
domain = (f'FieldDomain{{NumericContract{{"{_cpp_escape(field["unit"])}"sv, '
f"{minimum}}}}}")
return f'FieldContractView{{"{_cpp_escape(name)}"sv, {domain}}}'
def _emit_ratification(out: list[str], record: dict | None, grain_enumerator: str,
ratified_names: list[str], name: str) -> None:
"""Emit ONE grain's Ratification, indented inside its grain view initializer.
The grain enumerator is emitted for BOTH phases: an Authored state names the grain it
fails to rate, which is what lets the concept check that a state is filed against the
half it actually measures.
"""
if record is None:
out.append(f" Ratification::authored(RatificationGrain::{grain_enumerator})")
return
ratified_names.append(name)
out.append(f" Ratification::ratified(RatificationGrain::{grain_enumerator},")
out.append(" RatificationEvidence{")
out.append(f' "{_cpp_escape(record["corpus"])}"sv, '
f'"{_cpp_escape(record["residual"])}"sv,')
out.append(f' "{_cpp_escape(record["floor"])}"sv, '
f'"{_cpp_escape(record["study"])}"sv,')
out.append(f' "{_cpp_escape(record["date"])}"sv}})')
def _emit_view_index(out: list[str], index_name: str, initializers: list[str],
lead_comment: str | None) -> None:
if lead_comment is not None:
out.append(lead_comment)
out.append("// a hand-maintained list here would be the rot pattern the design refuses.")
if not initializers:
out.append(f"inline constexpr std::span<const IntentLibraryEntryView> {index_name}{{}};")
return
storage_name = f"{index_name}Storage"
out.append(f"inline constexpr std::array<IntentLibraryEntryView, {len(initializers)}> "
f"{storage_name}{{")
for initializer in initializers:
out.append(f" {initializer},")
out.append("};")
out.append(f"inline constexpr std::span<const IntentLibraryEntryView> "
f"{index_name}{{{storage_name}}};")
def emit_cpp(entries: dict[str, dict], resolution: dict[str, dict | None],
source_names: list[str]) -> str:
out: list[str] = []
out.append("// GENERATED by intent_library_codegen.py -- DO NOT EDIT.")
out.append(f"// tool version: {TOOL_VERSION}, codegen_common: {CODEGEN_COMMON_VERSION}")
out.append("// (both enter this header, never the declaration hash -- sec 5.3 MUST 4)")
out.append("// BUILT, never committed (sec 5.2): a pure function of (the declaration set, this tool).")
out.append(f"// declarations: {', '.join(source_names) if source_names else '(none)'}")
out.append("// clang-format off")
out.append("")
out.append("namespace logcraft::core::intent_library_gen")
out.append("{")
out.append("")
out.append("using namespace std::string_view_literals;")
out.append("")
out.append("// The canon-kind vocabulary pin: if canon grows a kind, -Werror=switch breaks THIS")
out.append("// function on the next build, forcing the mirror (IntentKind + to_canon_kind) and the")
out.append("// library to grow in the same commit. The mirror cannot drift silently.")
out.append("[[nodiscard]] consteval bool canon_kind_is_pinned(insight::tokenization::IntentMarkerKind kind)")
out.append("{")
out.append(" switch (kind)")
out.append(" {")
out.append(" case insight::tokenization::IntentMarkerKind::None:")
out.append(" case insight::tokenization::IntentMarkerKind::Job:")
out.append(" case insight::tokenization::IntentMarkerKind::Step:")
out.append(" return true;")
out.append(" }")
out.append(" return false;")
out.append("}")
out.append("static_assert(canon_kind_is_pinned(insight::tokenization::IntentMarkerKind::None) &&")
out.append(" canon_kind_is_pinned(insight::tokenization::IntentMarkerKind::Job) &&")
out.append(" canon_kind_is_pinned(insight::tokenization::IntentMarkerKind::Step));")
out.append("")
out.append("// sec 3.2 -- the kind comes FROM the package: a declared kind must be one the")
out.append("// dialect's own reader rows declare. Consumed from canon's data; nothing mirrored.")
out.append("[[nodiscard]] consteval bool kind_declared_by(")
out.append(" insight::tokenization::IntentMarkerKind kind,")
out.append(" std::span<const insight::semantic::IntentMarkerRow> markers)")
out.append("{")
out.append(" for (const insight::semantic::IntentMarkerRow& row : markers)")
out.append(" {")
out.append(" if (row.kind == kind)")
out.append(" return true;")
out.append(" }")
out.append(" return false;")
out.append("}")
out.append("")
out.append("// sec 2.4 -- the payload CONTRACT must agree with the dialect's emit rows:")
out.append("// `Declared` needs a payload-bearing emit row for the kind, `None` a payload-less one.")
out.append("[[nodiscard]] consteval bool payload_contract_served(")
out.append(" insight::tokenization::IntentMarkerKind kind, PayloadContract contract,")
out.append(" std::span<const insight::semantic::IntentEmitRow> emits)")
out.append("{")
out.append(" for (const insight::semantic::IntentEmitRow& row : emits)")
out.append(" {")
out.append(" if (row.kind != kind)")
out.append(" continue;")
out.append(" const bool payload_bearing{row.emit != insight::semantic::PayloadEmit::None};")
out.append(" if (payload_bearing == (contract == PayloadContract::Declared))")
out.append(" return true;")
out.append(" }")
out.append(" return false;")
out.append("}")
out.append("")
view_initializers: list[str] = []
structure_ratified_names: list[str] = []
body_ratified_names: list[str] = []
for name in sorted(entries):
declaration = entries[name]
symbol = _cpp_symbol(name)
out.append(f"// ---- {name} ----")
field_views: list[str] = []
body = declaration.get("body")
if body is not None:
for index, field in enumerate(body["fields"]):
field_views.append(_emit_field(symbol, index, field, out))
if field_views:
out.append(f"inline constexpr std::array<FieldContractView, {len(field_views)}> "
f"k{symbol}Fields{{")
for view in field_views:
out.append(f" {view},")
out.append("};")
out.append(f"struct {symbol}Entry")
out.append("{")
out.append(f' static constexpr IntentIdentity identity{{"{name}"sv}};')
dialect = declaration.get("dialect")
structure = declaration.get("structure")
# Each declared half is emitted PAIRED with its own grain's state (§4.6). The state
# sits inside the optional, so a declared half without one, or a state for an
# undeclared half, is unrepresentable rather than merely unwritten.
if structure is not None:
out.append(" static constexpr std::optional<StructureGrainView> structure{")
out.append(" StructureGrainView{")
out.append(f" StructuralHalfView{{IntentKind::{structure['kind']}, "
f"PayloadContract::{structure['payload']}}},")
out.append(f' "{grain_hash(declaration, "structure")}"sv,')
_emit_ratification(out, resolution[(name, "structure")], "Structure",
structure_ratified_names, name)
out.append(" }};")
else:
out.append(" static constexpr std::optional<StructureGrainView> structure{std::nullopt};")
if body is not None:
fields_span = (f"std::span<const FieldContractView>{{k{symbol}Fields}}"
if field_views else "std::span<const FieldContractView>{}")
out.append(" static constexpr std::optional<BodyGrainView> body{")
out.append(" BodyGrainView{")
out.append(f' BodyContractView{{"{_cpp_escape(body["message_template"])}"sv, '
f"{fields_span}}},")
out.append(f' "{grain_hash(declaration, "body")}"sv,')
_emit_ratification(out, resolution[(name, "body")], "Body", body_ratified_names, name)
out.append(" }};")
else:
out.append(" static constexpr std::optional<BodyGrainView> body{std::nullopt};")
if dialect is not None and structure is not None:
out.append(f" using dialect = insight::semantic::{dialect}::Dialect;")
out.append("};")
out.append(f"static_assert(StructuralContract<{symbol}Entry>);")
if dialect is not None and structure is not None:
out.append(f"static_assert(insight::semantic::DialectIntent<{symbol}Entry::dialect>,")
out.append(f' "{name}: dialect package must pair reader-to-writer");')
out.append(f"static_assert(kind_declared_by(insight::tokenization::IntentMarkerKind::{structure['kind']},")
out.append(f" {symbol}Entry::dialect::markers),")
out.append(f' "{name}: kind is not declared by the {dialect} dialect");')
out.append(f"static_assert(payload_contract_served(insight::tokenization::IntentMarkerKind::{structure['kind']},")
out.append(f" PayloadContract::{structure['payload']},")
out.append(f" {symbol}Entry::dialect::emit_markers),")
out.append(f' "{name}: payload contract has no serving emit row");')
dialect_view = f'"{_cpp_escape(dialect)}"sv' if dialect is not None else '""sv'
view_initializers.append(
f"IntentLibraryEntryView{{{symbol}Entry::identity, {dialect_view}, "
f"{symbol}Entry::structure, {symbol}Entry::body}}")
out.append("")
# Index emission note: the indices are SPANS over conditionally-emitted storage arrays.
# An empty index must not become std::array<T, 0> — MSVC's array<T, 0> declares a real T
# element, and IntentLibraryEntryView is (deliberately, tooth 1) not default-constructible.
_emit_view_index(out, "kEntries", view_initializers,
"// The derived index (sec 6): discovered by content, sorted by name --")
out.append("")
out.append("// Tooth 4 -- the showcase surface is fail-closed BY CONSTRUCTION: these indices")
out.append("// are generated from hash-matched records only. An Authored grain is not in one;")
out.append("// there is nothing to filter and nothing to forget to filter. ONE INDEX PER GRAIN")
out.append("// (sec 4.6): an entry ratified structurally appears in the structure index alone,")
out.append("// so a body claim can never be reached by way of a structure one.")
ordered_names = sorted(entries)
structure_views = [view_initializers[ordered_names.index(name)]
for name in structure_ratified_names]
body_views = [view_initializers[ordered_names.index(name)] for name in body_ratified_names]
_emit_view_index(out, "kStructureRatifiedEntries", structure_views, None)
out.append("")
_emit_view_index(out, "kBodyRatifiedEntries", body_views, None)
out.append("")
out.append("} // namespace logcraft::core::intent_library_gen")
out.append("// clang-format on")
out.append("")
return "\n".join(out)
# ── generation driver ────────────────────────────────────────────────────────
# ── revocation reporting ──────────────────────────────────────────────────
#
# A revoked grain is the one outcome here that changes what the build SHIPS without failing
# it: the entry leaves that grain's ratified index, the showcase surface loses a row, and
# the exit stays 0 because an edited declaration is a new unmeasured claim rather than an
# error (§4.6 — that ruling stands; nothing below relaxes it). What the combination cost was
# READABILITY: the only trace was one stderr line written from inside a ninja edge, and in
# CI that is a line in a build log nobody opens.
#
# Under GitHub Actions the sentence is ALSO shaped as a `warning` workflow command. Additive,
# never a replacement, and that is the load-bearing choice: the runner CONSUMES a recognised
# command line out of the plain log, so emitting only the command would make the revocation
# vanish entirely from the log on any run where the panel is not read or the command is not
# recognised — strictly worse than the line it replaced. Emitting both is worse than the
# annotation alone by one duplicated line and better than every failure of it.
#
# Reach, measured and unmeasured, because the difference decides how far to trust this: the
# runner reads ONE byte stream per step and cannot tell a line written by the step's shell
# from one written by a descendant, and the line was measured surviving
# conan -> cmake -> ninja -> python -> tee unprefixed and at column 0, which is what the
# command grammar needs. The runner's RENDERING of it into the annotation panel is NOT
# established here and needs a real Actions run.
#
# The desk keeps the bare sentence: this tool runs on every logcraft build
# (logcraft/core/CMakeLists.txt), and workflow syntax where no runner consumes it is noise
# on a routine authoring action.
# No `:` and no `,`: those are the property delimiters a title would otherwise have to
# escape, and the selftest holds the constant to that shape rather than trusting it.
_REVOCATION_WARNING_TITLE = "Intent ratification revoked"
def workflow_command_escape(message: str) -> str:
"""Escape a workflow-command MESSAGE for the Actions runner.
`%` is substituted FIRST: it is the escape character, so doing it after CR/LF would
re-escape the `%` this function had just written and the runner would render the
literal `%0A` instead of a newline.
"""
return message.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
def report_revocations(notices: list[str], *, in_github_actions: bool,
stream=sys.stderr) -> None:
"""Print each revocation; under Actions add the annotation form on the SAME stream.
One stream for both forms so the pair stays adjacent and ordered — across two streams
the runner may interleave them and the plain sentence stops reading as the annotation's
twin.
"""
for notice in notices:
print(f"notice: {notice}", file=stream)
if in_github_actions:
print(f"::warning title={_REVOCATION_WARNING_TITLE}::"
f"{workflow_command_escape(notice)}", file=stream)
def running_in_github_actions() -> bool:
"""The runner sets GITHUB_ACTIONS=true; nothing else in the workspace sets it."""
return os.environ.get("GITHUB_ACTIONS") == "true"
def generate(library_dir: Path, manifest_path: Path | None, out_path: Path,
linked_dialects: frozenset[str]) -> int:
entries = discover_entries(library_dir, linked_dialects)
notices: list[str] = []
records: list[dict] = []
if manifest_path is not None and manifest_path.exists():
records = parse_manifest(read_text(manifest_path, ascii_only=True),
str(manifest_path))
resolution = resolve_ratification(entries, records, str(manifest_path), notices)
source_names = [f"{name}{INTENT_FILE_SUFFIX}" for name in sorted(entries)]
rendered = emit_cpp(entries, resolution, source_names)
report_revocations(notices, in_github_actions=running_in_github_actions())
out_path.parent.mkdir(parents=True, exist_ok=True)
if out_path.exists() and out_path.read_bytes() == rendered.encode("ascii"):
return 0 # unchanged — do not touch the file (no rebuild churn)
with open(out_path, "w", encoding="ascii", newline="\n") as handle:
handle.write(rendered)
return 0
# ── selftest (synthetic fixtures ONLY — the moat rule) ───────────────────────
_SYNTHETIC_ENTRY = """\
intent:
name: synthetic.demo
dialect: github
structure:
kind: Step
payload: Declared
body:
message_template: "{verb} {object}"
fields:
- name: verb
role: Categorical
domain: [alpha, beta]
- name: object
role: Identifier
domain: open
cardinality: unbounded
- name: tally
role: Numeric
unit: count
constraint: ">= 0"
"""
_SYNTHETIC_GENERIC = """\
intent:
name: synthetic.generic
body:
message_template: "plain {token}"
fields:
- name: token
role: Categorical
domain: [one, two]
"""
# The fixture the hash PINS below are taken on. It is `_SYNTHETIC_ENTRY`'s semantic content
# carrying, in addition, the five lexical constructs the SHIPPED declarations use and the bare
# fixture does not — measured against `logcraft/core/library/*.intent.yaml`, not guessed:
# a column-0 comment, an indented full-line comment, a trailing comment after a value, a blank
# line inside a block map, and a trailing comment after a flow sequence. A pin taken on a fixture
# that exercises fewer front-end paths than the real declarations is green over exactly the
# refactor that moves a real hash, which is the whole failure this gate exists to catch.
#
# It is SYNTHETIC, and that is a moat rule rather than a style one: this tool is public and the
# intent declarations it projects are private (logcraft). No shipped declaration's content is
# copied here — see the structure-grain congruence argued at the pin itself.
_HASH_PIN_ENTRY = """\
# A column-0 comment, because every shipped declaration opens with one.
intent:
name: synthetic.pinned
dialect: github # a trailing comment after a value
structure:
kind: Step
payload: Declared
body:
# An indented full-line comment.
message_template: "{verb} {object}"
fields:
- name: verb
role: Categorical
domain: [alpha, beta] # a trailing comment after a flow sequence
- name: object
role: Identifier
domain: open
cardinality: unbounded # a continuation comment, indented past its key
- name: tally
role: Numeric
unit: count
constraint: ">= 0"
"""
# The structure grain of the SHIPPED `github.step`, spelled out as the canonical-JSON preimage
# `grain_hash` actually hashes. Sixty-nine bytes, every token of it public vocabulary: the grain
# covers `dialect:` plus the `structure:` subtree and nothing else — never the entry name, never
# the body. That is why the pin below can name the shipped hash inside a public tool without any
# private declaration content crossing the repo boundary.
_GITHUB_STEP_STRUCTURE_PREIMAGE = (
'{"dialect":"github","structure":{"kind":"Step","payload":"Declared"}}')
# THE VALUE THAT MUST NOT MOVE. `logcraft/core/library/ratification.manifest.yaml` keys its one
# record on this hash, and `resolve_ratification` matches records to entries BY it. The surface is
# fail-closed: when a hash stops matching, nothing errors — the record simply stops resolving, the
# entry drops out of `kStructureRatifiedEntries`, and the twin's ratified claim surface goes empty
# with a build that is still green. A front-end refactor that moves it therefore RETRACTS A CLAIM
# IN SILENCE, which is why the pin is a standing assertion here and not a step in a checklist.
_GITHUB_STEP_STRUCTURE_HASH = \
"8eb81dca10a02d8434d779764dbacf8b36a29ed2ccfc12126b72a4ed633db1c1"
# The same pin one grain over. The body grain is where the rich front-end paths land — the quoted
# template and its placeholders, the flow-sequence domain, the open-domain cardinality, the
# numeric unit and the quoted constraint — none of which the structure grain touches. Its value is
# the FIXTURE's own: a shipped body grain is private content and is not reproduced here, so this
# arm pins front-end invariance rather than a shipped record's key.
_PINNED_BODY_HASH = "d111dedf7b6fa8f7b5d05e6a82428e47b78055e8cdf9907b5ccc2bb686bf4ad9"
def _selftest_case(label: str, failures: list[str], check) -> None:
try:
check()
except AssertionError as error:
failures.append(f"{label}: {error}")
except DeclarationError as error:
failures.append(f"{label}: unexpected rejection: {error}")
def _expect_rejection(label: str, failures: list[str], needle: str, thunk) -> None:
try:
thunk()
except DeclarationError as error:
if needle not in str(error):
failures.append(f"{label}: rejected, but message lacks {needle!r}: {error}")
return
failures.append(f"{label}: accepted a declaration the fence must refuse")
def _parse_entry(text: str, name: str = "synthetic.demo") -> dict:
return validate_declaration(parse_subset_yaml(text, "<selftest>"), name, "<selftest>")
def _pin_arms(pinned: dict, base_structure_hash: str, base_body_hash: str,
failures: list[str]) -> None:
"""The four hash PINS, homed here so a fixture rejection reports once and skips them all."""
_selftest_case("pin: the fixture's structure grain IS github.step's", failures,
lambda: _assert(
json.dumps({"dialect": pinned.get("dialect"),
"structure": pinned.get("structure")},
sort_keys=True, separators=(",", ":"), ensure_ascii=True)
== _GITHUB_STEP_STRUCTURE_PREIMAGE,
"the pinned fixture's structure grain no longer canonicalizes to the "
"shipped github.step preimage, so the hash pin below has quietly stopped "
"guarding the shipped record. Restore the fixture — do NOT re-baseline the "
"constant, which would unpin the gate while leaving it green"))
_selftest_case("pin: github.step's structure hash has not moved", failures, lambda: _assert(
grain_hash(pinned, "structure") == _GITHUB_STEP_STRUCTURE_HASH,
"THE FRONT END MOVED A DECLARATION HASH. Expected "
f"{_GITHUB_STEP_STRUCTURE_HASH}, got {grain_hash(pinned, 'structure')}. This is not a "
"fixture failure: it is the key ratification.manifest.yaml resolves on, and the register "
"is FAIL-CLOSED, so shipping this moves github.step out of kStructureRatifiedEntries and "
"empties the twin's ratified claim surface with nothing red. Revert the front-end change; "
"a hash may only move when a DECLARATION is edited (ADR-17.D11, its Boundary)"))
_selftest_case("pin: the body grain's hash has not moved", failures, lambda: _assert(
grain_hash(pinned, "body") == _PINNED_BODY_HASH,
"the front end moved the BODY grain hash — the template, the flow-sequence domain, the "
"open-domain cardinality, the numeric unit or the quoted constraint canonicalizes "
f"differently. Expected {_PINNED_BODY_HASH}, got {grain_hash(pinned, 'body')}"))
_selftest_case("pin: comments and blank lines stay OUT of the hash", failures,
lambda: _assert(
grain_hash(pinned, "structure") == base_structure_hash
and grain_hash(pinned, "body") == base_body_hash,
"the five lexical constructs the shipped declarations carry — column-0 and "
"indented comments, a trailing comment after a value and after a flow "
"sequence, a blank line in a block map — must be transparent to the hash; "
f"pinned={grain_hash(pinned, 'structure')}/{grain_hash(pinned, 'body')} vs "
f"bare={base_structure_hash}/{base_body_hash}"))
def selftest() -> int:
failures: list[str] = []
base = _parse_entry(_SYNTHETIC_ENTRY)
base_structure_hash = grain_hash(base, "structure")
base_body_hash = grain_hash(base, "body")
# ── tooth 3: canonicalize-then-hash (MUST 3), asserted on the LIVE ratification key ──
# These four properties were first asserted on the entry-level canonical hash, which was
# ripped with `IntentIdentity::declaration_hash` — a gate aimed at a dead surface guards
# nothing, so they now exercise `grain_hash`, the key `resolve_ratification` compares.
reformatted = _SYNTHETIC_ENTRY.replace(" name: synthetic.demo",
" # a comment\n name: 'synthetic.demo'")
reordered = _SYNTHETIC_ENTRY.replace(
" name: synthetic.demo\n dialect: github\n",
" dialect: github\n name: synthetic.demo\n")
_selftest_case("hash: reformat invariant", failures, lambda: _assert(
grain_hash(_parse_entry(reformatted), "structure") == base_structure_hash
and grain_hash(_parse_entry(reformatted), "body") == base_body_hash,
"a reformat moved a grain hash — authors would avoid touching files (MUST 3)"))
_selftest_case("hash: key-order invariant", failures, lambda: _assert(
grain_hash(_parse_entry(reordered), "structure") == base_structure_hash
and grain_hash(_parse_entry(reordered), "body") == base_body_hash,
"mapping key order moved a grain hash"))
edited = _SYNTHETIC_ENTRY.replace("domain: [alpha, beta]", "domain: [alpha, gamma]")
_selftest_case("hash: edit revokes — and only its own grain", failures, lambda: _assert(