Skip to content

feat: materialize canonical foaf:name per agent into trust + spaces graphs (#62)#90

Merged
tkuhn merged 1 commit intomainfrom
feature/62-mirror-foaf-name
Apr 27, 2026
Merged

feat: materialize canonical foaf:name per agent into trust + spaces graphs (#62)#90
tkuhn merged 1 commit intomainfrom
feature/62-mirror-foaf-name

Conversation

@tkuhn
Copy link
Copy Markdown
Contributor

@tkuhn tkuhn commented Apr 27, 2026

Status

Draft — depends on nanopub-registry#113, which adds the per-account `name` + `nameCreatedAt` fields to the trust-state snapshot JSON. The schema parser is forward-compatible so this PR is safe to merge before the registry change ships (consumers running against an older registry simply produce no `foaf:name` triples), but the live effect only kicks in once both are deployed.

Pairs with nanopub-query#89 (observer-tier permissive grants) — that PR's worked-example query in `design-space-repositories.md` will need a small follow-up doc update to drop the `SERVICE <…/full>` clause, which is intentionally not in this PR (the doc edit only makes sense after #89 lands).

Summary

Consumes the per-account `name` + `nameCreatedAt` fields from the trust-state snapshot and folds them, at materialisation time, into one canonical `foaf:name` triple per approved agent — emitted in the trust-state named graph and mirrored into each new spaces state graph at full-build time.

Result: any consumer reading the trust or spaces state graph gets `?agent foaf:name ?n` directly, with no cross-repo `SERVICE` to `/repo/full`.

Resolution policy

The two layers were decided up front (see PR #89's discussion) to keep the on-disk schema minimal:

  • Per-`(agent, pubkey)` (registry, #113): keep the name from the intro with the latest `dct:created`. First write wins when no current timestamp exists; subsequent writes replace iff strictly newer. Multi-`foaf:name` on a single intro: `MIN(?name)` lex tiebreak for determinism.
  • Per-agent across keys (this PR, `TrustStateLoader.resolveCanonicalNames`): pick the row with `MAX(ratio)` from the agent's approved (`loaded` / `toLoad`) account rows; ties → `MIN(name)` lex. Agents with no qualifying row are simply absent from the result map (no triple emitted).

Composed: "the most-trusted key's freshest declaration".

Changes

Parser — `TrustStateSnapshot`

  • `AccountEntry` extends from 7 to 9 fields, adding optional `name` (`String`) and `nameCreatedAt` (`Instant`).
  • New `unwrapDateNullable` handles MongoDB extended JSON (`{"$date": "…"}` or `{"$date": {"$numberLong": "…"}}`), plain ISO-8601 strings, plain numeric epoch-ms, and JSON `null`/missing — so the parser works against any reasonable registry serialiser and against pre-#113 registries (where the fields are absent).

Resolver — `TrustStateLoader`

  • New static `resolveCanonicalNames(snapshot)` returning `Map<agent, name>`. Filters to `APPROVED_STATUSES` (`loaded` / `toLoad`), skips rows with null `ratio` or null `name`, picks `MAX(ratio)` per agent, breaks ties on lex-min name.
  • `materialize` now emits one ` foaf:name "…"` triple in the trust-state named graph per resolved agent, after the existing `AccountState` rows.

Mirror — `AuthorityResolver.mirrorTrustState`

  • After copying approved `AccountState` rows from the trust state graph into the new spaces state graph, additionally walks all `?subj foaf:name ?obj` triples in the trust-state graph and copies them too. The trust loader only emits names for approved agents, so no extra filter is needed here.

Tests

  • `TrustStateLoaderTest` (+5): `resolveCanonicalNames_picksRowWithMaxRatio`, `_breaksTiesOnLexMinName`, `_skipsUnapprovedRows`, `_omitsAgentsWithNoName`, `_acceptsToLoadStatusToo`. Pure-logic tests over the resolver function; no RDF4J / Mongo needed.
  • `TrustStateSnapshotTest` (+3): `parse_extractsNameAndNameCreatedAtWhenPresent` (extended-JSON `$date`), `parse_acceptsAccountWithoutNameFields` (forward-compat with pre-#113 registry), `parse_acceptsPlainStringNameCreatedAt` (alternative serialisation).
  • Existing tests using the 7-arg `AccountEntry` constructor updated to 9-arg with `null, null` for the new fields.

Test plan

  • `mvn test` — 177/177 pass locally (was 169; +8 new tests, 5 existing constructor sites updated)
  • CI green
  • Live verify after #113 + this both deploy:

Compatibility

  • Parser is fully forward-compatible: pre-#113 registry → `name`/`nameCreatedAt` parsed as null → no `foaf:name` triples emitted. Same behaviour as today.
  • Mirror is harmless when the trust graph has no `foaf:name` triples to copy.
  • No data-format change for existing rows; the new triples are purely additive.

🤖 Generated with Claude Code

…raphs (#62)

Consumes the per-account name + nameCreatedAt fields newly added by
nanopub-registry#113 and turns them into one canonical foaf:name triple
per approved agent, locally readable from the trust-state graph and
the mirrored spaces state graph (no SERVICE to /repo/full).

Resolution policy across approved keys: pick the row with MAX(ratio)
(ties → MIN(name) lex). Per-(agent, pubkey) resolution (latest declaring
intro supplies the name) lives on the registry side; this layer only
folds across keys.

Changes:
  - TrustStateSnapshot.AccountEntry gains name + nameCreatedAt
    (nullable). Parser handles MongoDB extended JSON {"$date": "..."},
    plain ISO-8601 strings, plain numeric epoch-ms, and treats absent
    fields as null so the consumer works against either pre- or
    post-#113 registry versions (additive non-breaking schema).
  - TrustStateLoader.resolveCanonicalNames + materialize: emits one
    <agent> foaf:name "..." triple in the trust state graph per
    approved agent.
  - AuthorityResolver.mirrorTrustState: copies foaf:name triples for
    approved agents into the new spaces state graph alongside the
    AccountState rows.

Tests cover the resolver policy (MAX(ratio), MIN(name) tiebreak,
unapproved-status skip, no-name absent, toLoad accepted) and the
parser (extended-JSON $date, plain ISO string, missing fields tolerated,
existing tests updated to the new 9-arg AccountEntry constructor).
177/177 pass locally.

Doc update of the worked-example query (drop the SERVICE clause) will
follow once both #89 and this PR are merged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tkuhn tkuhn marked this pull request as ready for review April 27, 2026 18:05
@tkuhn tkuhn merged commit 79fe043 into main Apr 27, 2026
7 of 8 checks passed
@tkuhn tkuhn deleted the feature/62-mirror-foaf-name branch April 28, 2026 06:04
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.

1 participant