// Package publish implements the revision publication pipeline and the trust // transitions it enforces. // // ArchitectureBlueprint.md section 47 assigns different trust levels to // different things: AI interpretation is advisory, generated code is an // untrusted candidate, deterministic tests are verification evidence, and only // a signed revision is a deployable artifact. Section 52 asks that this // distinction stay visible in code and data models rather than living in // reviewers' heads. // // The types here make it visible. A candidate cannot be published, because the // publish path takes a Verified value, and the only way to obtain one is to // pass verification. That is a weaker guarantee than a proof, but it means the // unsafe path has to be written deliberately rather than reached by accident. package publish import ( "errors" "fmt" "github.com/tegwick/fluid-core/internal/contract" "github.com/tegwick/fluid-core/internal/signing" ) // Candidate is an unverified revision descriptor. // // Whatever produced it — a human, a Builder, an LLM — it carries no authority. // The field is unexported so that a Candidate can only be made through // NewCandidate, and cannot be forged into a Verified value by struct literal // from another package. type Candidate struct { descriptor contract.Revision origin contract.Actor } // NewCandidate wraps a descriptor as an untrusted candidate. func NewCandidate(d contract.Revision, origin contract.Actor) Candidate { return Candidate{descriptor: d, origin: origin} } // Descriptor returns a copy of the candidate's descriptor for inspection. // // It is a copy on purpose: verification decides about a specific byte sequence, // and handing out a mutable reference would let a caller change the artifact // after it was judged. func (c Candidate) Descriptor() contract.Revision { return c.descriptor } // Origin reports who or what produced the candidate. func (c Candidate) Origin() contract.Actor { return c.origin } // ID reports the candidate's revision id. func (c Candidate) ID() contract.RevisionID { return c.descriptor.ID } // Verified is a candidate that has passed every deterministic gate and been // signed. Only a Verified value may be published and routed. type Verified struct { descriptor contract.Revision origin contract.Actor report Report } // Descriptor returns the verified descriptor, signature included. func (v Verified) Descriptor() contract.Revision { return v.descriptor } // Origin reports who or what produced the underlying candidate. func (v Verified) Origin() contract.Actor { return v.origin } // Report returns the evidence that justified verification. func (v Verified) Report() Report { return v.report } // ID reports the revision id. func (v Verified) ID() contract.RevisionID { return v.descriptor.ID } // Stage names a step of the publication pipeline (Blueprint section 35). type Stage string const ( StageSource Stage = "SOURCE" StageBuild Stage = "BUILD" StageContractCheck Stage = "CONTRACT_CHECK" StageTest Stage = "TEST" StageSecurityCheck Stage = "SECURITY_CHECK" StagePolicyCheck Stage = "POLICY_CHECK" StageSign Stage = "SIGN" StagePublish Stage = "PUBLISH" ) // Stages is the pipeline in order. var Stages = []Stage{ StageSource, StageBuild, StageContractCheck, StageTest, StageSecurityCheck, StagePolicyCheck, StageSign, StagePublish, } // StageResult records what happened at one stage. type StageResult struct { Stage Stage `json:"stage"` Passed bool `json:"passed"` Detail string `json:"detail,omitempty"` Evidence []string `json:"evidence,omitempty"` } // Report is the accumulated evidence for a publication attempt. // // It is retained whether or not the attempt succeeded. Failed candidates are // normal (Blueprint invariant 14) and the record of why one was rejected is // exactly the evidence a later hypothesis needs. type Report struct { Revision contract.RevisionID `json:"revision"` Stages []StageResult `json:"stages"` } // Passed reports whether every recorded stage passed. func (r Report) Passed() bool { for _, s := range r.Stages { if !s.Passed { return false } } return len(r.Stages) > 0 } // FirstFailure returns the stage that stopped the pipeline. func (r Report) FirstFailure() (StageResult, bool) { for _, s := range r.Stages { if !s.Passed { return s, true } } return StageResult{}, false } // ErrRejected reports a candidate that failed a gate. type ErrRejected struct { Revision contract.RevisionID Failure StageResult } func (e *ErrRejected) Error() string { return fmt.Sprintf("revision %s rejected at %s: %s", e.Revision, e.Failure.Stage, e.Failure.Detail) } // ErrNotVerified reports an attempt to publish something unverified. var ErrNotVerified = errors.New("revision has not passed verification") // signedDescriptor attaches a signature to a descriptor. func signedDescriptor(d contract.Revision, sig signing.Signature) contract.Revision { out := d out.Signature = &contract.RevisionSignature{ Algorithm: contract.RevisionSignatureAlgorithm(sig.Algorithm), KeyID: sig.KeyID, Value: sig.Value, } return out }