Skip to content

feat: dual-z writer — local z on new tags/taggings (tag-federation Half 2 Part B)#311

Merged
ark-clawds4 merged 5 commits into
feat/b-tag-primitivefrom
feat/dual-z-writer
Jun 18, 2026
Merged

feat: dual-z writer — local z on new tags/taggings (tag-federation Half 2 Part B)#311
ark-clawds4 merged 5 commits into
feat/b-tag-primitivefrom
feat/dual-z-writer

Conversation

@vcavallo

Copy link
Copy Markdown
Collaborator

TL;DR

The dual-z writer — Part B of Half 2. Makes every new tag element and tagging assertion stamp a second z tag (the local 39998:<runtime instance TA>:<slug>) alongside the existing canonical 39998:82b75e47…:<slug>. The canonical z keeps the event network-visible (Half-1 federates on it); the new local z lands it in this instance's own concept list so locally-authored activity actually shows up locally — instead of the local list staying empty while the tags are only visible network-wide.

Stacked PR — base is feat/b-tag-primitive (PR #310), not staging. Merge this into #310 first, then #310 → staging. Story 3 depends on Story 38 (the b-tag primitive) + Story 2 (the seeds) which live in #310; basing here keeps the diff to Story-3-only.

For an AI agent: read engineering-team/decisions/tag-federation/0003-dual-z-writer.md (the W11 design). This completes the dual-z model — #310 ships the map (each local concept header → canonical, via the pointer-b); this ships the membership (new events carry the local z so they join the local list).


What this PR does

Implements tag-federation Story 3 / ADR 0003 — the W11 stamping floor: a published tag/tagging carries its canonical (required) z plus a local z; z order is not load-bearing (readers #z-union per value). Scoped to the tag stack's two coordinates — no cloud/cap/rotation (those stay resolver-gated in community-reference ADR 0033).

Changelog

  • ui/src/utils/publishProfileTag.js (tagging-assertion writer) — +localTaPubkey param; appends a local z 39998:${localTaPubkey}:nostr-user-tag after the unchanged canonical NOSTR_USER_TAG_HANDLE. Additive — the ADR-0022 hybrid e+a shape (['a', tagAddress], ['e', tag.eventId], tagAddress = 39999:${tag.authorPubkey}:${tag.slug}) is untouched.
  • ui/src/hooks/useProfileTags.js — reads the runtime TA via useConfig().taPubkey; threads localTaPubkey into the assertion writer; stamps the same local z in createTag (the tag-element writer).
  • ui/src/pages/Tag.jsx — reads taPubkey; threads localTaPubkey into both handleApply and handleDispute.

Guarantees

  • Anti-hardcode (AC-6): the canonical z stays the ADR-0015 literal (the sanctioned named exception); the local z is composed from the runtime taPubkey — no new pubkey literal introduced (each writer still has exactly one 82b75e47…, the pre-existing const).
  • Non-fatal guard: a missing/malformed local TA warns and omits the local z (the canonical z still ships) — it never throws/blocks a publish. (Confirmed design choice.)
  • No migration (AC-5): existing single-z events are not rewritten; they stay canonical-only and network-visible.

Testing

  • test/dual-z-writer.test.js14/0 (source-contract over the two ESM writers + the call sites; registered in test/test.js).
  • Regression: the ADR-0022 hybrid-e+a suite stays 10/0 (the second z is additive, not a replacement); profile-tag-polish 11/0.
  • UI builds clean (vite).

Live verification deferred to a deployed env

AC-4 (a new tagging lands in the instance's local list) + the distinct-z behavior are not verified here: on the dev box the local TA equals the canonical coordinate (82b75e47…), so the two z's resolve to identical strings — the source-contract assertion of record is "two z entries emitted, the second runtime-sourced," not "two distinct values." The distinct-value case (and local-list population) manifests only on a non-dev instance, so it's left for whoever merges to a deployed env to confirm.


⚠️ Addendum for David — the dual-z shape

This PR makes each new Tag and Tagging carry TWO z tags: canonical (82b75e47…, network identity, unchanged) + local (the runtime instance TA, NEW, for the local concept list). Together with #310's one pointer-b per header (local → canonical), that's the full dual-z model: two z's per event, one b per concept header.

This is where your "the new Tags and Taggings need to have two z-tags in them, not just one" lands — confirmed. (Open question still flagged in #310: you mentioned "two b-tags" on the concepts; we ship one pointer-b per header per the wire spec — see #310's addendum for the reversal breadcrumb if you want a different shape.)

Reversal breadcrumb: removing the second ['z', …] line from each writer (publishProfileTag.js, useProfileTags.js createTag) reverts to single-z behavior with no data migration.

🤖 Generated with Claude Code

vcavallo and others added 5 commits June 17, 2026 20:25
…ory 3, Part B)

Stacked on feat/b-tag-primitive (PR #310). Story 3 = the dual-z writer:
new tags/taggings carry both canonical z (network visibility) and local z
(local concept-list membership). W11 stamping practice designed in the
Architecture phase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…deration 0003)

W11 floor applied: every new tag element + tagging assertion carries two z
tags — canonical (82b75e47 literal, unchanged, Half-1 federates on) + local
(runtime taPubkey, new, lands it in the instance's local concept list). Added
inline at the two client writers; taPubkey threaded as an explicit arg (pure
functions). Additive to the ADR-0022 hybrid e+a; no migration; client-only;
non-throwing guard on missing local TA. Resolves OQ-1..4. No firmware change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
14 source-contract tests over the two ESM writers (7 red / 7 stay-green
baseline). Asserts each writer emits canonical z (unchanged const) + a NEW
runtime-interpolated local z (39998:${taPubkey}:<slug>), exactly two z
entries (dev-box-degenerate-proof: assert two emitted, not that they differ),
the ADR-0022 e+a shape intact (regression guard), no new hardcoded literal
for the local handle, the localTaPubkey param + threading from both call
sites, and a non-throw tripwire. AC-4/AC-5 live-only (Playwright recipe).
Registered in test/test.js (4 points).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aggings (tag-federation story 3)

Implements ADR 0003. Each new tag element (createTag) + tagging assertion
(publishProfileTagAssertion) now stamps a SECOND z: the local
39998:${runtime taPubkey}:<slug>, alongside the unchanged canonical
39998:82b75e47…:<slug> (ADR-0015 literal). Lands locally-authored activity
in the instance's own concept list while staying network-visible.

- publishProfileTag.js: +localTaPubkey param; local z via conditional spread;
  non-fatal warn+omit on missing/malformed local TA (never throws — a missing
  local z has no network cost, canonical still ships).
- useProfileTags.js: read taPubkey via useConfig; thread localTaPubkey into the
  assertion writer; same local z + guard in createTag.
- Tag.jsx: read taPubkey; thread localTaPubkey into handleApply/handleDispute.
- Additive to the ADR-0022 hybrid e+a (regression-guarded; that suite stays 10/0).

ALSO tightened the SOFT non-throw test in dual-z-writer.test.js: its 220-char-
window heuristic false-positived once localTaPubkey entered the signature (right
before the pre-existing window.nostr/authorPubkey throws). Refined to the precise
forbidden pattern — an if(…localTaPubkey…){…throw} guard. (Flagged for review.)

Tests: dual-z-writer 14/0; UI builds clean; no regression. Live AC-4/AC-5
(local list populates) = Playwright/manual recipe per the test plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Canonical + local z on new tags/taggings (W11). Gates: dual-z 14/0,
hybrid e+a regression 10/0, profile-tag-polish 11/0, UI build clean.
Anti-hardcode confirmed (local z runtime, one canonical literal/file).
SOFT non-throw test precision fix judged legitimate (removes a
signature-induced false-positive; still catches a genuine throw-guard).
AC-4/AC-5 deferred to deployed-env; AC-7 breadcrumb for the PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ark-clawds4

Copy link
Copy Markdown
Collaborator

Claude 4.8 did a 20 minute review on PR 311 and 310 and recommends OK to merge both. Good enough for me.

@ark-clawds4
ark-clawds4 merged commit 2714997 into feat/b-tag-primitive Jun 18, 2026
@ark-clawds4
ark-clawds4 deleted the feat/dual-z-writer branch June 18, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants