Skip to content

refactor: write literal property values directly + decode both old and new shapes#604

Open
HexaField wants to merge 5 commits into
devfrom
refactor/literal-channel-v-separation
Open

refactor: write literal property values directly + decode both old and new shapes#604
HexaField wants to merge 5 commits into
devfrom
refactor/literal-channel-v-separation

Conversation

@HexaField

@HexaField HexaField commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Companion to coasys/ad4m#837. After that PR lands, the ad4m executor stops embedding signed-expression envelopes ({author, timestamp, data, proof}) in link targets when properties are written through the literal language — per-link author/timestamp/proof lives only on the RDF reifier going forward. This PR brings flux into line on both sides of that contract: the write helpers that put the envelope into the target in the first place, and the read sites that pulled values out of the envelope shape via .get().data.

Writes — stop embedding provenance in targets

Five flux call sites previously routed property values through client.expression.create(value, 'literal'). The executor's literal language signs the value into a literal:json:<envelope> URL, so the target carried {author, timestamp, data, proof} duplicating provenance the reifier already holds. After this PR every property target is encoded client-side as a deterministic plain literal:string: / :number: / :boolean: / :json: URL via Literal.from(value).toUrl(). No round-trip to the executor, no embedded provenance, no envelope.

File Sites
packages/utils/src/createNeighbourhoodMeta.ts neighbourhood name, createdAt, description
packages/utils/src/linkHelpers.ts (createLiteralLinks) generic property-link helper used by createProfile, updateProfile
packages/utils/src/linkHelpers.ts (createLiteralObject) parent + children helper used by createAgentWebLink

createLiteralObject gains a useful side effect: the deterministic parent URL doubles as a stable entity identity, so two calls with the same parent.target value resolve to the same IRI — which is what callers like createAgentWebLink actually want (same URL → same WebLink entity).

grep -rn "expression\.create\([^)]*['\"]literal['\"]\)" against packages/ returns no hits after this PR. The only remaining expression.create("…", "literal") callers across the ecosystem are dapp entanglement proofs and agent expressions in the ad4m repo, both of which intentionally produce addressable signed expressions (a separate concern from link-property storage).

Reads — handle both old and new target shapes

New unwrapLiteralValue(target) helper in @coasys/flux-utils that decodes literal: URLs and returns the inner value:

  • Plain literal → returns the decoded value directly.
  • Signed-expression envelope (legacy data on disk before the ad4m migration runs, plus any explicit expression.create(value, "literal") URL that callers feed in) → returns the inner .data.
  • Anything else (non-literal URL, undecodable) → returns undefined; call sites substitute the raw target.

Updated four reader sites that previously assumed the envelope shape:

File Was
packages/utils/src/linkHelpers.ts (mapLiteralLinks) three-prong startsWith dispatch with .get().data on the json: branch
packages/utils/src/getNeighbourhoodMeta.ts (getMetaFromLinks) .get().data on NAME / DESCRIPTION / CREATED_AT predicates
packages/utils/src/prologHelpers.ts (resolveExp inside model property resolution) .get().data on literal: URLs
packages/api/src/getAgentWebLinks.ts .get().data on weblink parent target

Plus packages/api/src/utils/parseLit.test.ts flipped from asserting .data extraction to asserting JSON.stringify(obj) to match parseLit's new behaviour in ad4m#837 (envelope semantics live in unwrapLiteralValue now, not in parseLit).

Test plan

  • pnpm exec vitest run packages/api/src/utils/unwrapLiteralValue (7/7) + parseLit (6/7 locally against the published ad4m, 7/7 expected in CI against the linked branch)
  • pnpm exec tsc --noEmit -p packages/utils/tsconfig.json clean for changed files
  • pnpm exec tsc --noEmit -p packages/api/tsconfig.json clean for changed files
  • Full CI green on this PR (resolves the parseLit assertion via the same-name ad4m branch)
  • Manual smoke: create a community, verify name/description round-trip through createNeighbourhoodMeta + getMetaFromLinks
  • Manual smoke: create a WebLink, verify two calls with the same URL produce one entity (deterministic identity check)

Cross-repo coupling

Flux CI resolves the same-name refactor/literal-channel-v-separation branch of @coasys/ad4m and tests against that build, not the currently-published version. The two PRs ship as one logical change.

Summary by CodeRabbit

  • Refactor

    • Refactored literal value processing throughout the codebase for improved consistency and maintainability.
    • Updated link and metadata derivation to use deterministic URL generation.
    • Introduced a centralized utility for decoding literal values across components.
  • Tests

    • Added comprehensive test suite for literal value unwrapping functionality.
    • Updated existing tests to reflect changes in literal object handling.

After the ad4m executor stops emitting signed-expression envelopes for
literal-language property writes, link targets storing string/number/
boolean values come back from Literal.fromUrl().get() as the raw value
rather than as an envelope object with a .data field. Existing
.get().data sites would start returning undefined.

Introduce unwrapLiteralValue in flux-utils which decodes literal: URLs
and returns the inner value for both plain and signed-envelope shapes.
Update the five sites in flux-utils + flux-api that previously assumed
the envelope shape.
@netlify

netlify Bot commented Jun 4, 2026

Copy link
Copy Markdown

Deploy Preview for fluxsocial-dev ready!

Name Link
🔨 Latest commit bedc643
🔍 Latest deploy log https://app.netlify.com/projects/fluxsocial-dev/deploys/6a3197c75318470008dc240f
😎 Deploy Preview https://deploy-preview-604--fluxsocial-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@HexaField, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 53 minutes and 57 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 97d619fa-99a6-4b95-aba1-1f4cc14a91f4

📥 Commits

Reviewing files that changed from the base of the PR and between f997812 and bedc643.

📒 Files selected for processing (6)
  • ADAM_MODEL_MIGRATION.md
  • app/src/views/main/profile/ProfileView.vue
  • docs/src/create-flux-plugin/basics/subjects.md
  • views/graph-view/src/components/CommunityGraph.tsx
  • views/kanban-view/src/components/Board/TaskModel.ts
  • views/table-view/src/components/NewClass/NewClass.tsx
📝 Walkthrough

Walkthrough

This PR introduces a centralized unwrapLiteralValue utility to standardize how literal: URIs are decoded across the codebase, and refactors multiple subsystems to use deterministic literal URL generation instead of server-side expression creation.

Changes

Literal value decoding refactor

Layer / File(s) Summary
unwrapLiteralValue utility definition and exports
packages/utils/src/unwrapLiteralValue.ts, packages/api/src/utils/unwrapLiteralValue.test.ts, packages/utils/src/index.ts
New unwrapLiteralValue(target) helper decodes literal: URIs and unwraps legacy "signed envelope" objects containing author and proof. Full test coverage verifies nullish inputs, non-literal URIs, plain literal decoding, JSON pass-through, envelope unwrapping, and non-envelope JSON with data fields. The utility is re-exported from the package index.
API web links and parseLit test update
packages/api/src/getAgentWebLinks.ts, packages/api/src/utils/parseLit.test.ts
getAgentWebLinks imports and uses unwrapLiteralValue to decode web link URLs with a fallback to the raw target value. parseLit test expectations are updated: JSON objects containing a data field are now asserted to be JSON-stringified as complete objects (including the data key) rather than having the nested value extracted.
Literal link helpers refactor to deterministic URIs
packages/utils/src/linkHelpers.ts
mapLiteralLinks delegates to unwrapLiteralValue for decoding link targets. createLiteralLinks no longer requires an Ad4mClient; instead it builds deterministic literal link targets via Literal.from(...).toUrl(). createLiteralObject derives a stable parentTarget from plain literal URLs instead of creating expressions server-side.
Neighbourhood metadata creation and retrieval
packages/utils/src/createNeighbourhoodMeta.ts, packages/utils/src/getNeighbourhoodMeta.ts
createNeighbourhoodMeta switches from server-side expression creation to deterministic Literal.from(...).toUrl() for NAME, CREATOR, CREATED_AT, and DESCRIPTION targets. getNeighbourhoodMeta uses unwrapLiteralValue to extract metadata values, with conditional literal unwrapping for timestamp fields.
Prolog literal resolution
packages/utils/src/prologHelpers.ts
prologHelpers updates resolveExp to use unwrapLiteralValue for decoding literal: URLs instead of inline Literal.fromUrl(...).get().data calls.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • coasys/flux#580: Both PRs modify literal-URI decoding logic in the same packages/utils helpers (e.g., getNeighbourhoodMeta.ts, linkHelpers.ts, prologHelpers.ts), migrating how literal: targets are interpreted/parsed—main PR via unwrapLiteralValue, retrieved PR via literal://literal: handling—so the changes are directly connected.
  • coasys/flux#544: The retrieved PR starts using createLiteralLinks to build literal link targets (e.g., in packages/api/src/createProfile.ts), which directly depends on the literal-link URL construction/handling changes made in packages/utils/src/linkHelpers.ts in the main PR.
  • coasys/flux#585: The main PR's change to packages/api/src/utils/parseLit.test.ts (updating expectations for JSON literal objects with a .data field) is directly connected to PR #585's refactor that deduplicates/centralizes parseLit usage across API queries, making their parseLit behavior/tests align.

Suggested reviewers

  • lucksus
  • jhweir

Poem

🐰 A rabbit hops through literal lands,
Where URIs dance in unwrapped strands,
No more expressions, client-bound and wide,
Deterministic URLs lead the guide!
One helper to rule them all with care,
Decoded values float through the air! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: write literal property values directly + decode both old and new shapes' clearly and specifically describes the main changes: (1) writing literals directly client-side without server-side envelope embedding, and (2) adding backward-compatible decoding for both plain literals and legacy envelope shapes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/literal-channel-v-separation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

HexaField added 3 commits June 4, 2026 10:27
Cross-repo branch linking means flux CI runs the same-name ad4m branch
of @coasys/ad4m, which drops the .data extraction from parseLit. Caller-
visible behaviour: literal:json: objects round-trip as JSON.stringify,
leaving envelope-vs-plain shape decisions to unwrapLiteralValue.
createNeighbourhoodMeta, createLiteralLinks, and createLiteralObject
previously routed property values through client.expression.create(value,
'literal'), which signs the value into a literal:json:<envelope> URI on
the executor — embedding author/timestamp/proof in the target IRI and
duplicating provenance that already lives on the link reifier.

Encode the value client-side with Literal.from(value).toUrl() instead.
Targets become deterministic plain literal URIs; the reifier remains the
single source of truth for who wrote the link and when.

createLiteralObject now uses the deterministic parent URL as a stable
entity identity — two calls with the same parent.target value resolve
to the same parent IRI, which is the intended semantics for value-keyed
entities like web links.
@HexaField HexaField changed the title refactor: adapt to ad4m literal-channel-V property reads refactor: literal targets are values, not signed expressions Jun 4, 2026
@HexaField HexaField changed the title refactor: literal targets are values, not signed expressions refactor: write literal property values directly + decode both old and new shapes Jun 4, 2026
@HexaField HexaField marked this pull request as ready for review June 8, 2026 11:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/utils/src/createNeighbourhoodMeta.ts (1)

45-45: 💤 Low value

Consider using strict equality.

The loose equality check (!=) works but strict equality (!==) is more idiomatic in TypeScript.

