Skip to content

feat(app-repo): v2 format carries the app's whole configuration - #70

Merged
rubenvdlinde merged 4 commits into
developmentfrom
feat/app-repo-format-v2
Aug 1, 2026
Merged

feat(app-repo): v2 format carries the app's whole configuration#70
rubenvdlinde merged 4 commits into
developmentfrom
feat/app-repo-format-v2

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What

Extends the published app-repo format from 4 file kinds to 8, so a published app carries enough to actually work.

channel source
data-registers/<slug>.json Application.dataRegisters[]
connectors/<kind>/<slug>.json new explicit Application.connectors[] binding
automations/<slug>.json Automation.applicationSlug
skills/<name>/… hermiq's SkillBundleSerializer layout (one shape, two apps)

Why

spectr is the sharp case. Its manifest binds pages to spectr-live 109 times and its per-app register has no companion schemas, so under v1 it serialises to a manifest plus zero schemas/ entries — and reports success. A green-but-empty publish: the artefact installs and the app does not work.

Design notes for review

  • Connectors are declared explicitly, not inferred. Inference from register targets needed no schema change, but would make an app's published surface depend on which other objects happen to target a shared register — the same app would export differently on two instances.
  • Collectors are total (mirroring collectCompanionSchemas()): a missing source yields no entries, never an exception, so serialisation can't block a publish. The descriptor's per-channel counts are what stop that becoming a silent empty artefact.
  • Parser is lenient on channels, strict on companion schemas — deliberate asymmetry. A companion schema is load-bearing; a channel entry is additive, so one unreadable connector must not make a valid repo unimportable.
  • No cross-app PHP dependency. OpenConnector was re-platformed onto OpenRegister (no lib/Db, no openconnector_* tables), so its configs are read as OR objects per ADR-022.
  • Secret stripping is defence in depth, not the primary control. Verified on the live instance: source 23 carries host/port/user/dbname plus a credential UUID — no password field at all. Credentials live in OR's broker.

Verification

  • 736 tests / 2219 assertions green
  • 40/40 hydra gates green, exit 0
  • PHPStan: 2 errors, both pre-existing (SetupController, AutomationCompilerService) — confirmed by running on origin/development unchanged
  • formatVersion had no test coverage at all before this; now pinned both directions, and the v1 back-compat test was verified to fail without the fix (mutation-checked, not assumed)

Also fixed (pre-existing)

deepLinks[0] pointed at schema example (doesn't exist) and route /examples/{uuid} (no page matches) — a deep link to nothing, failing gate-30 on development before this branch.

…onnectors[] binding

v1 emits four file kinds; everything that makes an app WORK — the shared data
registers it binds, the OpenConnector configs feeding them, its automations and
its skills — is left behind. spectr is the sharp case: its manifest references
spectr-live 109 times and it has no meaningful per-app companion schemas, so v1
serialises it to a manifest plus ZERO schemas/ entries and reports success.

Connectors are bound EXPLICITLY (21-connectors.json, ADR-037 fragment mirroring
20-data-registers.json) rather than inferred from register targets. Inference
needed no schema change but would have made an app's published surface depend on
which OTHER objects happened to target a shared register — the same app would
export differently on two instances.
Serializer gains four channels — data-registers/, connectors/, automations/,
skills/ — and stamps formatVersion 2.0. Parser accepts BOTH majors: a v1 repo
parses byte-identically to before, a v2 repo additionally yields channels.

Collectors are total, mirroring collectCompanionSchemas(): a missing source
yields no entries rather than an exception, so serialisation never blocks a
publish. The descriptor's per-channel counts are what stop that becoming a
silently empty artefact — spectr under v1 serialised to a manifest plus ZERO
schemas and reported success.

Connectors come from the EXPLICIT Application.connectors[] binding, with
one-level dependency resolution (a synchronization's source/mapping) reported
separately from declared entries. Secret stripping is defence in depth, not the
primary control: credentials live in OpenRegister's broker and configs reference
them by UUID — verified against the live instance, where source 23 carries
host/port/user/dbname plus a credential UUID and no password field at all.

Parser channel reading is deliberately LENIENT where companion-schema parsing is
strict: a companion schema is load-bearing, a channel entry is additive, so one
unreadable connector must not make a valid repository unimportable.

formatVersion had NO test coverage before this change — nothing asserted it, so
the field governing whether a repo parses at all could change silently. Now
pinned in both directions, and the v1 back-compat test was verified to fail
without the fix.
…don't exist

Pre-existing gate-30 failure, confirmed by running the gates on origin/development
without any of this change: deepLinks[0] declared schemaSlug 'example' (no such
schema) and urlTemplate /apps/openbuild/#/examples/{uuid} (no matching page
route). A deep link to nothing — template leftover, removed rather than pointed
somewhere arbitrary.

Also fixes the @SPEC anchors: gate-46 slugifies an apostrophe, so
"the app's whole configuration" is ...the-app-s-whole..., not ...the-apps-whole...
@rubenvdlinde
rubenvdlinde merged commit 365b3e6 into development Aug 1, 2026
2 checks passed
@rubenvdlinde
rubenvdlinde deleted the feat/app-repo-format-v2 branch August 14, 2026 09:37
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.

2 participants