| 1 | package release |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "crypto/ed25519" |
| 6 | |
| 7 | "github.com/carlosframework/platform/internal/registry" |
| 8 | ) |
| 9 | |
| 10 | // StageHeld is a held row's staging step, ensureStaged, with nothing |
| 11 | // published: the version lands in the version store and live/<host> is |
| 12 | // left alone. It refuses while CARLOS_HOST_CLAIM is on, and a refused write |
| 13 | // is ErrStageBlocked wrapping the cause. |
| 14 | // |
| 15 | // It stages only a build §4.1 authorizes (settings spec rev 11 §4.3a): |
| 16 | // HeldAuthorized, under pub and the box's ratchet r, answers first, and the |
| 17 | // staging binds the digest it returns. A build nothing authorizes is |
| 18 | // ErrHeldNotAuthorized, and nothing is downloaded. |
| 19 | func StageHeld(ctx context.Context, s Store, pub ed25519.PublicKey, r *DigestRatchet, optDir string, rt registry.Route, version string) (*Manifest, error) { |
| 20 | if err := heldStageable(rt, version); err != nil { |
| 21 | return nil, err |
| 22 | } |
| 23 | return stageHeld(ctx, s, pub, r, optDir, rt, version) |
| 24 | } |
| 25 | |
| 26 | // PublishHeld points live/<host> at a staged version, and does it inside |
| 27 | // guard: guard re-checks whatever authorised the stage (for the root relay, |
| 28 | // the registry hold frozen and the host claim re-read) and calls publish |
| 29 | // only if that still holds, as its last step. Staging can take minutes, so |
| 30 | // an authority checked only before it could be stale by now: a move aborted |
| 31 | // or replaced meanwhile must never have live/<host> repointed for it. A nil |
| 32 | // guard is refused: nothing is published on a request's word alone. |
| 33 | // |
| 34 | // Before guard it runs D3 as adoption does (admit, then bind on the staged |
| 35 | // bytes), so a release the box's proof policy does not admit is |
| 36 | // ErrHeldNotProven and live/<host> is untouched. A nil proof is refused. |
| 37 | func PublishHeld(ctx context.Context, s Store, optDir string, rt registry.Route, version string, proof *ProofGate, guard func(publish func() error) error) error { |
| 38 | if err := heldStageable(rt, version); err != nil { |
| 39 | return err |
| 40 | } |
| 41 | if guard == nil { |
| 42 | return errHeldNoGuard |
| 43 | } |
| 44 | if proof == nil { |
| 45 | return errHeldNoProofGate |
| 46 | } |
| 47 | proof.BeginPass() |
| 48 | ptr, v, err := heldProofAdmit(ctx, s, optDir, rt, version, proof) |
| 49 | if err != nil { |
| 50 | return err |
| 51 | } |
| 52 | return publishHeld(optDir, rt, version, proof, ptr, v, guard) |
| 53 | } |
| 54 | |
| 55 | // ServeHeldStageRequests is the root side of the held-stage relay, run by |
| 56 | // `carlos adopt` after its release adoption. For each <name>.json in |
| 57 | // HeldStageDir it validates every field before any filesystem effect (Host |
| 58 | // equal to name and a valid host shape, valid account, app and version, a |
| 59 | // non-empty op and box), asks authorize, stages, asks authorize again with |
| 60 | // the publication as its then, writes the HeldStaged ack and removes the |
| 61 | // request. A request that fails validation, that authorize refuses at |
| 62 | // either point with ErrStaleHeldStage, that D3 refuses (proof, as |
| 63 | // MaterializeHeld applies it: ErrHeldNotProven), or that §4.1 does not |
| 64 | // authorize (ErrHeldNotAuthorized, or a build that does not hash to its |
| 65 | // authorizing digest: ErrManifestDigest) is removed with a log line, |
| 66 | // and live/<host> is never repointed for it. A request whose staging, ack or |
| 67 | // publication fails any other way is kept for the next pass. With no |
| 68 | // request directory it reads nothing and creates nothing. |
| 69 | // |
| 70 | // authorize(req, then) checks req and, if it passes, runs then (when |
| 71 | // non-nil) under whatever guard keeps the check true until then returns. |
| 72 | // Before staging it is called with a nil then. |
| 73 | // |
| 74 | // The directory is agent-owned and this runs as root, so every read, write |
| 75 | // and removal goes through safefile: no symlink is followed at any |
| 76 | // component, and only a regular file with one link is read. |
| 77 | func ServeHeldStageRequests(ctx context.Context, s Store, pub ed25519.PublicKey, r *DigestRatchet, optDir, dataDir string, proof *ProofGate, authorize func(req HeldStageRequest, then func() error) error) error { |
| 78 | return ServeHeldStageRequestsWithPins(ctx, s, pub, r, optDir, dataDir, proof, authorize) |
| 79 | } |
| 80 | |