Skip to content

GO-7383 Import real space exports: full converter, unresolved references, uninstalled types - #11

Merged
requilence merged 5 commits into
mainfrom
go-7383-compact-widget-view-ids
Sep 12, 2026
Merged

requilence merged 5 commits into
mainfrom
go-7383-compact-widget-view-ids

Conversation

@requilence

Copy link
Copy Markdown
Contributor

Makes real whole-space exports import. Measured on a retained 79-space
account: 79 of 79 active spaces now import, against 17 before this work.

What is here

A shared bundle converter. bundle/convert turns a complete exported
bundle into a native v1 archive: installed definitions, stored property and
option keys, templates, participants, views, widgets, space settings, remote
file metadata and encryption keys. Authoring keeps the strict authored
subset. The CLI gains to-v1 -full.

A policy for references a bundle cannot resolve. A dangling reference has
three causes and the store already tells them apart: a tombstone (the user
deleted the object, and Anytype deliberately does not rewrite everything that
mentions it), a row the export did not write, and no row at all. index.json
declares the first two as subsets of unresolved.targets; the full
validation surface admits a declared target at the severity its class earns
and still refuses an undeclared one; the authoring surface refuses all of
them. bundle.Inspect returns the graded report, and Validate keeps its
signature and its wording.

Export also stops manufacturing _missing_object for an id the space had no
row for. It keeps the id and warns, because the importer already writes the
sentinel for anything it cannot resolve, so a backup taken during a sync gap
now carries what a later sync could still make whole.

Uninstalled types travel. Removing a type sets isUninstalled, which the
app mirrors into isDeleted, and the exporter enumerated through a query
that refuses deleted rows, so the definition vanished while its objects kept
the key. 64 of the 72 unresolved type identities in the sweep were exactly
that. Such a type is now exported as a type document carrying
uninstalled: true and restores hidden; in the vocabulary it owns its stored
key and claims no display name.

A type the space never held imports as a Page. Five objects an old import
created with a type it never made. They are declared under
unresolved.types; identity slots become Page, and a slot that merely
references the type drops the entry.

Smaller repairs found on real data: a type document's own type is
normalized to objectType; a stated internal_key breaks a contested
declaration spelling; a dictionary entry's own spelling outranks a display
name another entry carries; a bundled spelling a live custom property also
answers to owes a legend entry; a stored _missing_object drops from a
dictionary entry's target types; an option key containing a slash gets an
escaped archive id. The icon gains a cid variant for images with no file
object behind them, which is how a participant avatar and a 1-to-1 space
icon are stored.

Review

A five-lens review over the finished change set found four severe defects,
all fixed here with a test each: a declared missing type that validated and
then refused to convert, an over-declaring index that silently retyped live
objects, a composer that never censused the property dictionary and so could
ship a bundle its own validator rejects, and an option archive id that was
not injective and made output nondeterministic. The corpus exercises none of
those paths, so the sweep was green with all four present.

Known and deliberate

  • An uninstalled bundled type still restores installed, because the
    importer installs bundled definitions before the guard runs. Zero of the 68
    removed type keys in the corpus are bundled. The agreed resolution, scoping
    the guard by the importer's new-space flag, is written up in the design note
    and deliberately not implemented here.
  • format/v2/SKILL.md carries absolute local paths and needs them made
    repository-relative.

Design notes: docs/superpowers/specs/2026-09-11-unresolved-references-design.md
and docs/superpowers/specs/2026-09-11-uninstalled-types-design.md.

Extract the CLI's authoring converter into a shared bundle/convert package
with two entry points: Authoring keeps the strict authored subset, Bundle
converts a complete exported space — installed definitions, stored
property and option keys, templates, participants, views, widgets, space
settings, remote file metadata and encryption keys — into native v1
archive entries and attachment path mappings for streaming.

The CLI gains to-v1 -full for bundle directories, with directory or ZIP
output that refuses to overwrite an existing destination.
A dangling index target has three causes and the store already tells them
apart: a tombstone (the user deleted the object; a reference to it is
ordinary state), a row this export did not write (archived, or outside a
partial export), and no row at all (not yet synced, or never here — the
one real loss). index.json now declares the first two as the subsets
unresolved.deleted and unresolved.omitted of unresolved.targets, classified
by the composer through the existence and deletion resolvers with one
exported predicate, ClassifyUnresolvedTarget.

bundle.Inspect returns the whole verdict as a graded Report; Validate keeps
its wording and refuses only on errors. On the full surface a declared
target is admitted — deleted as info, omitted or absent as a warning — while
an undeclared one stays an error, and the authoring surface refuses every
dangling target. The converter forwards the graded lines and returns the
three classes on Result.Unresolved; the CLI prints them after ok bundle and
gains -strict.

Export no longer manufactures _missing_object for an absent id: the id is
kept verbatim and warned, because the sentinel is the importer's answer and
a backup taken during a sync gap must carry what a later sync could still
make whole. A deleted space icon follows the document-level icon rule.

The icon gains a cid variant for images with no file object behind them —
a participant avatar, a 1-to-1 space icon — chosen by CID codec (dag-pb or
raw against dag-cbor), never folded, declared or resolved, refused by the
authoring subset, with the old overloaded file spelling read for
compatibility.

Spec: docs/superpowers/specs/2026-09-11-unresolved-references-design.md.
On the 79-space corpus sweep, 70 spaces now import against 17 before; the
9 remaining failures are missing type documents.
Nine real space exports still failed to import after the unresolved-
reference work. Read against the source stores, 64 of the 72 type
identities their objects named were types the user had REMOVED from the
space, three were a legacy `ot-type`, and five were types an old import
created and never made. None was a deletion.

A removed type now travels. Removing a type sets `isUninstalled`, the app
mirrors that into `isDeleted`, and the exporter enumerated through a query
that refuses deleted rows, so the definition vanished while its objects
kept the key. It is exported as an ordinary type document carrying
`uninstalled: true` on the envelope, the mirror of the member a removed
property already has, and import writes the flag back so it restores
hidden and reinstallable. In the vocabulary such a type owns its stored
key, so `type-<key>` references resolve, and claims no display name, so a
live type that took the freed name is not shadowed.

A type document's own type is written as `objectType`, derivable from the
kind, with a warning when the store disagrees.

A type the source space never held is declared in `index.json` under
`unresolved.types`. The full surface admits a declared one as a warning and
refuses an undeclared one; the authoring surface refuses both, and so does
a declaration naming a type the bundle actually carries. The converter
imports such an object as a Page, and a template as one for Page, because
kept verbatim it is unsearchable by type and renders with a guessed layout.
A slot that merely references the type drops the entry instead, the way a
list already expresses an absent reference by being shorter.

Property identity repairs found on real data: a stated `internal_key`
breaks a contested declaration spelling; a dictionary entry's own
`property` spelling outranks a display name another entry carries; a
bundled spelling a live custom property also answers to owes a legend
entry. A stored `_missing_object` drops from a dictionary entry's target
types, and an option key containing a slash gets an escaped archive id,
escaping the escape character first so two keys cannot collapse onto one
entry. The comparator learns the two type-document normalizations so
neither reads as loss.

Measured: all nine spaces validate and import, against 17 of 79 at the
start. Design note in docs/superpowers/specs.
format/v2/README.md already lists SKILL.md, but the file itself was never
tracked, so the link was broken in the published tree.

The skill points an authoring agent at the three authoring schemas and the
habit_tracker example rather than the full spec, tells it to model the
user's domain instead of defaulting every object to Page, and requires it
to run the validator on the generated bundle before delivery rather than
recommending the command or validating the example instead.
@requilence
requilence merged commit f08d5ad into main Sep 12, 2026
2 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 12, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant