From d3435222e8585069a5d70b7c5050ce0d3d229343 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 27 Oct 2026 05:30:41 -0400 Subject: [PATCH] fix(accounts): a delivery is composed in the authority core by the pass that applies it A composition delivery (ApplyComposition) recorded the carrier, ran a reconcile pass, and composed the module in the authority core only after that pass returned. The pass ends by converging the fixture's declared installations, so each one found its target not yet composed: the core refused it ("is not a composed solution; a module is installed by composition"), the solution's core installation and its teams' role grants never happened, and a member reaching the solution was issued nothing of its role. - The reconcile pass composes each binding it applies in the core right after applying it, from the inbox's exact applied generation and its signed authority document, before the module registry and the fixture installations run. It covers every delivery path; ApplyComposition's own composition after the pass becomes a replay that changes nothing. - Fixture convergence waits on a target the core has not composed yet, keeping the row for the next pass, instead of reporting a refusal; a module's installation remains accounts' own record alone. The harness delivers a fixture-declared solution to the running host: the pass that applies it installs it in the core, active at the declaring organization alone, with the declared team holding its role. It fails without the change. Co-Authored-By: Claude Opus 5.5 --- .../accounts/code/fixtures/installations.go | 30 ++++++-- .../host_interface_conformance_test.go | 33 +++++++++ .../testdata/host-interface-conformance.yaml | 3 + .../pkg/business/authority_core_delivery.go | 73 +++++++++++++------ .../pkg/business/solution_host_bindings.go | 8 ++ .../testdata/control_plane_call_sites.txt | 2 +- 6 files changed, 119 insertions(+), 30 deletions(-) diff --git a/module/services/accounts/code/fixtures/installations.go b/module/services/accounts/code/fixtures/installations.go index d30749f41..ad56195fd 100644 --- a/module/services/accounts/code/fixtures/installations.go +++ b/module/services/accounts/code/fixtures/installations.go @@ -150,8 +150,13 @@ func (i *Installations) Converge(ctx context.Context) error { w.Info("fixture installation installed", wool.Field("installation", row.declared.describe()), wool.Field("installation_id", installation.GetId())) } // The authority core holds the same installation, which the host - // interfaces decide with (coreInstall). - if err := i.coreInstall(ctx, row); err != nil { + // interfaces decide with (coreInstall). A target the core has not + // composed yet keeps the row for the next pass. + coreWaits := false + switch err := i.coreInstall(ctx, row); { + case errors.Is(err, errCoreTargetNotComposed): + coreWaits = true + case err != nil: failures = append(failures, fmt.Errorf("fixture installation %s: %w", row.declared.describe(), err)) } // Each declared team is exposed once the installation admits it (an @@ -164,7 +169,7 @@ func (i *Installations) Converge(ctx context.Context) error { unexposed = append(unexposed, teamID) } } - if row.teamIDs = unexposed; len(row.teamIDs) > 0 { + if row.teamIDs = unexposed; len(row.teamIDs) > 0 || coreWaits { remaining = append(remaining, row) } } @@ -173,6 +178,10 @@ func (i *Installations) Converge(ctx context.Context) error { return errors.Join(failures...) } +// errCoreTargetNotComposed is a declared installation whose target the +// authority core has not composed yet; the next pass installs it there. +var errCoreTargetNotComposed = errors.New("the authority core has not composed this target yet") + // coreInstall performs the declared installation in the authority core as the // host's own setup (InstallAsHost), then grants the installer role to its // owner and its role to each declared team (GrantRoleAsHost), as the local @@ -187,8 +196,17 @@ func (i *Installations) coreInstall(ctx context.Context, row pendingInstallation return err } // A module's installation is accounts' own record, which Converge's install - // made; the core reads it and installs no module itself. - if m, err := core.Composed(ctx, row.declared.Target); err == nil && m.Kind != domain.KindSolution { + // made; the core reads it and installs no module itself. A target the core + // has not composed yet is waited on: the pass that applies its delivery + // composes it. + m, err := core.Composed(ctx, row.declared.Target) + if code, ok := domain.CodeOf(err); ok && code == domain.NotFound { + return fmt.Errorf("%w: %s", errCoreTargetNotComposed, row.declared.Target) + } + if err != nil { + return fmt.Errorf("the authority core: %w", err) + } + if m.Kind != domain.KindSolution { return nil } inst, err := core.InstallAsHost(ctx, row.orgID, row.declared.Target, row.declared.Role, row.ownerID, i.service.AuthorityInstallStep()) @@ -224,7 +242,7 @@ func (i *Installations) ConvergeAuthority(ctx context.Context) error { var failures []error for _, row := range i.pending { err := i.coreInstall(ctx, row) - if code, ok := domain.CodeOf(err); ok && code == domain.NotFound { + if code, ok := domain.CodeOf(err); errors.Is(err, errCoreTargetNotComposed) || ok && code == domain.NotFound { continue } if err != nil { diff --git a/module/services/accounts/code/pkg/adapters/host_interface_conformance_test.go b/module/services/accounts/code/pkg/adapters/host_interface_conformance_test.go index 3a63a8d51..19f5f5975 100644 --- a/module/services/accounts/code/pkg/adapters/host_interface_conformance_test.go +++ b/module/services/accounts/code/pkg/adapters/host_interface_conformance_test.go @@ -828,6 +828,39 @@ func runHostInterfaceFixture(t *testing.T) { require.Greater(t, count, 0, "the suite's revocations are audited") }) + // A solution the composition delivers once the host is running is composed + // in the authority core by the pass that applies it, before the fixture's + // installations converge in that same pass: the declared installation is + // then the core's too, active at the declaring organization alone, and the + // declared team holds its role there. + t.Run("a solution delivered to a running host is installed where the fixture declares it", func(t *testing.T) { + reconciler.AfterEachPass(installations.Converge) + t.Cleanup(func() { reconciler.AfterEachPass(nil) }) + note := wireScope("conformance-late.note", "read") + deliverSolution(t, ctx, conn, signer, conformanceSolution{alias: "conformance-late", generation: 1, ceilings: []*authorityv1.WorkScopeV1{note}, + roles: map[string][]*authorityv1.WorkScopeV1{"installer": {note}, "viewer": {note}}}) + installed := func(caller authority.Person, org string) (domain.Installation, bool) { + t.Helper() + all, err := core.Installations(ctx, caller, org) + require.NoError(t, err) + for _, inst := range all { + if inst.Target == "conformance-late" { + return inst, true + } + } + return domain.Installation{}, false + } + inst, ok := installed(alex, fixtureOrganization) + require.True(t, ok, "the pass that applied the delivery installed it in the core") + require.Equal(t, domain.Active, inst.Status) + _, ok = installed(authority.Person{ID: conformanceZoe, Organization: globexOrganization, SessionID: "conformance-harness"}, globexOrganization) + require.False(t, ok, "and nowhere the fixture does not declare it") + grants, err := core.ListRoleGrants(ctx, alex, fixtureOrganization, inst.ID) + require.NoError(t, err) + finance := teamNamed(t, ctx, svc, fixtureOrganization, "Finance") + require.Contains(t, grants, domain.RoleGrant{InstallationID: inst.ID, Role: "viewer", TeamID: finance}, "the declared team holds its role") + }) + // Last, because the envelope it writes becomes every module's ceiling: a // real envelope write that narrows a module's binding (the write // PutModuleEnvelope makes, without its platform administrator check) is diff --git a/module/services/accounts/code/pkg/adapters/testdata/host-interface-conformance.yaml b/module/services/accounts/code/pkg/adapters/testdata/host-interface-conformance.yaml index e7ec4f9e4..37aa1ab2e 100644 --- a/module/services/accounts/code/pkg/adapters/testdata/host-interface-conformance.yaml +++ b/module/services/accounts/code/pkg/adapters/testdata/host-interface-conformance.yaml @@ -152,3 +152,6 @@ installations: - {org: Acme Corp, target: conformance-proposals, role: viewer, owner: alex@acme.test, teams: [Finance]} # The assistant solution acts for itself down a chain of modules. - {org: Acme Corp, target: conformance-assistant, role: viewer, owner: alex@acme.test} + # Delivered only once the host is running, as a composition delivers a + # solution after boot: installed at Acme alone, and reached by its finance team. + - {org: Acme Corp, target: conformance-late, role: viewer, owner: alex@acme.test, teams: [Finance]} diff --git a/module/services/accounts/code/pkg/business/authority_core_delivery.go b/module/services/accounts/code/pkg/business/authority_core_delivery.go index 7135736a3..3a53612d4 100644 --- a/module/services/accounts/code/pkg/business/authority_core_delivery.go +++ b/module/services/accounts/code/pkg/business/authority_core_delivery.go @@ -138,8 +138,7 @@ func coreDeliveryError(err error) error { // reconcileAuthorityCore composes, in the core, every delivery accounts has // applied: what a host runs once its core is built, so a module delivered // before this host decided through the core, or while its core could not be -// built, is composed there too. Each is read from the inbox's exact applied -// generation, the signed documents accounts verified when it took them. +// built, is composed there too. func (s *Service) reconcileAuthorityCore(ctx context.Context) error { if s.deliveryStore == nil { return nil @@ -150,29 +149,57 @@ func (s *Service) reconcileAuthorityCore(ctx context.Context) error { } var failures []error for _, record := range records { - manifest, err := record.AppliedManifest() - if err != nil || manifest == nil { - continue - } - var delivered *SolutionDeliveryRecord - if err := s.store.WithControlPlane(ctx, func(ctx context.Context) error { - var err error - delivered, err = s.deliveryStore.ReadDeliveredDocument(ctx, record.BindingID, record.Applied.Generation) - return err - }); err != nil { - failures = append(failures, fmt.Errorf("binding %s: %w", record.BindingID, err)) - continue - } - if delivered == nil { - continue - } - var signed *compositionv1.SignedManifestV1 - if len(delivered.AuthorityDocument) > 0 { - signed = &compositionv1.SignedManifestV1{Document: delivered.AuthorityDocument, Envelope: delivered.AuthorityEnvelope} - } - if _, err := s.applyCoreModule(ctx, manifest, delivered.Payload, signed); err != nil { + if err := s.composeAppliedDelivery(ctx, record); err != nil { failures = append(failures, fmt.Errorf("binding %s: %w", record.BindingID, err)) } } return errors.Join(failures...) } + +// composeAppliedDelivery composes one binding's applied generation in the +// core, read from the inbox's exact applied generation: the signed documents +// accounts verified when it took them. Every delivery path reaches the core +// this way, the composition interface's and the host's delivery endpoint +// alike, so a solution delivered to a running host is one the core can +// install. A generation the core already holds changes nothing. +func (s *Service) composeAppliedDelivery(ctx context.Context, record *SolutionHostBindingRecord) error { + if s.deliveryStore == nil || record == nil { + return nil + } + manifest, err := record.AppliedManifest() + if err != nil || manifest == nil { + return nil + } + var delivered *SolutionDeliveryRecord + if err := s.store.WithControlPlane(ctx, func(ctx context.Context) error { + var err error + delivered, err = s.deliveryStore.ReadDeliveredDocument(ctx, record.BindingID, record.Applied.Generation) + return err + }); err != nil { + return err + } + if delivered == nil { + return nil + } + var signed *compositionv1.SignedManifestV1 + if len(delivered.AuthorityDocument) > 0 { + signed = &compositionv1.SignedManifestV1{Document: delivered.AuthorityDocument, Envelope: delivered.AuthorityEnvelope} + } + _, err = s.applyCoreModule(ctx, manifest, delivered.Payload, signed) + return err +} + +// composeAppliedBinding composes binding's applied generation in the core, +// right after a pass applied it. +func (s *Service) composeAppliedBinding(ctx context.Context, binding string) error { + records, err := s.ListSolutionHostBindings(ctx) + if err != nil { + return err + } + for _, record := range records { + if record.BindingID == binding { + return s.composeAppliedDelivery(ctx, record) + } + } + return nil +} diff --git a/module/services/accounts/code/pkg/business/solution_host_bindings.go b/module/services/accounts/code/pkg/business/solution_host_bindings.go index f253c5993..6aab5c03b 100644 --- a/module/services/accounts/code/pkg/business/solution_host_bindings.go +++ b/module/services/accounts/code/pkg/business/solution_host_bindings.go @@ -391,6 +391,14 @@ func (r *SolutionHostBindingReconciler) RunOnce(ctx context.Context) error { } failures = append(failures, fmt.Errorf("apply binding %q generation %d: %w", applied.Document.Binding, applied.Document.Generation, err)) + continue + } + // The applied generation is composed in the authority core at once, + // whichever path delivered it: a solution the host's delivery endpoint + // took is then one the core installs and the host interfaces decide. + if err := r.service.composeAppliedBinding(ctx, applied.Document.Binding); err != nil { + failures = append(failures, fmt.Errorf("compose binding %q generation %d in the authority core: %w", + applied.Document.Binding, applied.Document.Generation, err)) } } diff --git a/module/services/accounts/code/pkg/infra/testdata/control_plane_call_sites.txt b/module/services/accounts/code/pkg/infra/testdata/control_plane_call_sites.txt index bcd45c8a4..46ea6b557 100644 --- a/module/services/accounts/code/pkg/infra/testdata/control_plane_call_sites.txt +++ b/module/services/accounts/code/pkg/infra/testdata/control_plane_call_sites.txt @@ -29,7 +29,7 @@ pkg/auth/pg/session_store.go: (*SessionStore).RotateRefresh: WithControlPlane pkg/business/audit.go: (*DurableAuditEmitter).Emit: WithControlPlane pkg/business/audit.go: (*Service).AggregateAuditLog: WithControlPlane pkg/business/audit.go: (*Service).QueryAuditLog: WithControlPlane -pkg/business/authority_core_delivery.go: (*Service).reconcileAuthorityCore: WithControlPlane +pkg/business/authority_core_delivery.go: (*Service).composeAppliedDelivery: WithControlPlane pkg/business/authority_delivery.go: (*Service).verifyAppliedAuthorityDocument: WithControlPlane pkg/business/connector_credentials.go: (*Service).SourceSigningSecret: WithControlPlane pkg/business/current_installation.go: (*Service).ModuleCurrentInstallation: WithControlPlane