agent: builder model: opus max_turns: 60 budget_usd: 15 —

Brief: #928 step 2b (properties), yaml: anchors, tags, aliases, from the y2 draft

Issue (#928, copied)

The owner’s direction: an official package yaml with a zero-copy tree pointing into the input, spans, safety limits on by default (alias expansion, depth, size), YAML 1.2 core schema, tags kept, a duplicate key an error, << not merged by itself. Every gap in the compiler, lowering or std is a finding fixed in the compiler, never routed around in the package.

Read first: docs/specification/adr/adr-yaml-1.md (D2-D5 and D8 for this brief), then packages/yaml/src/main.nika and its tests as the previous brief (docs/briefs/yaml/y1-tree-core.md, its report beside it) left them. Its Facts the architect probed hold here too: input ref Bytes, rule WS = "" -> { }, the tree built inside the grammar’s actions (#1000), entries as a struct (#999). Also read y5-escapes-and-radix.md (the brief before this one): its Facts the last builder paid for hold here, and the Scalar shape it left (a double-quoted scalar’s value decoded by the grammar, every other scalar’s value a view, ADR-yaml-1 D4 amended, D11).

The check happens once, where the knowledge is (owner, 2026-10-30; ADR-yaml-1 D5’s last paragraph, D11): what the grammar can check (a block scalar header, a tag’s shape, a %YAML version) it checks with a guard, so a mistake is a parse error with line and column, and no later function checks it again.

Block scalars (y2 step 1) are on develop (9d92e474). This brief builds y2’s step 2, which stopped on #1026 (a view field read from a struct a grammar label binds was refused, NK1106); y8-1026-grammar-label-struct.md fixed it (555fe6e9; a field bound by a struct pattern in a match keeps its tier too). A label on a one-element group, n:(P), works since #1027 (y9, 9293d338); a label on a literal returned or stored binds its text since #1030 (y10, 24751a95); a call in a guard is a call site since #1032 (y11) and a guard reads a binding text(..) label since #1033 (y12); the draft’s text(..) captures stay as they are.

Start from the draft: branch origin/wip/y2-properties (ea068fa4) holds the step’s main.nika on top of the pre-rebase step 1 (9fe7210f). Take it with git fetch origin wip/y2-properties && git diff 9fe7210f ea068fa4 -- packages/yaml/src/main.nika | git apply (9d92e474 has the same content as 9fe7210f, rebased). Its shape: struct Props { start, end, anchor: String?, tag: String? } built by rule Props -> Props, copied onto the Node in Propped and Content; a TagKind handed to the scalar rules so a core tag fixes and checks the kind in the grammar (fits_node, empty_fits). Keep that shape; finish and test it. The y2 report and debrief are beside the old brief (y2-tree-properties.md.report.md, .debrief.md).

Pre-flight (first turn, before the draft): check the winnow-grammar rev of target/fast/nikaia (docs/builder-facts.md, first trap), then run the tests of #1032 (tests/compiler/tests/grammar_a_call_in_a_guard_is_a_call_site.nika, y11), #1033 (tests/compiler/tests/grammar_a_guard_reads_a_binding_text_label.nika, y12) and #1037 (tests/compiler/tests/a_dependencys_type_with_two_view_fields_keeps_its_places.nika, y13, a use compiler_tests test: run it with nikaia test --project tests/compiler > out.txt 2>&1 and grep its line, or rebuild its probe as a two-file scratch package). If one does not pass, build cargo build --profile fast -p nikaia once (the binary may predate it) and run again; still failing: stop, Result: blocked, Found: that issue not fixed. Then apply the draft and run target/fast/nikaia check packages/yaml/src/main.nika and the package tests before writing a line.

Run 1 (y2c-properties.md.run1.report.md, its debrief beside it) stopped on exactly those two defects: the draft applied cleanly onto 9d92e474, nikaia check refused it at is_float (NK1185, #1032: the guards’ fits_node calls were no call sites), and with that call neutralised every package test failed in rustc on Double’s guard reading s (#1033). A call in a guard and a guard over a binding text(..) are separate cases for the text-tier pass and the guard lowering: if a new refusal appears, probe the guard alone first.

Between the runs the architect applied the draft onto today’s develop (after y11, y12): nikaia check packages/yaml/src/main.nika passes, and nikaia test --project packages/yaml failed 9 of 10 in rustc, every one at a test’s fn show(n: ref yaml::Node): Struct takes 3 lifetime arguments but 1 lifetime argument was supplied (the draft’s Node has three view fields, anchor, tag, value, so three places, ADR-353 D7), then bool cannot be dereferenced and str unsized at its match n.value. That is #1037, fixed by y13-1037-dependency-places.md before this run. Anything else rustc or the checker refuses now is new: probe it alone (a two-file package, as #1037’s) and stop on it (Found:).

Run 2 (y2c-properties.md.run2.report.md, its debrief y2c-properties.run2.debrief.md) built the step: the draft applied, nikaia check passed, the package tests 11/11 with the new tests/anchors_tags_aliases.nika (outputs checked by hand against YAML 1.2.2). Only the package gate’s fmt::every_nika_file was red: #1015, nikaia fmt broke the 9 rule lines over 100 columns holding if(call(..), "label") into text that does not parse. y15-1015-fmt-guard-label.md fixed it before this run. The finished work is on branch origin/wip/y2c-properties (b223f972, on develop fbba5562).

Run 3 (this run) starts from run 2’s branch, not from the y2 draft: the paragraph Start from the draft above and run 1’s history are background only. Steps:

  1. Pre-flight: the winnow-grammar rev (builder-facts, first trap); check that y15’s test tests/compiler/tests/fmt_keeps_a_grammar_guard_whole.nika exists on develop and target/fast/nikaia is newer than its commit (else cargo build --profile fast -p nikaia once). Missing: stop, Result: blocked, Found: #1015 not fixed.
  2. The main checkout holds run 2’s leftovers packages/yaml/nikaia.derived and nikaia.lock (modified, uncommitted; this lane’s own): restore exactly those two by name (git checkout -- packages/yaml/nikaia.derived packages/yaml/nikaia.lock), nothing else.
  3. git fetch origin wip/y2c-properties && git diff fbba5562 b223f972 -- packages/yaml/src packages/yaml/tests | git apply.
  4. target/fast/nikaia fmt packages/yaml/src/main.nika packages/yaml/tests/anchors_tags_aliases.nika first, then nikaia check, the package gate (both parts), then commit packages/yaml/src/main.nika, the new test and its .stdout, and the ledger files the test run rewrote (nikaia.contracts, nikaia.derived, nikaia.lock, nikaia.proofs where changed, as earlier package commits did), message Part of #928.

If fmt still writes something that does not parse, or rustc/the checker refuses anything, that is new: probe it alone and stop (Found:), never a shorter rule picked to dodge it.

A separate defect, not needed by the draft: #1038 (a function returning a view field of a yaml type beside an owned text is refused, NK1104; the package’s tests return f"..." in every arm, which it does not touch). If what you write meets it, stop and report it (Found: #1038); never a shape chosen to avoid it (CLAUDE.md, Fix, do not work around).

Facts the y2 builder paid for: several named options in a rule call are Block(n; c: c, k: x) (Part II 10); a package test’s loop is nikaia test --project packages/yaml --bless then nikaia fmt FILE; a one-letter struct name (S) in a probe is read as a type parameter.

To build (one commit)

  1. Properties and aliases. Anchors &a, aliases *a (a Value::Alias node, not resolved: ADR-yaml-1 D6), tags: !local, !!str and the other core tags (they fix the kind, D5: !!str 12 is a string, !!int "12" an int; a core tag that fixes a kind checks the scalar’s shape there, in the grammar, so !!int "x" is an error at its position and as_i64 still checks nothing per digit), !<tag:yaml.org,2002:str>, a named handle !e!x (kept as written; %TAG resolution is the next brief’s). Properties before a block collection on the line above, and on an empty node. Test tests/anchors_tags_aliases.nika.

The expected .stdout is what YAML 1.2.2 says; the yaml-test-suite case for a construct is the reference (read only, nothing copied: ADR-yaml-1 D9 waits for the owner); name it in a comment.

Explicit keys, directives and Limits are the next brief, y2b-keys-directives-limits.md: not this run.

Done when

target/fast/nikaia test --project packages/yaml passes (the earlier tests included); the package gate below is green; the step committed and pushed; its message says Part of #928.

Rules (CLAUDE.md; this block is pasted into every brief, also a fix-forward)

Report (at most 15 lines, to exactly /home/user/Nikaia/docs/briefs/yaml/y2c-properties.md.report.md)

Fields of docs/architect.md, Briefs and reports: Result: done|noop|partial|blocked (the bare word alone on that line: Result: done, never a summary after it, which cuts the batch, b869a 2026-10-27; Owner: is a question for the owner or none, never a role), Files, Commit, Body (optional, one paragraph for the commit message), Fixes, Pushed, Tests, Gate (green|red), Deviation, Found, Needs, Changelog, Owner, Disk freed, and for a move Rs2nika: (the kept N of M lines of rs2nika --score, then the three costliest hand edits; none for a task that moves no Rust, none (redesign) for a brief with Shape: redesign and no Convert: line). none where nothing, and then the bare word: Deviation: none (a note) cuts the batch (batch.nika compares exactly); a note goes into the commit message. Found: is only for a defect outside your change (it cuts the batch); a process event such as another session resetting or rewriting your files is no Found:, it goes under Needs: (m3a 2026-10-28); a converter draft that came out wrong is no Found:, it is one of the costliest edits under Rs2nika:, which the converter lane reads (e12-4b 2026-10-27); how you solved your own step goes into the commit message. Deviation: is only a step of the brief built otherwise than written or not built (a choice the brief left to you, such as keep what X needs and say so, is no deviation: it goes into the commit message; nor is building two steps in one commit when every step is built, e12-3a 2026-10-27, nor a step another commit on develop already built, shown by your test failing with that commit’s change reverted, b889 2026-10-27: name the commit in Body:, nor a test’s expected .stdout/.stderr taken from what the program really prints where the brief’s sketch of it was short or wrong, b934 2026-10-29: say so in Body:, nor a lowering that follows the ADR governing it where the brief’s expected lowering contradicts that ADR, b933 2026-10-29: name the ADR in Body:, nor a lowering other than the brief’s where rustc cannot take the brief’s, b1029 2026-10-31: say why in Body:, nor re-keying an allow-list entry (KNOWN) whose hash changed for the same program and the same reason, b-fixforward-sweep2 2026-10-28: say so in Body:, nor an existing test’s expected .stdout/.stderr updated because your change rightly changes the message it prints, b1012b 2026-10-31: name the test in Body:); how you verified (e.g. a test not run against the unfixed compiler) goes under Tests:. A noop says why under Tests: (what was probed), not under Deviation:. Final answer, one line: done <sha>, report <path> (or BLOCKED: ... / PUSH REJECTED ...).

Attribution: Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01UVxJDveRM5qKZKyCqq6Y24

This site is open source. Improve this page.