package hostops import ( "bytes" "context" "crypto/ed25519" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "os" "sort" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/release" ) // Publish freezes exact report bytes into #232's already-fenced backfill. // The collecting manifest is the global create/record-writer freeze. Its ETag // is supplied by the #232 coordinator, which retains that freeze through done. func (b *Backfill) Publish(ctx context.Context, signer release.Signer, collectingETag string) error { raw, tag, err := b.Store.Get(ctx, BackfillKey) if err != nil { return err } var p BackfillPlan if tag != collectingETag || tag == "" || release.VerifyRootEnvelope(b.Directory.PublicKey, raw, &p) != nil || p.V != 1 || p.Phase != "collecting" || p.ID == "" || p.CreatedAt.IsZero() || p.Hosts == nil { return ErrContract } a := ProductionAuthority{Store: b.Store, Directory: b.Directory} census, err := a.census(ctx, "", "", p.ID, p.DirectoryRevision, nil) if err != nil { return err } p.Reports = map[string]string{} for box, report := range census.Reports { if report.CapturedAt.Before(p.CreatedAt) { return ErrIdentity } key, err := reportKey(p.ID, box) if err != nil { return err } body, _, err := b.Store.Get(ctx, key) if err != nil { return err } var envelope CensusEnvelope if json.Unmarshal(body, &envelope) != nil || envelope.Report.Reservation != report.Reservation || !envelope.Report.Complete { return ErrIdentity } sum := sha256.Sum256(body) p.Reports[box] = hex.EncodeToString(sum[:]) } // No activation over unfinished seed state (seeded-instances; merge // review S2): with claims on nothing can commit, abort or finish it. // Checked here, under the collecting freeze, on the census just read. if err := activationSeedRefusal(census); err != nil { return err } for _, r := range census.Records { if err := CheckHostQuarantine(ctx, b.Store, r.Holder.Host); err != nil { return err } selected, ok := p.Hosts[r.Holder.Host] if !ok || selected.Key != r.Holder.Key || selected.ETag != r.ETag || selected.Acquisition == "" || r.Holder.Acquisition != selected.Acquisition { return ErrIdentity } // Each host's box and region come from the signed census, never from // a typed list: the one box whose report names the host, and that // box's enrolled region. Adoption checks the same ownership again. // A host no box reports keeps no box, so it is never adopted. A box // or region the collecting plan already named must agree. var owners []string for ref, report := range census.Reports { for _, i := range report.Installations { if i.Host == r.Holder.Host { owners = append(owners, ref) } } } if len(owners) > 1 { return ErrIdentity } if len(owners) == 1 { enrolled := census.Roster.Roster.Boxes[owners[0]] if (selected.Box != "" && selected.Box != owners[0]) || (selected.Region != "" && selected.Region != enrolled.Region) || enrolled.Region == "" { return ErrIdentity } selected.Box, selected.Region = owners[0], enrolled.Region p.Hosts[r.Holder.Host] = selected } } if len(p.Hosts) != len(census.Records) { return ErrIdentity } p.Phase = "adopting" body, err := release.SignRootEnvelope(signer, p) if err != nil { return err } _, err = b.Store.Put(ctx, BackfillKey, body, tag) return err } // Backfill.Done's refusals, one per condition. They are diagnostics: the // claims run phase puts its own refusal in front of the ones an operator // acts on (the plan, the records, the installations). var ( ErrDoneETag = fmt.Errorf("%w: backfill done: the plan's etag is not the collecting plan's", ErrContract) ErrDoneEnvelope = fmt.Errorf("%w: backfill done: the plan does not verify", ErrContract) ErrDonePhase = fmt.Errorf("%w: backfill done: the plan is not collecting", ErrContract) ErrDoneRecords = fmt.Errorf("%w: backfill done: the record listing is not the plan's", ErrIdentity) ErrDoneClaims = fmt.Errorf("%w: backfill done: control/hosts/ lists a claim", ErrIdentity) ErrDoneAdmission = fmt.Errorf("%w: backfill done: an app admission exists", ErrIdentity) ErrDoneReport = fmt.Errorf("%w: backfill done: a keyed box's census report does not verify", ErrIdentity) ErrDoneStale = fmt.Errorf("%w: backfill done: a census report was captured before the plan", ErrIdentity) ErrDoneInstallations = fmt.Errorf("%w: backfill done: a census report lists installations", ErrIdentity) // ErrDoneRecordsChanged is Done's third listing failing after the write: // done is written, over a record set that is no longer the plan's. It // wraps ErrDoneRecords, so the claims run puts the records refusal in // front of it. ErrDoneRecordsChanged = fmt.Errorf("%w: done was written over a changed record set", ErrDoneRecords) ) // Done closes an empty cohort's collecting plan straight to done (claim // trial Option B, Paul's D1 of 2026-10-20). It adopts nothing, so it needs // no report from a box that cannot report: what it proves is that every box // that can report owns nothing, not that the others own everything else. // // Each of these is its own refusal, and a refusal writes nothing: // - the plan's etag is collectingETag; // - its root envelope verifies; // - it is collecting; // - the live record listing (Apps, Records) is the plan's hosts, by key, // etag and acquisition, both before the reports are read and again // immediately before the write; // - control/hosts/ lists nothing; // - no app has an admission.json; // - every roster box with a census key has a report for the reservation // that verifies exactly as the census verifies it, captured no earlier // than the plan, listing no installation. // // Boxes with no census key are skipped and returned as unreported. The plan // is then written as done under If-Match, with the reporting boxes' digests, // read back, and CheckCreateFreeze must accept it. The records are listed a // third time after that; a change there is ErrDoneRecordsChanged, with done // already written. func (b *Backfill) Done(ctx context.Context, signer release.Signer, collectingETag string) ([]string, error) { raw, tag, err := b.Store.Get(ctx, BackfillKey) if err != nil { return nil, err } if tag == "" || tag != collectingETag { return nil, ErrDoneETag } var p BackfillPlan if release.VerifyRootEnvelope(b.Directory.PublicKey, raw, &p) != nil || p.V != 1 || !safeSegment(p.ID) || p.CreatedAt.IsZero() || p.Hosts == nil { return nil, ErrDoneEnvelope } if p.Phase != "collecting" { return nil, ErrDonePhase } roster, err := b.Directory.Read(ctx) if err != nil { return nil, err } if roster.Roster.Revision != p.DirectoryRevision { return nil, ErrPending } // The live record listing, as the census and collect list it. apps, records, err := b.doneRecords(ctx, p.Hosts) if err != nil { return nil, err } // No host claim, under the prefix the migration census lists. claims, err := b.Store.List(ctx, "control/hosts/") if err != nil { return nil, err } if len(claims) != 0 { return nil, ErrDoneClaims } // No app admission (apps///admission.json). for _, app := range apps { key, err := AdmissionKey(app.Account, app.Name) if err != nil { return nil, err } if _, _, err := b.Store.Get(ctx, key); err == nil { return nil, ErrDoneAdmission } else if !errors.Is(err, os.ErrNotExist) { return nil, err } } // Every keyed box reports, and reports nothing. refs := make([]string, 0, len(roster.Roster.Boxes)) for ref := range roster.Roster.Boxes { refs = append(refs, ref) } sort.Strings(refs) unreported := []string{} p.Reports = map[string]string{} for _, ref := range refs { box := roster.Roster.Boxes[ref] if len(box.CensusPublicKey) == 0 { unreported = append(unreported, ref) continue } key, err := reportKey(p.ID, ref) if err != nil { return nil, err } body, reportTag, err := b.Store.Get(ctx, key) if err != nil { return nil, errors.Join(ErrDoneReport, err) } report, err := verifyCensusReport(body, reportTag, ref, box, p.ID, roster.Roster.Revision, records) if err != nil { return nil, errors.Join(ErrDoneReport, err) } if report.CapturedAt.Before(p.CreatedAt) { return nil, ErrDoneStale } if len(report.Installations) != 0 { return nil, ErrDoneInstallations } sum := sha256.Sum256(body) p.Reports[ref] = hex.EncodeToString(sum[:]) } // A done with no report at all would prove nothing. if len(p.Reports) == 0 { return nil, ErrDoneReport } fresh, err := b.Directory.Read(ctx) if err != nil { return nil, err } if fresh.ETag != roster.ETag { return nil, ErrPending } p.Phase = "done" body, err := release.SignRootEnvelope(signer, p) if err != nil { return nil, err } // The second listing, after every report was fetched and verified and // immediately before the write: a record created while the reports were // read is refused here, and nothing is written. The plan's If-Match // covers only the plan, so this narrows the window; the trial closes it // by stopping the console's record writes for the window. if _, _, err := b.doneRecords(ctx, p.Hosts); err != nil { return nil, err } if _, err = b.Store.Put(ctx, BackfillKey, body, tag); err != nil { return nil, err } got, _, err := b.Store.Get(ctx, BackfillKey) if err != nil { return nil, err } if !bytes.Equal(got, body) { return nil, ErrContract } if err := CheckCreateFreeze(ctx, b.Store, b.Directory.PublicKey); err != nil { return nil, err } // The third listing, after the write and its readback: a record that // appeared between the second listing and now is not in the done plan, // so done was written over a changed record set. Say so; the write // cannot be taken back here. if _, _, err := b.doneRecords(ctx, p.Hosts); err != nil { if errors.Is(err, ErrDoneRecords) { return nil, ErrDoneRecordsChanged } return nil, err } return unreported, nil } // doneRecords lists the live records as collect and the census list them // (Apps, then Records per app) and refuses, with ErrDoneRecords, unless // they are hosts by key, etag and acquisition, no more and no fewer. Done // runs it before the reports, again immediately before its write and once // more after it. func (b *Backfill) doneRecords(ctx context.Context, hosts map[string]BackfillHost) ([]App, map[string]Record, error) { apps, err := Apps(ctx, b.Store) if err != nil { return nil, nil, err } records := map[string]Record{} for _, app := range apps { rs, err := Records(ctx, b.Store, app.Account, app.Name) if err != nil { return nil, nil, err } for _, r := range rs { if _, dup := records[r.Holder.Host]; dup { return nil, nil, ErrDoneRecords } records[r.Holder.Host] = r } } if len(records) != len(hosts) { return nil, nil, ErrDoneRecords } for host, r := range records { frozen, ok := hosts[host] if !ok || frozen.Key != r.Holder.Key || frozen.ETag != r.ETag || frozen.Acquisition == "" || frozen.Acquisition != r.Holder.Acquisition { return nil, nil, ErrDoneRecords } } return apps, records, nil } // verifyCensusReport is the census's acceptance of one roster box's report // for the reservation, for Backfill.Done: the envelope verifies under the // box's enrolled census key, the report is complete and names this box, // enrollment, reservation and roster revision, and every installation names // a listed record, once. It is ProductionAuthority.census's per-box check // line for line; census is an accepted-legacy unit and is left untouched, // and TestDoneVerifiesReportsAsTheCensusDoes holds the two together. func verifyCensusReport(raw []byte, tag, ref string, box hostactors.Box, reservation string, revision uint64, records map[string]Record) (CensusReport, error) { var envelope CensusEnvelope if decode(raw, &envelope) != nil { return CensusReport{}, ErrIdentity } report := envelope.Report canonical, e := json.Marshal(report) if e != nil || len(box.CensusPublicKey) != ed25519.PublicKeySize || !ed25519.Verify(box.CensusPublicKey, canonical, envelope.Signature) { return CensusReport{}, ErrIdentity } if tag == "" || !report.Complete || report.Box != ref || report.Enrollment != box.Enrollment || report.Reservation != reservation || report.DirectoryRevision != revision || report.BootID == "" || report.CapturedAt.IsZero() || report.Installations == nil { return CensusReport{}, ErrIdentity } seen := map[string]bool{} for _, i := range report.Installations { if i.Host == "" || seen[i.Host] || (!i.Route && !i.Local) { return CensusReport{}, ErrIdentity } seen[i.Host] = true r, ok := records[i.Host] if !ok || i.Account != r.Holder.Account || i.App != r.Holder.App || i.Key != r.Holder.Key || i.Family != r.Holder.Family { return CensusReport{}, ErrIdentity } inc, err := r.Incarnation() if err != nil || (i.Incarnation != "" && i.Incarnation != inc) { return CensusReport{}, ErrIdentity } } return report, nil } // VerifyAdopting is the adopters' own acceptance of a published plan, run // by the signer as readback after Publish: the signed plan reads adopting, // and the complete census it froze verifies against its report digests, as // Adoption and AuthorizeBackfillApp require. It writes nothing. func (b *Backfill) VerifyAdopting(ctx context.Context) (BackfillPlan, error) { p, tag, err := b.read(ctx) if err != nil { return p, err } a := ProductionAuthority{Store: b.Store, Directory: b.Directory} if _, err = a.census(ctx, "", "", p.ID, p.DirectoryRevision, p.Reports); err != nil { return p, err } _, fresh, err := b.read(ctx) if err != nil { return p, err } if fresh != tag { return p, ErrPending } return p, nil } // PublishCensusReport is called by the guarded box census collector. Each box // owns its private signing key; the root-signed roster binds its public key to // verified enrollment. The console has only that public key. func PublishCensusReport(ctx context.Context, store release.Store, d hostactors.Directory, report CensusReport, private ed25519.PrivateKey) error { if len(private) != ed25519.PrivateKeySize { return ErrContract } roster, err := d.Read(ctx) if err != nil { return err } box, ok := roster.Roster.Boxes[report.Box] if !ok || report.Enrollment != box.Enrollment || report.DirectoryRevision != roster.Roster.Revision || !bytes.Equal(private.Public().(ed25519.PublicKey), box.CensusPublicKey) || report.BootID == "" || !report.Complete || report.Installations == nil || report.CapturedAt.IsZero() { return ErrIdentity } canonical, err := json.Marshal(report) if err != nil { return err } raw, err := json.Marshal(CensusEnvelope{Report: report, Signature: ed25519.Sign(private, canonical)}) if err != nil { return err } key, err := reportKey(report.Reservation, report.Box) if err != nil { return err } if _, err = store.Put(ctx, key, raw, ""); err == nil { return nil } if !errors.Is(err, release.ErrCASConflict) { return err } previous, _, err := store.Get(ctx, key) if err != nil { return err } if !bytes.Equal(previous, raw) { return ErrIdentity } return nil } // AuthorizeBackfillApp is the only legacy app-admission initialization proof. // It accepts the same complete frozen census as host adoption, including apps // with no host records. Empty results from a failed listing never qualify. func (a ProductionAuthority) AuthorizeBackfillApp(ctx context.Context, account, app string) (string, error) { if a.Backfill == nil || !safeSegment(account) || !safeSegment(app) { return "", ErrContract } p, tag, err := a.Backfill.read(ctx) if err != nil { return "", err } census, err := a.census(ctx, account, app, p.ID, p.DirectoryRevision, p.Reports) if err != nil { return "", err } apps, err := Apps(ctx, a.Store) if err != nil { return "", err } found := false for _, candidate := range apps { if candidate.Account == account && candidate.Name == app { found = true } } if !found { return "", ErrIdentity } for _, r := range census.Records { planned, ok := p.Hosts[r.Holder.Host] if !ok || planned.Key != r.Holder.Key || planned.ETag != r.ETag || planned.Acquisition == "" || planned.Acquisition != r.Holder.Acquisition { return "", ErrIdentity } if err = CheckHostQuarantine(ctx, a.Store, r.Holder.Host); err != nil { return "", err } } _, fresh, err := a.Backfill.read(ctx) if err != nil { return "", err } if fresh != tag { return "", ErrPending } return p.ID, nil }