The graph namespaces explained themselves by citing a separate project
whose Python pipeline reads the graphs this backend writes. A reader of
this repository does not have that project and should not need it, and a
docstring that justifies a choice by pointing elsewhere cannot be checked
here.
Every claim survives; only the framing changes. Column names and types
are Penpot's own decision, recorded with the reason for each divergence
from the snake_case default. The transform registry describes the edges
it materializes. The denormalizations in `app.graph.project.document`
are justified by the walk already holding both answers.
Three corrections fall out of the rewrite:
- `app.graph.schema.contract` claimed a test, `graph_contract_test`,
that walks a checked-in schema manifest and fails on any divergence.
No such test exists. The paragraph is gone.
- `app.graph.project.document` pointed at
`app.graph.meta/projection-transforms`, which does not exist.
- `app.graph.project.transforms/registry` claimed its entries were "in
application order" while `apply-transforms!` reduced over the literal
vector. The three registered transforms read disjoint columns, so the
order is not load-bearing. The docstring now says so, and the one real
ordering constraint is stated where it applies: `link-swap-slots!`
strips `swap-slot-*` entries from `touched`, so anything reading
`touched` has to run before it.
`contract/pending-beadpot-columns` becomes `contract/unprojected-keys`.
It is referenced nowhere else.
AI-assisted-by: mixed models
Split out of "🐛 Declare the shape attributes stored files carry",
which is now #11125 and carries only its `common/` half. This commit is
the graph's own side of that change, and it stays on this branch.
`app.graph.schema.contract` pins `svg_viewbox` to `DOUBLE[4]` and
`svg_transform` to `DOUBLE[6]`. The shape schema types both `:map` on
purpose, because legacy files hold them as plain maps rather than as
`::grc/rect` and `::gmt/matrix` records, and a tighter *schema* would
reject those files. A tighter *column* costs nothing, since
`app.graph.schema.values/coerce` reads either form.
`app.graph.schema.nodes` declares four file-level attributes as
projection `:extra` rather than in `ctf/schema:file`: `:options`,
`:backend`, `:comment-thread-seqn`, and `:ignore-sync-until`. Declaring
them in the file schema breaks saving, measured at 185 failures, because
`app.binfile.common/update-file!` derives its UPDATE column list from a
file map's keys and the `file` table has no `backend` column, that value
being synthesized on read. An `:extra` is local to the graph and cannot
reach a write.
`app.graph.project.document` lifts `:options` out of `:data` before the
blob is dropped, so a consumer reads file-level configuration without
opening the blob.
AI-assisted-by: mixed models
A projected graph is a cache of one file at one revision, built by one
schema, and nothing in it said so. `GraphMeta` records the file, the
revision, the schema version and the producer, and is written last, so
its presence also marks the build complete and its contents say whether
a cached database is still worth opening.
- `graph/meta.clj`: the `GraphMeta` table and its writer.
- `graph/schema/contract.clj`: one place that maps a Penpot key to its
graph column. The rule is snake_case of the key; every exception, be
it a rename, a drop or a type override, is recorded there with its
reason, so a divergence is a diff to review rather than a silent
rename.
- `graph/project/document.clj`: `page-id` and the inherited
`component-id` are written during the tree walk, which already knows
both, rather than by a post-ingest statement. `graph/sync.clj` does
the same on the incremental path, so a live-synced graph matches a
rebuild.
- `graph/project/transforms.clj`: a registry, so adding a derived-link
pass is one entry. Adds `RefersTo` (from `shape-ref`) and
`FillsSwapSlot` (from `swap-slot-*` entries in `touched`, then
stripped as `ctk/normal-touched-groups` does).
- `graph/debug.clj`, `graph/stats.clj`: enumerate relationship tables
from the catalog instead of naming them, so the console's graph view
and the ingest counts pick up new edge types without being told.
- `graph/debug.clj`, `http/debug.clj`: `graph-export` gains
`source=session`, which snapshots the live in-memory console graph
through EXPORT/IMPORT DATABASE. Live sync moves that graph away from a
fresh projection, and taking it away to query elsewhere is the point
of asking for it.
AI-assisted-by: mixed models