Suggested change
-  if (description != '') {
+  if (description !== '') {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/utils/src/createNeighbourhoodMeta.ts` at line 45, Replace the loose
inequality check in createNeighbourhoodMeta.ts with a strict one: locate the
conditional that reads if (description != '') and change it to use strict
comparison (===/!==) so it becomes if (description !== '') to follow TypeScript
best practices; ensure any equivalent checks in the same function (or nearby
conditionals) are updated similarly to maintain consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/api/src/utils/unwrapLiteralValue.test.ts`:
- Around line 31-40: Add a test that verifies unwrapLiteralValue does NOT unwrap
a partial "envelope" missing timestamp: create an object like { data: 'value',
author: 'did:key:…', proof: { signature: 'sig' } }, convert it to a literal URL
via Literal.from(...).toUrl(), call unwrapLiteralValue(url) and assert the
result strictly equals the original object (not the inner data). Place this
alongside the existing envelope tests and name it something like "does not
unwrap objects that have data/author/proof but lack timestamp" to ensure
unwrapLiteralValue rejects incomplete envelope shapes.

In `@packages/utils/src/linkHelpers.ts`:
- Around line 45-53: The function createLiteralLinks currently marked async
contains no asynchronous code; remove the async keyword from its declaration so
it is a synchronous function, keeping the parameters (client: Ad4mClient,
source: string, map: PredicateMap) and its existing implementation that uses
Literal.from(...).toUrl() and returns new Link({...}), ensuring any callers
expect a plain array return rather than a Promise.

In `@packages/utils/src/unwrapLiteralValue.ts`:
- Around line 24-31: The envelope detection in unwrapLiteralValue (check on
parsed object) currently verifies 'data', 'author', and 'proof' but omits
'timestamp', causing false positives; update the conditional in the
parsed/object branch (the block that returns parsed.data) to also require
'timestamp' in parsed so the envelope shape matches { author, timestamp, data,
proof } before unwrapping.

---

Nitpick comments:
In `@packages/utils/src/createNeighbourhoodMeta.ts`:
- Line 45: Replace the loose inequality check in createNeighbourhoodMeta.ts with
a strict one: locate the conditional that reads if (description != '') and
change it to use strict comparison (===/!==) so it becomes if (description !==
'') to follow TypeScript best practices; ensure any equivalent checks in the
same function (or nearby conditionals) are updated similarly to maintain
consistency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b1101ad3-b6fb-4f76-aae2-c3f1302f7759

📥 Commits

Reviewing files that changed from the base of the PR and between 86215ec and f997812.

📒 Files selected for processing (9)
  • packages/api/src/getAgentWebLinks.ts
  • packages/api/src/utils/parseLit.test.ts
  • packages/api/src/utils/unwrapLiteralValue.test.ts
  • packages/utils/src/createNeighbourhoodMeta.ts
  • packages/utils/src/getNeighbourhoodMeta.ts
  • packages/utils/src/index.ts
  • packages/utils/src/linkHelpers.ts
  • packages/utils/src/prologHelpers.ts
  • packages/utils/src/unwrapLiteralValue.ts

Comment on lines +31 to +40
it('unwraps signed-envelope literal:json: targets to their inner data', () => {
const envelope = {
author: 'did:key:z6MkExample',
timestamp: '2024-01-01T00:00:00Z',
data: 'wrapped value',
proof: { key: '#z6MkExample', signature: 'sig', valid: true },
};
const url = Literal.from(envelope).toUrl();
expect(unwrapLiteralValue(url)).toBe('wrapped value');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add test case for partial envelope shape.

The envelope unwrapping test includes all four fields (author, timestamp, data, proof). Consider adding a test case for a JSON object that has data, author, and proof but lacks timestamp, to verify it is not unwrapped (because it's not a valid envelope). This would catch the bug flagged in unwrapLiteralValue.ts.

🧪 Suggested test case
it('does not unwrap objects that have data/author/proof but lack timestamp', () => {
  // Not a valid envelope — missing 'timestamp'.
  const notAnEnvelope = { data: 'value', author: 'did:key:z6Mk', proof: { signature: 'sig' } };
  const url = Literal.from(notAnEnvelope).toUrl();
  expect(unwrapLiteralValue(url)).toEqual(notAnEnvelope);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/api/src/utils/unwrapLiteralValue.test.ts` around lines 31 - 40, Add
a test that verifies unwrapLiteralValue does NOT unwrap a partial "envelope"
missing timestamp: create an object like { data: 'value', author: 'did:key:…',
proof: { signature: 'sig' } }, convert it to a literal URL via
Literal.from(...).toUrl(), call unwrapLiteralValue(url) and assert the result
strictly equals the original object (not the inner data). Place this alongside
the existing envelope tests and name it something like "does not unwrap objects
that have data/author/proof but lack timestamp" to ensure unwrapLiteralValue
rejects incomplete envelope shapes.

Comment on lines +45 to 53
export async function createLiteralLinks(_client: Ad4mClient, source: string, map: PredicateMap) {
// Values land as deterministic `literal:string:` targets — the link reifier
// carries the author/timestamp/proof for the write itself.
return Object.keys(map)
.filter((predicate) => typeof map[predicate] === 'string')
.map((predicate) => {
const target = Literal.from(map[predicate] as string).toUrl();
return new Link({ source, predicate, target });
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove async from createLiteralLinks signature.

The function no longer contains any asynchronous operations (no await, no Promise). It should be declared as a synchronous function.

♻️ Proposed fix
-export async function createLiteralLinks(_client: Ad4mClient, source: string, map: PredicateMap) {
+export function createLiteralLinks(_client: Ad4mClient, source: string, map: PredicateMap) {
   // Values land as deterministic `literal:string:` targets — the link reifier
   // carries the author/timestamp/proof for the write itself.
   return Object.keys(map)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/utils/src/linkHelpers.ts` around lines 45 - 53, The function
createLiteralLinks currently marked async contains no asynchronous code; remove
the async keyword from its declaration so it is a synchronous function, keeping
the parameters (client: Ad4mClient, source: string, map: PredicateMap) and its
existing implementation that uses Literal.from(...).toUrl() and returns new
Link({...}), ensuring any callers expect a plain array return rather than a
Promise.

Comment on lines +24 to +31
if (
parsed &&
typeof parsed === 'object' &&
'data' in parsed &&
'author' in parsed &&
'proof' in parsed
) {
return parsed.data;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add 'timestamp' to envelope detection.

The envelope detection checks for data, author, and proof, but the docstring (line 10) states the envelope shape is { author, timestamp, data, proof }. Without checking for timestamp, a user JSON object that happens to have {data, author, proof} fields would be incorrectly unwrapped.

🛡️ Proposed fix
  if (
    parsed &&
    typeof parsed === 'object' &&
    'data' in parsed &&
    'author' in parsed &&
+   'timestamp' in parsed &&
    'proof' in parsed
  ) {
    return parsed.data;
  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/utils/src/unwrapLiteralValue.ts` around lines 24 - 31, The envelope
detection in unwrapLiteralValue (check on parsed object) currently verifies
'data', 'author', and 'proof' but omits 'timestamp', causing false positives;
update the conditional in the parsed/object branch (the block that returns
parsed.data) to also require 'timestamp' in parsed so the envelope shape matches
{ author, timestamp, data, proof } before unwrapping.

… unwrap sites

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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