Project context
The proposed Party Governance and Project evolution plan defines the next persistent model, phased Waldur handover, owner contracts and draft OpenAPI replacements for Themelio routes. The source-managed canonical slice and retained directory have separate contracts.
Canonical source-managed Project
The Project aggregate owns canonical identity, metadata and complete child
memberships. A source reservation allocates its UUID independently of the exact
provider/instance/external reference. The enrolled source scope and management
generation remain fixed for this iteration. Only complete qualified source reads
change its active state; source failures, inventory absence, not-found and
access-denied responses cannot close a Project. The provider is the source of
truth for the Project's existence: when a direct read of the enrolled Project
reports it removed (Waldur is_removed), the refresh worker closes it with the
operator close command. An open Project missing from a complete inventory is read
directly, so its removal is observed.
Source roles become project-administrator or developer through the Project
adaptor. Unsupported roles remain observations and grant no capability. Member
references resolve through Party Governance's published binding port; unresolved
references remain in the complete observation. An absent qualified administrator
is a governance issue. The slice does not invent an administrator or implement
native management writes.
ObserveExternalProject runs as a plain application command inside one owner
transaction. Material changes raise ProjectChanged and, where effective access
changes, ProjectAccessChanged with affected Parties, including removals.
Successful unchanged reads advance separate source-status evidence while
preserving the material Project revision and emitting no new domain facts.
Recorded receipt replay also preserves the original acquisition time.
The canonical service queries are ResolveProjectProviderBinding,
GetProjectIntegrationSnapshot, ListProjectIntegrationSnapshots and
CheckProjectCapability. Point snapshots are complete and bounded to 256 source
members; overflow requires recovery and never silently truncates access. The
inventory materialises immutable, scoped pages of canonical snapshots at one
checkpoint. A capability decision requires fresh complete source
evidence, a currently established exact Party binding at its retained revision,
and current Party authentication eligibility. The default source freshness budget
is two minutes, with a five-minute configuration ceiling. Runtime qualification
determines operational availability and revocation bounds.
The published capability matrix
separates Project information, Run state, Run outputs, launch, manual retry,
cancellation, Workload and Pipeline operations, and access administration. The
three broader project.* capabilities remain transition aliases for #794 only.
Project authority has its own revision stream. An initial reservation or a real source notice within the retry budget publishes pending invalidation; routine scans preserve the last complete evidence until it expires. A first or changed complete-read failure publishes failed authority. Repeated identical failures back off, then probe every five minutes after eight attempts. A successful complete read restores ready authority automatically; operator recovery can bring the next probe forward. Each successful complete read publishes one ready renewal with its original observation time and absolute validity. An unchanged read advances this authority revision without advancing the Project aggregate. The transition and exact public event bytes commit together. Replay retains the old authority timestamps. Confirmed deliveries are cleaned after 24 hours. The authority queues retain messages for 305 seconds; consumer restart and repair use the published current snapshot. See the authority delivery contract.
Consumers merge this stream with material, Party and binding views and fail
closed while rebuilding or after expiry. The owner query remains the
decision-time gate until consumers qualify equivalent local checks; separate
event queues do not form an ordering barrier. The contract guide records the
measurable bounds, transition callers and accepted-work continuation policy.
The operator composition services/project-operations accepts close,
security-cancel and advance-management. Closure is permanent. Security
cancellation and management handover advance the management generation, fencing
previously accepted work while leaving current membership intact. The aggregate,
command receipt, material events, authority invalidation and source-lease fence
commit together. A source refresh preserves closure and uses the new generation;
it cannot reopen the Project or restore an older generation. An operator decision
preserves the last complete-read timestamp and queues a fresh acquisition within
the existing retry budget. Exhausted sources retain their periodic recovery probes.
Project-scoped member suspension also needs Beto-owned commands and integration
facts. The accepted-work policy
requires immediate suspension of personal Project access and Site account
delivery (#765) to retain the exact native account and binding needed to finish
accepted work or authorised cancellation and cleanup. This owner workflow remains pending implementation.
See the application workflow for owner ports, recovery, query evidence and source attribution. Metallo owns its local projections and consumer receipts. Native Project writes, fulfilment and browser subscriptions remain separate work. Source Control also owns its GitLab group/member effects and durable projections. Its owner-query adaptor and scopes remain disabled pending qualification.
Runtime and enrolment
Beto opens Project's isolated PostgreSQL database alongside Party Governance.
Canonical broker queries authenticate the publisher and require explicit
PROJECT_QUERY_SCOPES: a JSON array of subject, provider, instance and
scope. It defaults to an empty array. Execution Core, Pipeline Coordination and
Workload Portfolio have separate broker principals, queues and exact Project
query scopes. The Waldur registration supplies those scopes to Beto's source
Secret; provider credentials remain confined to Beto.
For Waldur, scope uses the customer's lower-case UUID without hyphens, matching
the source adaptor's normalised enrolment and complete-read evidence.
The source worker is disabled by default. PROJECT_WALDUR_ENABLED=true requires
the complete installation/customer scope, read-only API token, STOMP credentials
and verified CA bindings. Configuration lives in
the composition root.
Runtime defaults are a 30-second inventory interval, 30-second fenced database
lease and 25-second acquisition deadline. Permission checks fail closed when
source evidence is pending, failed or older than the configured freshness budget.
With the accepted five-second source clock skew, the default absolute revocation
ceiling is 125 seconds after a change immediately following a complete read; the
maximum configured ceiling is 305 seconds. Delivery and consumer progress share
the remaining validity window and are observable from the event's transition
time and the consumer's committed apply time. Live qualification must establish
normal availability and lag below those ceilings.
In the dev-box deployment, registration targets namespace pharos, pod label
app: beto and Secret beto-project-source. It supplies restricted reader and
STOMP credentials, and writes the enrolled scope alone to Secret
metallo-project-source, which Metallo's views load for (#780). Project consumes
STOMP 1.2 over verified TLS on port 61614.
Only committed notice/quarantine receipts permit ACK. REST acquisition and
periodic inventory use separate Project ports. No Project/Party provider
credentials are installed in Metallo. Production execution workers remain
paused while their consumer implementation is completed separately.
Project resolves bindings and checks eligibility through Party Governance's published query contracts. The composition root supplies the in-process, scope-authorised query handlers; Project's adaptor imports only those published contracts. Cross-context writes, implementation imports and foreign SQL are absent. New bindings are picked up on a subsequent complete source acquisition.
The retained login, AUP and provider writers stay authoritative. The Waldur 8.1.3 infrastructure adaptation requires live login/AUP and registration qualification before deployment. See the migration provenance and gates.
Retained static directory
GetProjects is a read-only query returning the complete static directory.
There is no aggregate, write command, database, migration, event or browser
projection associated with this directory operation.
The application query receives ports.ProjectReadRepository. Its Go read model
preserves the legacy Themelio GET /api/models/projects fields: numeric id,
name, path, full_path, nullable description and avatar_url,
visibility, web_url, created_at and updated_at. The numeric ID remains an
opaque legacy provider binding. It grants no canonical Pharos identity.
The mock returns two deterministic example projects and allocates fresh data for each read. All trusted service callers receive the same list. It performs no network access. The legacy endpoint filtered GitLab subgroups using the caller's token and excluded groups marked for deletion. A future real adaptor needs an explicit user-scope design, complete pagination, deletion filtering and provider error mapping before replacing the mock.
The web application uses ListMyProjects through GET /api/v1/me/projects.
Project derives membership from the authenticated Party actor and checks current
project.view authority for every result. It returns paginated canonical UUIDs,
revisions, names, organisations and lifecycle. The retained directory remains
available for historical consumers and fixtures. See the
member listing contract.
The composition root registers GetProjects
through its generated response-bound registration.
The schema generates the plain input/result models and
owner codecs.
The service's shared
request registry handles dispatch,
envelope validation, complete handler registration and reply encoding. Provider failures become redacted
temporarily_unavailable replies; invalid envelopes go to quarantine.
Beto's composition module binds the mock and registry through Fx and runs the
RabbitMQ consumer; DI and wire dependencies remain outside application packages.
The registry dispatches plain application values through QueryBusPort. The
application handler selects the complete Projects response; its published read
model aliases the generated plain Project. The checked encoder preserves nullable
fields and rejects integers outside the JavaScript-safe range. Encoding failures
produce redacted temporarily_unavailable replies. Generated identifiers preserve
the existing wire names. Registration rejects missing mappings, invalid or foreign
identifiers, duplicate handlers and input/reply type mismatches before workers start.
See the wire contract and decision 0046.
Verification
The query tests cover legacy fields, empty results, independent mock data, malformed and foreign contracts, and redacted provider failures. Registry tests cover duplicate and mismatched registrations, independent query dispatch and cancellation. Composition tests cover graph resolution, partial-start cleanup and worker draining. The RabbitMQ lane checks reply correlation, quarantine, restricted permissions and redelivery after an unroutable reply, then repeats the query against the built service image.
Recorded on 13 October 2026 for the slice based on fb798afca:
Reference improvements — 14 October 2026
Beto adopts the reference's service/context adaptor organisation, selective contract generation, explicit generated output locations and pinned Go static checks. See the Beto adaptor map for navigation and commands. The legacy project fields, static results and wire identities remain unchanged. At that stage Project had no persistence authority or browser subscription.
The broker manifest declares its query boundary. It generates exact resource and topic permissions for Project and a separate topology role. Beto runtime users cannot configure resources, bind queues or bypass routing through the default exchange. API callers have the scoped temporary-reply-queue capability in the transport contract. The topology role can redeclare registered resources and bind the registered query/reply keys. RabbitMQ requires queue read and delivery-exchange write for dead-letter declarations; that limited capability remains confined to administration.
Host provisioning preserves the Project runtime password and returns a fresh
TOPOLOGY_BROKER_URL for the current administration pass. It is never mounted in
Beto's workload. The standalone topology executable requires the
pharos_beto_topology identity. Kubernetes provisioning restarts Beto after applying permissions to replace cached
authorisation; retired queues retain their evidence
until explicit administrative cleanup.
Verification for this update on 14 October 2026:
Reference alignment — 15 October 2026
Reviewed the reference through 3c8b080. Project now uses schema-derived query/reply
identifiers and direct composition with owner mappings and QueryBusPort. Shared
broker consumers reject envelope visibility that disagrees with their subscription.
The complete static listing and published Protobuf bytes retain their contract.
The reference's durable command reactions and explicit realtime publications
remain future work for a write-capable context. See
decision 0046.
Generated query contracts — 15 October 2026
Reviewed the subsequent reference commits 33d464a and a9d7bf6. The Project
schema now owns the plain input/result types, response association, checked codecs
and typed registration. The existing application read-model name aliases its
generated resource type. See the contract guide
for generation, supported field shapes and numeric limits.
pnpm verify and pnpm test:integration both passed after this update. This
includes all Go static/race/build checks, architecture and generation fixtures,
Python and Web checks, real PostgreSQL/RabbitMQ, all service images, the packaged
Project query and Chromium sessions/realtime. Schema-evolution tests compile and
exercise generated Go encoders with optional, nested and repeated fields.
Historical fixtures preserve exact request/reply bytes; legacy JSON keys and null
values also pass. Generation prunes retired models/adaptors and preserves
handwritten owner files.
Both lanes passed again after integrating API endpoint commit 2796ab049. The
API generates the schema metadata within its own module; concurrent broker queries
and the built API's GET /api/v1/projects round trip through Beto also passed.
Live UAT, Minikube/k3s rollout, NetworkPolicy enforcement, dev-box takeover, real provider integration, remote CI and production deployment remain unrun.
Main integration — 16 October 2026
Merged main through 78d20394c. The Argo administration image now provisions
Project's broker credentials and topology before API and Beto start. Project
remains storage-free alongside Metallo Site Runtime's private SQLite journals.
API retains the native listener settings introduced on main.
pnpm verify, full contract regeneration and architecture checks passed on the
merged tree. This includes API/Beto static checks and native race tests, the
executable listener checks, 104 tooling tests, 344 development-tool tests and
1,221 Metallo tests, Web types/build, Kubernetes/Argo renders and Ansible syntax.
The development-tool suite skipped five launcher tests because the expected
Docker socket was absent, and one Linux process-identity test on macOS. All 11
focused broker provisioning tests passed after adding coverage for mounted
credentials and secret-free output from the administration entry point.
The merged tree has not rerun the real PostgreSQL/RabbitMQ, built-image, Chromium or edge lanes while container testing is paused. Native dev-box takeover is pending release of another checkout's workstation connection. Live k3s rollout, NetworkPolicy enforcement, UAT, real provider integration and production deployment remain unverified for this merge.
Project operator commands
Run services/project-operations from a controlled maintenance environment with
PROJECT_OPERATIONS_DATABASE_URL set to the restricted Project owner database
credential. This executable is the independent management authority; its commands
have no member HTTP or RabbitMQ registration and its credential is absent from
Runtime containers. The canonical operator ID is audit attribution. Enrol the
same provider, instance, scope, initial generation and source freshness as Beto.
The request is an immutable JSON file, reused verbatim after an uncertain result:
Invoke the executable with -request command.json -provider waldur -instance INSTANCE -scope SCOPE -initial-generation 1 -source-freshness 2m. A stale revision refuses
without changing Project state. Replaying a successful command returns its receipt
without advancing the generation or renewing authority. Reusing its ID with changed
material is an idempotency conflict. Existing outbox publishers deliver the facts;
Execution Core and Pipeline Coordination save policy cancellation on observing a
permanent fence, then recover native stop and cleanup independently of membership.
For an exhausted source, use the same executable with -recover-source. The
request contains ID, ProjectID, OperatorID, ExpectedGeneration, Reason
and At; omit Action and ExpectedVersion. Read generation,
failure_attempts, last_failure, last_attempt_at and exhausted_at from the
scoped pharos.project_sources row before preparing it. Resolve the provider
problem first. The response is resumed, not_found, not_exhausted or
conflict, with the recorded source generation. Resume resets the eight-failure
budget and makes work due ahead of the next periodic probe, retaining the failure and memberships. A later notice
can mark authority pending; a complete read must succeed to restore ready
authority. Recovery works before a Project aggregate exists. Reuse the
exact request file after an uncertain result; an old receipt cannot resume a
later exhaustion.
GetProjectMembers serves the API’s Project member view after a current
project.view decision. It returns the complete bounded membership as canonical
Party IDs and roles, the Project revision and a count of unresolved bindings.
It validates exact Party binding evidence and rechecks caller authority and the
Project/source revisions before returning. It exposes no provider IDs or guessed
profile names. repositories.delete grants Project administrators the authority
checked by Source Control’s durable deletion workflow; developers remain denied.
Page source
Source snapshot · Revision f928834c8.