Skip to content

APP-01: Publisher service configuration UI #116

Description

@ja573

Status

PROPOSED - AWAITING CTO SPECIFICATION APPROVAL

Implementation: NOT AUTHORIZED

Current gate: HOLD

Programme

Publisher Services and Distribution Configuration

Programme stage: Phase C - application interfaces

Parent programme issue: thoth-pub/thoth#765

Repository responsibility

Repository: thoth-pub/thoth-app

thoth-app owns authenticated publisher/staff UI and client behaviour only.

thoth-pub/thoth owns:

  • publisher package semantics;
  • package capabilities;
  • distribution-platform inventory and descriptors;
  • linked-platform normalization;
  • service-configuration authorization;
  • configuration version/concurrency semantics;
  • durable distribution-job lifecycle and state;
  • the GraphQL schema.

APP-01 must consume those contracts and must not reproduce them as independent frontend policy.

Task identity

Task ID: APP-01

Risk: MEDIUM

Coordination issue: this issue.

Implementation decomposition is proposed before coding:

  • APP-01A - v1.7 contract pin and publisher read-only configuration;
  • APP-01B - superuser configuration editing and concurrency/error UX;
  • APP-01C - truthful latest back-catalogue job presentation.

Each child is an independently implementable repository-local task and therefore requires its own approved issue/specification, exact authorized base SHA, branch and PR.

No implementation branch or PR is authorized by this issue alone.

Current repository baseline

Observed development branch:

dev @ 7a4e7c6ceaec36fbdb201eaeb9ae36985a709889

Observed release/default branch:

main @ 3f2c4a3428641078b820f78c8e08a844b49c71de

Both currently resolve to source tree:

2ea8c49a1936dd25cbea74a749532a32bb6d8399

Observed topology:

dev -> main

Normal task target under the merged root AGENTS.md:

dev

Authorized implementation base SHA:

NONE - implementation is not yet authorized.

Every child implementation must perform a fresh preflight and record the exact dev SHA separately when implementation is explicitly authorized.

Branch-readiness gate

BR-APP-01 remains separate branch-normalization work.

APP-01 requires either:

  1. completed BR-APP-01; or
  2. an explicit durable CTO exception permitting APP-01 child tasks to use the current dev -> feature/... -> dev topology.

The proposed exception does not authorize branch normalization, main promotion, Vercel/provider changes, deployment or production activation.

Upstream contract

Upstream repository: thoth-pub/thoth

Upstream release: v1.7.0

Exact release/source commit:

40e9c06d4ab76217c3ef277dd539d3b5580e2bb8

Exact source tree:

8797558959fcf1f418eb33ca080b469e97783cbd

Backend contracts consumed:

APP-01 must pin the v1.7.0 contract as a whole rather than regenerate against an unpinned moving API.

Exact SDL control

The upstream repository does not commit the generated thoth-client/assets/schema.graphql artifact. The canonical upstream schema generation path is create_schema().as_sdl().

Before APP-01A implementation proceeds:

  1. generate the GraphQL SDL using the canonical upstream generation path from exactly 40e9c06d4ab76217c3ef277dd539d3b5580e2bb8;
  2. record the generated SDL SHA-256 in this issue or APP-01A's issue;
  3. store/use an immutable local contract snapshot as APP-01 codegen input;
  4. record its provenance: upstream repo, v1.7.0, commit and tree;
  5. regenerate gql/;
  6. record the complete generated-code diff;
  7. prove regeneration from the same pinned SDL is clean/deterministic.

Code generation against https://api.test.thoth.pub/graphql is not acceptable evidence of exact v1.7.0 compatibility for APP-01.

If the upstream GraphQL schema changes while APP-01 is in progress, STOP. Do not silently regenerate against the new schema. Perform an explicit compatibility assessment and re-pin only under an approved scope/contract update.

Dependencies

Satisfied upstream source dependencies:

  • BE-01: satisfied by v1.7.0;
  • BE-02: satisfied by v1.7.0;
  • BE-03: satisfied by v1.7.0;
  • BE-04: satisfied by v1.7.0 for the job-aware read contract.

Remaining app/readiness dependencies:

  • explicit BR-APP-01 completion or CTO branch exception;
  • app portion of CG-11 resolved, or an explicit CTO readiness exception;
  • exact v1.7.0 SDL generated and digest recorded;
  • this APP-01 specification approved;
  • child issue/specification approved before each child implementation.

MIG-01

MIG-01 is not a source-code or GraphQL-contract dependency for APP-01 implementation.

It does affect production data population. Until production package/platform backfill is reconciled, APP-01 must render the API's actual current state rather than invent expected legacy configuration.

Production-facing release/activation must separately decide whether MIG-01 completion is required before publisher users should rely on the displayed configuration.

APP-01 must not perform, invoke or simulate MIG-01.

Problem

The v1.7.0 backend now owns authoritative publisher service configuration, including package, effective package capabilities, enabled distribution platforms, linked-platform normalization, optimistic concurrency, and durable distribution-job reporting.

thoth-app currently has no UI consuming this contract.

Publisher users need to see their permitted service configuration. Superusers need a bounded editing interface using the backend's dedicated mutation. Job-aware presentation must show actual BE-04 durable job state and must not derive delivery status from desired configuration.

Scope

Publisher-facing service configuration

For the active authenticated publisher, display:

  • current subscription package;
  • effective package capabilities returned by the API;
  • currently enabled distribution platforms;
  • platform display labels and applicable backend-provided metadata;
  • read-only service configuration for ordinary publisher users.

Capabilities must be consumed from effectiveCapabilities. Do not implement a package-to-capability matrix in TypeScript.

Platform interaction/display must consume distributionPlatformOptions, including backend-owned linked-group and assignability metadata. Do not maintain an OAPEN/DOAB rule table in the frontend.

Superuser configuration editing

For superusers only, provide controls to replace:

  • subscription package;
  • enabled distribution platforms.

Mutation: replacePublisherServiceConfiguration.

The mutation request must include the updatedAt value from the exact configuration version the user edited as expectedUpdatedAt.

Package changes must never implicitly add/remove distribution platforms in client code.

Linked platform closure must not be implemented as a second authoritative frontend algorithm. The UI may present the backend linkage clearly, but the submitted state and returned server-normalized state remain authoritative.

After a successful mutation:

  • discard any locally assumed final state;
  • use the exact mutation response and/or a fresh protected configuration read;
  • update/invalidate query state from the server result;
  • show success only after the server has accepted the mutation.

Optimistic-concurrency behaviour

The backend GraphQL error classification STALE_SERVICE_CONFIGURATION must be preserved by app GraphQL error plumbing.

On a stale write:

  • do not claim success;
  • do not automatically overwrite the newer configuration;
  • re-read the current configuration;
  • clearly tell the user that the configuration changed since they loaded it;
  • require a deliberate new edit/retry against the fresh version.

Do not parse human-readable error strings to identify concurrency errors.

Job-creation-disabled behaviour

Automatic distribution-job creation is intended to remain OFF.

The app must not attempt to activate or configure that switch.

When the API rejects a qualifying new automatic-push activation with DISTRIBUTION_JOB_CREATION_DISABLED:

  • treat the mutation as failed;
  • make clear that the configuration was not saved;
  • leave/refetch client state from the API;
  • do not show a pending/synthetic onboarding job;
  • do not retry automatically.

Job-aware presentation

Where superuser UI displays onboarding/job state, use the actual BE-04 contract.

Use the nullable latestBackCatalogueJob associated with the server's service-configuration report surface.

If no job exists, represent that truthfully, for example:

No back-catalogue onboarding job is recorded.

A null job must never be transformed into:

  • success;
  • failure;
  • pending;
  • not required;
  • already disseminated;
  • not disseminated.

When a job exists, display only API-backed facts useful for staff, such as:

  • job status;
  • targets;
  • attempt count;
  • latest bounded worker-reported failure information where present;
  • relevant timestamps where useful.

Do not infer dissemination execution or observed remote delivery from a durable job alone.

Maintain these distinctions:

desired configuration != durable distribution job != dissemination execution/observed delivery

Existing app integration

Prefer extending the existing authenticated publisher profile path rather than creating a parallel publisher administration architecture.

Reuse:

  • /admin/publisher;
  • the existing Profile widget;
  • active publisher state from the publisher state machine;
  • src/entities/publisher Feature-Sliced Design boundaries;
  • generated graphql(...) documents;
  • GraphqlService;
  • TanStack Query hooks;
  • existing user/superuser data for UI affordances.

Do not change NextAuth/Zitadel policy.

App GraphQL error plumbing

Current GraphqlService keeps the first GraphQL error message but discards error extensions.

APP-01B may make a narrowly scoped, backwards-compatible enhancement that preserves the stable GraphQL error type for caller inspection while retaining the existing useful message behaviour.

This change must:

  • not log bearer tokens or request headers;
  • not expose full sensitive GraphQL payloads;
  • keep existing callers compatible;
  • have focused unit tests;
  • avoid introducing backend-specific policy into the shared transport layer beyond preserving server-returned structured error metadata.

Authorization and security invariants

  • The Thoth backend remains the authorization authority.
  • Hidden controls are presentation only, never an authorization boundary.
  • Ordinary publisher users must not receive a superuser mutation path from UI presentation.
  • Direct/manual requests remain the backend's responsibility to deny.
  • No service/machine credential may be placed in browser code.
  • No NextAuth/Zitadel policy or role semantics change is in scope.
  • Do not create a frontend role/authorization matrix mirroring backend policy.
  • Do not log access tokens, credentials, authorization headers or complete sensitive GraphQL request/response payloads.
  • Authorization failures must not leave stale optimistic UI that appears saved.

UI/domain invariants

  • No package capability matrix exists in the frontend.
  • No independent linked-platform normalization algorithm exists in the frontend.
  • No platform eligibility matrix exists in the frontend.
  • No distribution-job state machine exists in the frontend.
  • No configuration-version semantics are reimplemented in the frontend.
  • Server-normalized state wins after every mutation.
  • No job is never treated as evidence of dissemination success or failure.
  • Package selection never implicitly changes platform assignments in client logic.
  • Non-assignable options follow API-provided metadata.
  • Existing publisher/staff workflows must not regress.

Proposed implementation decomposition

APP-01A - Contract pin and publisher read-only configuration

Scope:

  • capture/pin exact v1.7.0 SDL;
  • make codegen deterministic against that pinned artifact;
  • regenerate gql/;
  • add publisherServiceConfiguration query;
  • add distributionPlatformOptions query;
  • add entity/service/hooks for reads;
  • render current package, effective capabilities and enabled platforms in the existing publisher profile;
  • ordinary publisher presentation remains read-only.

No mutation.
No job-status presentation.
No shared GraphQL error plumbing change unless required only to preserve existing behaviour.

APP-01B - Superuser service-configuration editing

Depends on APP-01A.

Scope:

  • superuser-only edit controls;
  • replacePublisherServiceConfiguration;
  • expectedUpdatedAt;
  • server-normalized mutation result;
  • stale-write UX;
  • job-creation-disabled UX;
  • narrowly scoped structured GraphQL error preservation;
  • query invalidation/refetch after outcomes.

No backend change.
No job worker.
No job creation.
No provider/runtime configuration.

APP-01C - Truthful back-catalogue job presentation

Depends on APP-01A; may follow APP-01B unless review shows no file overlap.

Scope:

  • superuser-only consumption of the existing service-configuration report surface for the active publisher;
  • nullable latestBackCatalogueJob;
  • status, targets, attempt/failure data that exists in the API;
  • explicit no-job semantics.

No APP-02 report page.
No CSV report/export.
No broad staff filters.
No dissemination execution UI.
No observed-delivery model.

Explicit non-goals

  • APP-02 staff subscription/reporting UI;
  • APP-03 licence options;
  • MIG-01 audit/backfill;
  • DIS-01 comparison/cutover;
  • DIS-02 worker;
  • OAI-PMH;
  • changing package/capability semantics;
  • changing distribution platform inventory;
  • changing linked-platform normalization;
  • changing backend authorization;
  • changing durable-job lifecycle;
  • creating jobs locally;
  • activating automatic job creation;
  • running migrations;
  • provider/Vercel reads or writes;
  • deployment;
  • production release or activation;
  • branch normalization;
  • general refactoring of publisher administration.

If implementation starts absorbing any of these, STOP for scope review.

Expected test coverage

APP-01A

  • an authorized publisher can render its permitted configuration;
  • package comes from protected API configuration;
  • capabilities come from effectiveCapabilities;
  • no frontend package-capability lookup exists;
  • enabled platforms come from API assignments;
  • display/link/assignability metadata comes from distributionPlatformOptions;
  • ordinary publisher UI exposes no editing controls;
  • changing active publisher reloads the applicable protected configuration;
  • authorization/API failure does not fabricate configuration;
  • generated GraphQL types correspond to the exact pinned v1.7.0 SDL;
  • rerunning generation from the pinned SDL produces no unexplained generated diff.

APP-01B

  • mutation sends the exact configuration token as expectedUpdatedAt;
  • successful mutation replaces client state with the server-normalized result;
  • package editing does not implicitly alter platform state in client code;
  • backend-linked normalization is reflected from the response;
  • ordinary publisher UI exposes no mutation controls;
  • backend remains authoritative if a mutation is attempted outside UI controls;
  • STALE_SERVICE_CONFIGURATION produces explicit stale-write UX and a refresh, with no success state;
  • stale writes are never blindly retried;
  • DISTRIBUTION_JOB_CREATION_DISABLED is shown as a failed/non-saved change;
  • authorization/API failures leave client state consistent with the server;
  • structured GraphQL error metadata is preserved without regressing existing GraphQL callers.

APP-01C

  • a real latest job is rendered from BE-04 state;
  • job status is never derived from configured platforms;
  • attempt/failure presentation comes only from API fields;
  • null latest job renders as no recorded job;
  • null latest job does not render success, failure, pending or dissemination status;
  • desired configuration and job state remain visually/textually distinct;
  • no APP-02 filters/CSV/reporting behaviour is introduced.

Regression/security

  • existing publisher profile functionality remains functional;
  • non-superusers retain their existing publisher behaviour;
  • no token/header/full sensitive payload appears in errors or logs;
  • existing auth/session flow remains unchanged.

Required validation

For every applicable child PR, reverify package scripts and run/report:

npm ci
npm run generate
npm run lint
npm test -- --run --coverage
npm run build

Also run focused tests for the changed modules.

The generated-code check must use the exact pinned v1.7.0 SDL.

Any pre-existing lint/test/build/codegen failure must be measured and explicitly separated from a regression introduced by the child task.

GitHub CI coverage is currently insufficient by itself to constitute this validation.

CI/readiness dependency

The app portion of shared control gap CG-11 remains open.

A separate repository-local CI/readiness task should determine the approved GitHub gates for:

  • lint;
  • production build;
  • codegen consistency.

