Skip to content

Add deep cloning for vocabulary objects - #1337

Open
dahlia wants to merge 1 commit into
fedify-dev:mainfrom
dahlia:feat/vocab-deep-clone
Open

dahlia wants to merge 1 commit into
fedify-dev:mainfrom
dahlia:feat/vocab-deep-clone

Conversation

@dahlia

@dahlia dahlia commented Oct 27, 2026

Copy link
Copy Markdown
Member

clone() shares nested objects, so resolving an embedded Note can change the QuoteRequest Hollo later echoes in an Accept or Reject. deepClone() gives the resolving code an independent graph while leaving clone() cheap and unchanged.

Fixes #1243. Related: #1242, #1246, fedify-dev/hollo#643.

Design

  • Generated private-field copiers walk stored values without fetching or a JSON-LD round trip. An identity map preserves shared references, and a work queue avoids call stack limits on deep graphs. Replacement values, URLs and byte arrays are copied too; immutable scalars and opaque keys remain shared.
  • Each node first uses clone(), reusing its validation, subclass construction and trust rules. The graph copy then retains that node's loader, verifier, tracer and base URL defaults, with caller options applied only to the root so nested objects keep their own policies.
  • References back to the source receive a separate copy of its pre-update values. Consequently, x.deepClone({ object: x }) does not manufacture a self-cycle in the updated root.
  • Portable referrers within the graph point to independent historical copies, without applying replacements to their ancestry. This keeps caller edits out of verification history and prevents later hydration through that history from changing the source; external ancestors remain external references. Positive verification evidence is not inherited.
  • Cached JSON-LD and retained signed representations are discarded at every node because they may describe stale values or proofs that no longer cover the copy. The API copies modeled data, not unknown JSON-LD properties or exact received documents. Callers should keep the original for echoing and sign copies again when needed.

Copy nested vocabulary values without hydration leaking back to the
source.  Preserve graph aliases, defaults and historical portable
referrers, while dropping cached JSON-LD and signed representations.

Add regression coverage, a copy benchmark and manual guidance.  Design
was reviewed with Claude Code (claude-opus-5-5) and Codex (gpt-6-astra);
prose was reviewed with Claude Code (claude-fable-5-1).

Fixes fedify-dev#1243
fedify-dev#1242
fedify-dev#1246
fedify-dev/hollo#643

Assisted-by: Codex:gpt-6.1-sol
Assisted-by: Claude Code:claude-fable-5-1
@dahlia dahlia added this to the Fedify 2.5 milestone Oct 27, 2026
@dahlia dahlia self-assigned this Oct 27, 2026
@dahlia dahlia added component/vocab Activity Vocabulary related component/vocab-tools Vocabulary code generation (@fedify/vocab-tools) labels Oct 27, 2026
@netlify

netlify Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for fedify-json-schema canceled.

Name Link
🔨 Latest commit 1761a54
🔍 Latest deploy log https://app.netlify.com/projects/fedify-json-schema/deploys/6ae05aa419df0ea117947d1b

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-27T08:02:45.798888Z
We couldn’t complete this review. Comment @codex review to try again.
1761a54 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: c5798edd-38f8-425b-aac7-28910ad98607
Commits

Reviewing files that changed from the base of the PR and between fdeb138 and 1761a54.

Files ignored due to path filters: 3
  • packages/vocab-tools/src/__snapshots__/class.test.ts.deno.snap is excluded by !**/*.snap
  • packages/vocab-tools/src/__snapshots__/class.test.ts.node.snap is excluded by !**/*.snap
  • packages/vocab-tools/src/__snapshots__/class.test.ts.snap is excluded by !**/*.snap
Files selected for processing: 13
  • CHANGES.md
  • changes.d/vocab/deep-clone.md
  • claude-plugin/skills/fedify/SKILL.md
  • docs/manual/vocab.md
  • packages/vocab-runtime/src/internal/portable-dereference.test.ts
  • packages/vocab-runtime/src/internal/portable-dereference.ts
  • packages/vocab-tools/src/class.ts
  • packages/vocab-tools/src/constructor.ts
  • packages/vocab-tools/src/deep-clone.ts
  • packages/vocab/src/deep-clone.test.ts
  • packages/vocab/src/gateway.test.ts
  • packages/vocab/src/signed-representation.test.ts
  • packages/vocab/src/vocab.bench.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Walkthrough

This change adds a generated deepClone() method to vocabulary objects. It copies nested object graphs and replacement values, handles portable provenance, and adds tests, benchmarks, and documentation.

Changes: Vocabulary deep cloning

Layer / File(s) Summary
Portable provenance retargeting
packages/vocab-runtime/src/internal/portable-dereference.ts, packages/vocab-runtime/src/internal/portable-dereference.test.ts, packages/vocab/src/gateway.test.ts
Adds a helper that replaces an existing provenance referrer in a copy when a matching object is resolved. Tests cover missing or unresolved referrers and historical collection ancestry.
Generated deep-clone methods and graph copiers
packages/vocab-tools/src/constructor.ts, packages/vocab-tools/src/deep-clone.ts, packages/vocab-tools/src/class.ts
Generates deepClone() methods and per-type descriptors. The graph copier memoizes object copies, copies URL and Uint8Array values, and retargets portable references.
Graph-copy tests and benchmark
packages/vocab/src/deep-clone.test.ts, packages/vocab/src/gateway.test.ts, packages/vocab/src/signed-representation.test.ts, packages/vocab/src/vocab.bench.ts
Adds coverage for graph isolation, aliases, cycles, options, trust, replacement values, subclasses, and signed representations. Adds activity-copy benchmarks for shallow clone, deep clone, and a JSON-LD round trip.
API and behavior documentation
docs/manual/vocab.md, claude-plugin/skills/fedify/SKILL.md, CHANGES.md, changes.d/vocab/deep-clone.md
Documents the API, copy semantics, options, provenance, and representation handling. Adds the changelog entry and issue and PR references.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant DeepClone as Generated deepClone()
  participant Clone as this.clone()
  participant Graph as deepCloneGraph
  participant Descriptor as Per-type clone descriptor
  participant Provenance as retargetPortableReferrer
  Caller->>DeepClone: Provide replacements and optional root settings
  DeepClone->>Clone: Create root copy
  DeepClone->>Graph: Copy source graph into root
  Graph->>Descriptor: Create copies of typed objects
  Graph->>Provenance: Retarget copied portable referrers
  DeepClone-->>Caller: Return copied vocabulary object
Loading

Possibly related PRs

  • fedify-dev/fedify#1247: Adds CI validation for changelog fragments, which applies to this PR’s changelog entries.
  • fedify-dev/fedify#1331: Changes vocabulary code generation; this PR adds generators and generated vocabulary methods.
Merge Risk: Minimal · up to 1761a

No actionable issue remains from the reviewed changes; the PR is ready for normal merge checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surv… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title "Add deep cloning for vocabulary objects" matches the main change. The changeset adds a deepClone() method to vocabulary objects, and the PR objectives state the same goal. The title is co…
Description check Passed The description explains the motivation and design of the change. It states that deepClone() gives an independent graph while clone() stays unchanged. It covers identity maps, replacement copying,…
Linked Issues check Passed Directly linked issue [#1243] has coding requirements only. The PR meets them, based on the PR description and the change summary. No repository inspection was available, so this rests on the summary …
Out of Scope Changes check Passed Each change in the summary relates to the deepClone() objective in [#1243] or to its design.

- vocab-tools (class.ts, constructor.ts, deep-clone.ts): generates the deepClone() method and …
Full details: Title check

Explanation

The title "Add deep cloning for vocabulary objects" matches the main change. The changeset adds a deepClone() method to vocabulary objects, and the PR objectives state the same goal. The title is concise and specific, and it does not describe any unrelated change.

Full details: Description check

Explanation

The description explains the motivation and design of the change. It states that deepClone() gives an independent graph while clone() stays unchanged. It covers identity maps, replacement copying, portable referrers, and discarded cached JSON-LD and signed representations, all of which appear in the changeset. It also links the related issues.

Full details: Linked Issues check

Explanation

Directly linked issue [#1243] has coding requirements only. The PR meets them, based on the PR description and the change summary. No repository inspection was available, so this rests on the summary and the named tests.

  • Add deepClone() generated by @fedify/vocab-tools: constructor.ts adds generateDeepCloner, which emits a public deepClone(values, options) method. class.ts and deep-clone.ts emit the supporting code into generated classes.
  • Same parameters as clone(): the method takes values and an options object with loader and verifier fields and calls this.clone(values, ...) for each node. Exact option parity with clone() is not confirmed. The summary lists documentLoader, contextLoader, and verifyPortableObject but does not list the full clone() option set.
  • Leave clone() unchanged: the summary shows only documentation changes to clone(). The shallow behavior is kept.
  • Copy nested objects recursively, so hydration on either copy does not change the other: deep-clone.ts walks the graph with an identity map. deep-clone.test.ts covers embedded quote hydration isolation.
  • Shared subobjects: the identity map preserves repeated references, which answers the first open question in [#1243].
  • Cached JSON-LD and trust/provenance: the PR discards cached JSON-LD and signed representations on every copied node. It keeps embedded-object trust and portable restrictions and does not inherit positive verification evidence. This answers the open questions in [#1243]. Discarding the cache is a documented design choice, not a conflict with the issue. The Hollo echo case is covered by keeping the original object, as the PR description states.
  • Values passed in: replacement values are copied, which answers the fourth open question.
  • Cost: vocab.bench.ts adds shallow, deep, and JSON-LD round-trip benchmarks.
  • The 2.5.0 timing is a release note, not a coding requirement.

The excluded snapshot files (class.test.ts*.snap) are test artifacts. No requirement depends on their contents.

Full details: Out of Scope Changes check

Explanation

Each change in the summary relates to the deepClone() objective in [#1243] or to its design.

  • vocab-tools (class.ts, constructor.ts, deep-clone.ts): generates the deepClone() method and its helpers. Direct implementation.
  • vocab-runtime (portable-dereference.ts and its test): adds retargetPortableReferrer. The graph copy uses it to keep portable referrers pointing at historical copies. This supports the stated design and is not an unrelated change.
  • vocab tests (deep-clone.test.ts, gateway.test.ts, signed-representation.test.ts): test the copy behavior, collection ancestry, and dropped signed representations.
  • vocab.bench.ts: the cost benchmark named as an open question in [#1243].
  • docs/manual/vocab.md, claude-plugin/skills/fedify/SKILL.md, and CHANGES.md / changes.d/vocab/deep-clone.md: document the new public method and its behavior, and follow the project changelog convention. Documentation for a new public API supports the objective.

No change in the summary lacks a connection to the objective.

Full details: Docstring Coverage

Explanation

Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 5 functions across 9 files. (4 skipped: 4 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@codecov

codecov Bot commented Oct 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

Files with missing lines Coverage Δ
...vocab-runtime/src/internal/portable-dereference.ts 93.46% <100.00%> (+0.96%) ⬆️
packages/vocab-tools/src/class.ts 99.23% <100.00%> (+0.04%) ⬆️
packages/vocab-tools/src/constructor.ts 100.00% <100.00%> (ø)
packages/vocab-tools/src/deep-clone.ts 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/vocab Activity Vocabulary related component/vocab-tools Vocabulary code generation (@fedify/vocab-tools)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add deepClone() to vocabulary objects

1 participant