Do not modify .github/workflows/* opportunistically in APP-01.

If the CTO chooses to allow APP-01 implementation before CG-11 is closed, the exception and compensating validation requirements must be explicit and durable before implementation authorization.

Compatibility

Backend compatibility baseline is exactly v1.7.0.

APP-01 must remain compatible while:

THOTH_DISTRIBUTION_JOB_CREATION = OFF

That means:

  • reads remain useful;
  • package-only changes remain governed by the API;
  • pull/manual behaviour remains governed by the API;
  • qualifying new automatic-push activation may be rejected by the backend;
  • APP-01 reports that rejection truthfully and does not activate the switch.

No client assumes DIS-01 or DIS-02 is deployed.

If the upstream v1.7.0 contract is changed by later backend development, existing APP-01 work continues against the pinned contract until an explicit compatibility decision authorizes a re-pin.

Merge/deployment ordering

Repository/source dependency:

thoth v1.7.0 -> APP-01A -> APP-01B / APP-01C

The upstream source contract is already released.

MIG-01 may proceed independently because it affects data population rather than the APP-01 client contract.

APP-01 may be implemented and merged to thoth-app/dev while automatic distribution-job creation remains OFF.

Each child requires:

implementation -> independent exact-head review -> merge readiness/authorization -> merge to dev

Merge to dev is not production release authorization.

Later:

dev validation -> release readiness -> dev/main promotion -> production observation

remain separate gates.

Any PR-triggered Vercel preview/status creation must be identified as an expected automatic side effect of an authorized PR action. It does not authorize provider reads/writes.

Rollout

Prefer additive UI changes.

Initial dev/preview validation should cover:

  • ordinary publisher read-only view;
  • superuser read/edit;
  • one server-normalized linked-platform response;
  • stale-write behaviour;
  • authorization failure;
  • job-creation-disabled behaviour with no false saved state;
  • no-job presentation;
  • representative actual job presentation if suitable non-production/test data exists.

No provider/runtime access is implied.

APP-01 must not change THOTH_DISTRIBUTION_JOB_CREATION.

Production release remains separately authorized.

Rollback

Primary rollback is an app-only revert of each bounded APP-01 child PR.

No backend rollback, database migration rollback, distribution-job deletion or dissemination cleanup should be required by APP-01 itself.

If a release needs to be withdrawn, revert/hide the app UI while retaining the additive v1.7.0 backend contract.

HOLD conditions

HOLD if:

  • live dev base cannot be established;
  • the branch exception/BR-APP-01 gate is unresolved;
  • the APP-01 or child specification is not approved;
  • CG-11/readiness has neither closure nor an explicit CTO exception;
  • the exact v1.7.0 SDL and digest are not established;
  • generated client types do not match the pinned SDL;
  • a materially overlapping app PR appears;
  • required provider action is not separately authorized.

BLOCKED conditions

BLOCKED if:

  • the current backend contract lacks information required to render correct UI state;
  • the backend and app authorization models require contradictory behaviour;
  • server-normalized platform state cannot be represented without inventing frontend policy;
  • correct concurrency handling requires a backend contract change;
  • required job state cannot be obtained from BE-04 without fabricating it;
  • safe implementation requires an unapproved cross-repository architecture change.

STOP conditions

STOP if:

  • APP-01 begins changing package/platform/domain architecture;
  • APP-01 begins implementing APP-02 or APP-03;
  • APP-01 begins performing MIG-01 work;
  • APP-01 begins implementing dissemination/worker behaviour;
  • a new reusable cross-programme abstraction is proposed;
  • a task attempts to activate distribution-job creation;
  • implementation scope expands into auth-policy or provider configuration changes.

Current authorization

Authorized for the current control step:

  • read-only repository/GitHub inspection;
  • creation of this repository-local APP-01 issue;
  • separately authorized chronological linkage/gate comment on parent issue thoth-pub/thoth#765.

Not authorized:

  • source/worktree modification;
  • new repository files;
  • delete/move/rename;
  • branch creation;
  • commit;
  • push;
  • PR creation/update;
  • other issue/comment mutation;
  • manual CI dispatch/rerun;
  • provider/runtime read or write;
  • migration execution;
  • release/tag/publication;
  • merge;
  • deployment;
  • production activation.

Current gate

HOLD - specification candidate created; implementation is not ready or authorized.

Next actions

  1. CTO reviews/approves or amends this APP-01 specification and proposed A/B/C decomposition.
  2. Separately resolve/record the BR-APP-01 exception or complete branch normalization.
  3. Create/resolve the app-local CG-11 CI/readiness task, or explicitly authorize a temporary readiness exception.
  4. Capture the exact v1.7.0 SDL digest under separately authorized work.
  5. Prepare APP-01A with a fresh exact dev base.
  6. Only after its own approved child specification and explicit action authorization may APP-01A implementation begin.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions