Forgeyard Change Proposal, Review & Integration System Architecture
Document type: Core Forgeyard System & Architecture
Project: Forgeyard CI/CD
Subsystem: VCS-neutral change proposal, review, approval, policy, checks, mergeability, integration queue, submit/merge, and external forge synchronization
Implementation direction: Pure Rust Forgeyard core, Dioxus UI, Axum APIs, Postcard internal protocol, RON policy/config, VCS-neutral source/snapshot model, PostgreSQL/Neon distributed metadata, Stoolap standalone metadata
Status: Target production architecture
Position in Forgeyard: This subsystem sits directly above the VCS-neutral source layer and directly below/alongside Pipeline IR, policy, scheduler, checks, release, and deployment systems.
1. Purpose
Forgeyard needs a first-class change-review system because source changes are the control point connecting:
source
review
CI
policy
security
reproducibility
integration
release
A conventional Pull Request implementation is too Git/provider-specific.
Forgeyard therefore uses the neutral domain concept:
ChangeProposal
which can represent:
GitHub Pull Request
GitLab Merge Request
Forgejo/Gitea Pull Request
Mercurial review proposal
Jujutsu logical change proposal
Pijul/Darcs change proposal
Forgeyard-native proposal
The system must not assume:
branch
commit
merge
are universal concepts.
Instead, it builds around:
proposal source state
proposal target state
canonical SourceSnapshotId
review evidence
check evidence
policy evidence
integration candidate
submit result
2. Central Architectural Rule
A Forgeyard approval, check result, mergeability decision, and integration decision is always bound to an exact immutable source snapshot or integration snapshot—not merely to a mutable branch/ref/proposal number.
Therefore:
proposal update
↓
new SourceSnapshotId
↓
previous evidence evaluated
↓
retain / invalidate / partially invalidate
↓
new checks/review if required
3. Core Design Goals
Forgeyard Change Proposal MUST:
- remain VCS-neutral;
- support external and Forgeyard-native proposals;
- bind review/check evidence to immutable snapshots;
- support base/head revisions and snapshots;
- support logical change identities;
- support patch-centric VCS backends;
- support draft/open/closed lifecycle;
- separate lifecycle/review/check/policy/integration states;
- support inline review comments;
- support threaded discussions;
- support suggestions;
- support approvals;
- support changes-requested reviews;
- support CODEOWNERS-like ownership policy;
- support path/domain scoped approvals;
- support required checks;
- support custom checks;
- support policy gates;
- support security findings;
- support dependency/SBOM diffs;
- support performance/size/coverage diffs;
- support mergeability evaluation;
- support integration candidates;
- support merge/rebase/squash/backend-native submission;
- support merge/submit queue;
- support speculative integration;
- support queue batching;
- support stale-base handling;
- support conflict detection;
- support exact tested-integration submission;
- support external provider synchronization;
- support webhook idempotency;
- support concurrency/race protection;
- support auditability;
- support enterprise approval policy;
- support multi-tenancy;
- support notifications/events;
- support CLI/API/UI;
- work in standalone mode;
- work in distributed mode.
4. Non-Goals
Forgeyard Change Proposal does not initially need to replace GitHub/GitLab/Forgejo review UIs. It can first import and synchronize those systems, then progressively enable Forgeyard-native review.
The system also does not implement universal VCS merge semantics itself. Integration semantics belong to the VCS backend.
5. Architectural Position
VCS / Source Layer
│
▼
Change Proposal
│
┌────────────┼────────────┐
▼ ▼ ▼
Review Checks Policy
│ │ │
└────────────┼────────────┘
▼
Mergeability
│
▼
Integration Candidate
│
▼
Integration Queue
│
▼
Submit / Merge
│
▼
Resulting Revision
│
▼
Release Pipeline
6. Suggested Workspace
crates/
├── change/
│ ├── forgeyard-change/
│ ├── forgeyard-change-model/
│ ├── forgeyard-change-store/
│ ├── forgeyard-change-service/
│ ├── forgeyard-change-review/
│ ├── forgeyard-change-comment/
│ ├── forgeyard-change-approval/
│ ├── forgeyard-change-ownership/
│ ├── forgeyard-change-check/
│ ├── forgeyard-change-policy/
│ ├── forgeyard-change-mergeability/
│ ├── forgeyard-change-integration/
│ ├── forgeyard-change-queue/
│ ├── forgeyard-change-provider/
│ ├── forgeyard-change-events/
│ ├── forgeyard-change-notification/
│ └── forgeyard-change-audit/
Physical crate count can later be consolidated.
7. Dependency Direction
forgeyard-core
↑
forgeyard-vcs-model
↑
forgeyard-change-model
↑
change services
↑
provider adapters / API / UI
Core must never know GitHub PR numbers, GitLab IIDs, or Git merge refs as primary domain identity.
8. ChangeProposalId
#![allow(unused)]
fn main() {
pub struct ChangeProposalId(Ulid);
}
Do not encode repository + provider PR number into the internal ID. Provider IDs are aliases.
9. Change Proposal Model
#![allow(unused)]
fn main() {
pub struct ChangeProposal {
pub id: ChangeProposalId,
pub repository: RepositoryId,
pub source: ProposalSource,
pub target: ProposalTarget,
pub title: ProposalTitle,
pub description: ProposalDescription,
pub author: PrincipalId,
pub lifecycle: ProposalLifecycle,
pub current_revision: ProposalRevisionId,
pub created_at: Timestamp,
pub updated_at: Timestamp,
pub provider_binding: Option<ProviderProposalBinding>,
}
}
10. Proposal Revision
A proposal itself evolves. Each meaningful update creates an immutable revision:
#![allow(unused)]
fn main() {
pub struct ProposalRevision {
pub id: ProposalRevisionId,
pub proposal: ChangeProposalId,
pub source_revision: ResolvedRevision,
pub source_snapshot: SourceSnapshotId,
pub target_revision: ResolvedRevision,
pub target_snapshot: SourceSnapshotId,
pub change_set: ChangeSetId,
pub created_at: Timestamp,
}
}
This preserves:
Proposal #42
rev1 -> Snapshot A
rev2 -> Snapshot B
rev3 -> Snapshot C
Approvals and checks can bind to exact proposal revisions and snapshots.
11. Proposal Source and Target
#![allow(unused)]
fn main() {
pub struct ProposalSource {
pub selector: RevisionSpec,
pub resolved: Option<ResolvedRevision>,
}
pub struct ProposalTarget {
pub selector: RevisionSpec,
pub resolved: Option<ResolvedRevision>,
}
}
These can represent Git branches, Mercurial bookmarks, Jujutsu ChangeIds, Pijul channels/changes, exact revisions, or provider-supplied source states.
12. Lifecycle and Composite Status
#![allow(unused)]
fn main() {
pub enum ProposalLifecycle {
Draft,
Open,
Closed,
Integrated,
Superseded,
Abandoned,
}
}
Lifecycle is separate from review/check/policy/integration state.
#![allow(unused)]
fn main() {
pub struct ChangeProposalStatus {
pub lifecycle: ProposalLifecycle,
pub review: ReviewStatus,
pub checks: CheckAggregateStatus,
pub policy: PolicyStatus,
pub mergeability: MergeabilityStatus,
pub integration: IntegrationStatus,
}
}
This avoids giant impossible state enums.
13. Review State
#![allow(unused)]
fn main() {
pub enum ReviewStatus {
NotRequired,
AwaitingReview,
ChangesRequested,
Approved,
PartiallyApproved,
Stale,
}
}
14. Checks State
#![allow(unused)]
fn main() {
pub enum CheckAggregateStatus {
NotConfigured,
Pending,
Running,
Passed,
Failed,
Cancelled,
Stale,
}
}
15. Policy State
#![allow(unused)]
fn main() {
pub enum PolicyStatus {
NotEvaluated,
Passing,
Failing,
RequiresException,
ExceptionGranted,
}
}
16. Mergeability State
#![allow(unused)]
fn main() {
pub enum MergeabilityStatus {
Unknown,
Evaluating,
Mergeable,
Conflicted,
StaleBase,
PolicyBlocked,
ReviewBlocked,
ChecksBlocked,
BackendBlocked,
}
}
17. Integration State
#![allow(unused)]
fn main() {
pub enum IntegrationStatus {
NotRequested,
CandidatePending,
CandidateReady,
Queued,
Testing,
ReadyToSubmit,
Submitting,
Submitted,
Failed,
Superseded,
}
}
18. Review Model
#![allow(unused)]
fn main() {
pub struct Review {
pub id: ReviewId,
pub proposal: ChangeProposalId,
pub proposal_revision: ProposalRevisionId,
pub reviewer: PrincipalId,
pub verdict: ReviewVerdict,
pub body: Option<String>,
pub scope: ReviewScope,
pub created_at: Timestamp,
}
pub enum ReviewVerdict {
Comment,
Approve,
RequestChanges,
}
}
19. Review Scope
#![allow(unused)]
fn main() {
pub enum ReviewScope {
WholeProposal,
Paths(PathScope),
Domains(DomainScope),
Policy(PolicyScope),
}
}
This supports path-aware and domain-aware approvals.
20. Approval Binding
#![allow(unused)]
fn main() {
pub struct Approval {
pub id: ApprovalId,
pub proposal: ChangeProposalId,
pub proposal_revision: ProposalRevisionId,
pub snapshot: SourceSnapshotId,
pub reviewer: PrincipalId,
pub scope: ApprovalScope,
pub created_at: Timestamp,
}
}
Approval authority is snapshot-bound.
21. Approval Invalidation
#![allow(unused)]
fn main() {
pub enum ApprovalInvalidationPolicy {
AlwaysInvalidate,
InvalidateIfScopedPathsChanged,
PreserveIfOnlyNonOwnedPathsChanged,
PreserveManual,
}
}
Example:
reviewer approved crates/security/**
new update changes only docs/**
Policy may preserve security approval.
22. Discussion Threads
#![allow(unused)]
fn main() {
pub struct DiscussionThread {
pub id: DiscussionThreadId,
pub proposal: ChangeProposalId,
pub proposal_revision: ProposalRevisionId,
pub anchor: DiscussionAnchor,
pub state: ThreadState,
}
}
#![allow(unused)]
fn main() {
pub enum DiscussionAnchor {
Proposal,
File { path: CanonicalRepoPath },
Line {
path: CanonicalRepoPath,
side: DiffSide,
line: u32,
},
Symbol {
path: CanonicalRepoPath,
symbol: SymbolAnchor,
},
}
}
23. Thread State
#![allow(unused)]
fn main() {
pub enum ThreadState {
Open,
Resolved,
Outdated,
}
}
Line numbers are unstable. Persist path, old/new blob IDs, line context hash, optional symbol anchor, and proposal revision so UI can attempt safe relocation.
If an anchor cannot map to the latest revision, mark it Outdated; never silently move it to unrelated code.
24. Comments and Suggestions
#![allow(unused)]
fn main() {
pub struct ReviewComment {
pub id: CommentId,
pub thread: DiscussionThreadId,
pub author: PrincipalId,
pub body: String,
pub created_at: Timestamp,
pub edited_at: Option<Timestamp>,
}
}
#![allow(unused)]
fn main() {
pub struct CodeSuggestion {
pub thread: DiscussionThreadId,
pub base_snapshot: SourceSnapshotId,
pub path: CanonicalRepoPath,
pub range: SourceRange,
pub replacement: Vec<u8>,
}
}
Applying a suggestion creates a new source state, ProposalRevision, and SourceSnapshotId.
25. Ownership System
Forgeyard needs a provider-neutral CODEOWNERS-like model.
#![allow(unused)]
fn main() {
pub struct OwnershipRule {
pub pattern: PathPattern,
pub owners: Vec<OwnerRef>,
pub requirements: OwnershipRequirement,
}
}
#![allow(unused)]
fn main() {
pub enum OwnerRef {
User(PrincipalId),
Team(TeamId),
Role(RoleId),
Dynamic(DynamicOwnerRule),
}
}
#![allow(unused)]
fn main() {
pub struct OwnershipRequirement {
pub approvals_required: u16,
pub require_distinct_owners: bool,
pub dismissal_policy: ApprovalInvalidationPolicy,
}
}
26. Ownership Sources
Forgeyard can import:
CODEOWNERS
GitLab CODEOWNERS
Forgeyard RON ownership rules
organization ownership directory
into one internal model.
Ownership resolution:
changed paths
↓
ownership matcher
↓
required owner groups
↓
approval requirements
27. Domain Ownership
Beyond path ownership, Forgeyard can define domains:
security
billing
authentication
release
platform/apple
platform/windows
schema
migration
Domain ownership may require independent approval even when file ownership overlaps.
28. CheckRun
#![allow(unused)]
fn main() {
pub struct CheckRun {
pub id: CheckRunId,
pub proposal: ChangeProposalId,
pub proposal_revision: ProposalRevisionId,
pub snapshot: SourceSnapshotId,
pub kind: CheckKind,
pub state: CheckState,
pub run: Option<RunId>,
pub evidence: Vec<EvidenceRef>,
pub required: bool,
pub started_at: Option<Timestamp>,
pub completed_at: Option<Timestamp>,
}
}
29. Check Kinds
#![allow(unused)]
fn main() {
pub enum CheckKind {
Build,
UnitTest,
IntegrationTest,
EndToEndTest,
Lint,
Format,
StaticAnalysis,
Security,
DependencyPolicy,
LicensePolicy,
Coverage,
Benchmark,
BinarySize,
Reproducibility,
Packaging,
IntegrationCandidate,
Custom(CheckKindId),
}
}
30. Check States
#![allow(unused)]
fn main() {
pub enum CheckState {
Queued,
Running,
Passed,
Failed,
Cancelled,
TimedOut,
Skipped,
Stale,
}
}
31. Check Evidence
#![allow(unused)]
fn main() {
pub enum EvidenceRef {
Artifact(StoreObjectId),
TestReport(TestReportId),
SecurityReport(SecurityReportId),
Sbom(SbomId),
Provenance(ProvenanceId),
Benchmark(BenchmarkReportId),
Coverage(CoverageReportId),
Reproduction(ReproductionEvidenceId),
}
}
32. Required Checks
#![allow(unused)]
fn main() {
pub struct RequiredCheckRule {
pub condition: ProposalCondition,
pub checks: Vec<CheckRequirement>,
}
}
Examples:
all proposals -> build + tests
security/** changed -> security scan
release/** changed -> reproducibility
native/** changed -> ABI/linkage checks
33. Conditional Check Planning
changed paths
+
ecosystem graph
+
policy
↓
required check plan
Reuse previous evidence only when relevant SourceSnapshotId, derivation/check identity, policy version, and evidence scope all match.
34. Stale Check Protection
When source changes, old checks become stale unless the scoped derivation is provably unchanged and policy explicitly allows evidence reuse.
A check completion must include proposal revision, snapshot, and attempt identity. A stale result can never mark the latest proposal green.
35. Policy Engine Integration
Change Proposal invokes Forgeyard policy for:
review requirements
ownership
required checks
dependency policies
security policies
license policies
integration strategy
queue requirements
release constraints
exceptions
#![allow(unused)]
fn main() {
pub struct ChangePolicyInput {
pub proposal: ChangeProposalId,
pub revision: ProposalRevisionId,
pub source_snapshot: SourceSnapshotId,
pub target_snapshot: SourceSnapshotId,
pub changes: ChangeSetId,
pub author: PrincipalId,
pub labels: BTreeSet<Label>,
pub repository: RepositoryId,
}
}
36. Policy Decision
#![allow(unused)]
fn main() {
pub struct ChangePolicyDecision {
pub allowed: bool,
pub requirements: Vec<PolicyRequirement>,
pub violations: Vec<PolicyViolation>,
pub decision_digest: Digest,
}
}
Every decision records policy bundle/version digest for audit.
37. Policy Exceptions
#![allow(unused)]
fn main() {
pub struct PolicyException {
pub id: PolicyExceptionId,
pub violation: PolicyViolationId,
pub granted_by: PrincipalId,
pub reason: String,
pub expires_at: Option<Timestamp>,
}
}
Do not mutate a failing policy into a passing policy. Preserve the violation plus explicit exception evidence.
38. Semantic Change Evidence
The proposal view can aggregate ecosystem-specific evidence:
dependency diff
SBOM diff
vulnerability diff
license diff
coverage diff
benchmark diff
binary/package-size diff
API/ABI diff
schema/migration diff
protocol compatibility diff
These are evidence sources, not review authority by themselves.
39. Dependency Diff
Examples:
+ crate/package
- package
version upgrade
source change
new build script
new proc macro
new native dependency
new lifecycle script
new annotation processor
40. SBOM / Vulnerability / License Diff
Prefer deltas:
new components
removed components
new findings
resolved findings
new licenses
removed licenses
policy-impacting changes
rather than only totals.
41. Performance and Size Diff
Benchmark evidence includes runner class and confidence/noise metadata.
Binary/package size can compare:
binary size
package size
section size
asset size
by target.
42. Mergeability Engine
Mergeability is derived, never manually assigned.
#![allow(unused)]
fn main() {
pub struct MergeabilityDecision {
pub status: MergeabilityStatus,
pub reasons: Vec<MergeBlockReason>,
pub evaluated_revision: ProposalRevisionId,
pub target_revision: RevisionKey,
pub policy_digest: Digest,
}
}
43. Merge Block Reasons
#![allow(unused)]
fn main() {
pub enum MergeBlockReason {
Draft,
ReviewRequired,
ChangesRequested,
ApprovalMissing,
RequiredCheckPending,
RequiredCheckFailed,
PolicyViolation,
Conflict,
BaseOutdated,
QueueRequired,
ProviderRestriction,
BackendRestriction,
}
}
44. Integration Strategy
#![allow(unused)]
fn main() {
pub enum IntegrationStrategy {
FastForward,
Merge,
Rebase,
Squash,
BackendNative(BackendIntegrationKind),
}
}
The VCS backend owns execution semantics.
45. VCS Integration Backend
#![allow(unused)]
fn main() {
#[async_trait]
pub trait VcsIntegrationBackend {
async fn prepare_candidate(
&self,
request: &IntegrationRequest,
) -> Result<IntegrationCandidate, IntegrationError>;
async fn submit_candidate(
&self,
candidate: &IntegrationCandidate,
expected_target: &ResolvedRevision,
) -> Result<IntegrationResult, IntegrationError>;
}
}
46. Integration Candidate
#![allow(unused)]
fn main() {
pub struct IntegrationCandidate {
pub id: IntegrationCandidateId,
pub proposal: ChangeProposalId,
pub proposal_revision: ProposalRevisionId,
pub base_revision: ResolvedRevision,
pub source_revision: ResolvedRevision,
pub strategy: IntegrationStrategy,
pub result_snapshot: SourceSnapshotId,
pub native_candidate: NativeIntegrationCandidate,
pub created_at: Timestamp,
}
}
Candidates are immutable. Changed source/base produces a new candidate.
47. Candidate CI
The critical safety pattern is:
proposal source passes CI
↓
create integration candidate
↓
candidate snapshot passes CI
↓
submit same candidate state
This prevents a proposal from passing against an old base and then entering a changed target untested.
48. Integration Snapshot Identity
Reuse SourceSnapshotId for candidate content. The semantic meaning is carried by candidate metadata, not by inventing a second tree digest scheme.
49. Submit Precondition
Before submit:
current target revision == candidate expected base revision
unless queue/backend policy explicitly reprepares the candidate.
Conceptually submission behaves like:
CAS(target_ref, expected_base, candidate_result)
where the VCS can provide equivalent semantics.
50. Base Drift
If target moves:
candidate -> Superseded
then Forgeyard reprepares and retests according to queue policy.
51. Integration Queue
#![allow(unused)]
fn main() {
pub struct IntegrationQueue {
pub id: IntegrationQueueId,
pub repository: RepositoryId,
pub target: RevisionSpec,
pub policy: QueuePolicy,
}
}
#![allow(unused)]
fn main() {
pub struct QueueEntry {
pub id: QueueEntryId,
pub proposal: ChangeProposalId,
pub proposal_revision: ProposalRevisionId,
pub priority: QueuePriority,
pub enqueued_at: Timestamp,
pub state: QueueEntryState,
}
}
52. Queue State
#![allow(unused)]
fn main() {
pub enum QueueEntryState {
Waiting,
Preparing,
Testing,
Ready,
Submitting,
Submitted,
Failed,
Superseded,
Removed,
}
}
53. Queue Policy
#![allow(unused)]
fn main() {
pub struct QueuePolicy {
pub mode: QueueMode,
pub batching: BatchPolicy,
pub stale_base: StaleBasePolicy,
pub failure: QueueFailurePolicy,
pub max_parallel_candidates: u16,
}
}
#![allow(unused)]
fn main() {
pub enum QueueMode {
Serial,
Speculative,
Batched,
}
}
54. Serial Queue
main
↓
A candidate/test/submit
↓
main+A
↓
B candidate/test/submit
This is the safest initial production mode.
55. Speculative Queue
Can prepare predicted chains:
A on main
B on predicted main+A
C on predicted main+A+B
If A fails, dependent predictions are invalidated.
56. Batched Queue
Combine A+B+C into one candidate for throughput. If the batch fails, adaptive bisect policy can identify the failing proposal(s).
#![allow(unused)]
fn main() {
pub enum BatchPolicy {
Disabled,
MaxSize(u16),
Adaptive {
max_size: u16,
failure_bisect: bool,
},
}
}
57. Queue Priority and Fairness
#![allow(unused)]
fn main() {
pub enum QueuePriority {
Normal,
Elevated,
ReleaseCritical,
Emergency,
}
}
Priority changes require permission and audit.
Fairness can combine FIFO, priority aging, repository fairness, and tenant fairness.
58. Queue Failure Policy
#![allow(unused)]
fn main() {
pub enum QueueFailurePolicy {
RemoveFailed,
PauseQueue,
RetryTransient,
BisectBatch,
}
}
Distinguish deterministic failures from infrastructure/transient failures before retrying.
59. Queue Leases
#![allow(unused)]
fn main() {
pub struct IntegrationLease {
pub lease_id: LeaseId,
pub entry: QueueEntryId,
pub worker: WorkerId,
pub expires_at: Timestamp,
}
}
Leases prevent duplicate submission work.
60. Idempotency
State-changing commands accept idempotency keys:
create proposal
submit review
enqueue
prepare candidate
submit candidate
provider sync
61. External Provider Mode
External PR/MR
↓
provider adapter
↓
Forgeyard ChangeProposal
↓
checks/review evidence
↓
status/comment sync
External provider may remain the source of review UX initially.
62. Forgeyard-Native Mode
VCS repository
↓
Forgeyard ChangeProposal
↓
Forgeyard reviews/checks/integration
↓
VCS submit
63. Hybrid Mode
Possible configurations:
external proposal + Forgeyard-owned policy/queue
Forgeyard-native proposal + external issue tracker
provider-owned review + Forgeyard submit authority
64. Provider Binding
#![allow(unused)]
fn main() {
pub struct ProviderProposalBinding {
pub provider: ProviderId,
pub external_repository: ExternalRepositoryId,
pub external_proposal: ExternalProposalId,
pub sync_mode: ProviderSyncMode,
}
pub enum ProviderSyncMode {
ImportOnly,
Bidirectional,
StatusOnly,
ForgeyardAuthoritative,
}
}
65. Provider Adapter
#![allow(unused)]
fn main() {
#[async_trait]
pub trait ChangeProviderAdapter {
async fn import_proposal(
&self,
event: ProviderProposalEvent,
) -> Result<ProviderProposalSnapshot>;
async fn publish_check(
&self,
update: ProviderCheckUpdate,
) -> Result<()>;
async fn publish_comment(
&self,
comment: ProviderCommentUpdate,
) -> Result<()>;
async fn publish_review(
&self,
review: ProviderReviewUpdate,
) -> Result<()>;
}
}
Provider is separate from VCS: GitHub/GitLab/Forgejo are providers; Git is the VCS.
66. Provider Webhook Handling
receive
↓
verify signature
↓
dedupe delivery ID
↓
normalize event
↓
resolve exact source state
↓
append proposal revision
↓
schedule reevaluation
67. Change Events
#![allow(unused)]
fn main() {
pub enum ChangeEvent {
ProposalCreated,
ProposalUpdated,
ProposalClosed,
ProposalReopened,
ReviewSubmitted,
ThreadCreated,
ThreadResolved,
CheckUpdated,
PolicyEvaluated,
CandidateCreated,
QueueEntered,
QueueRemoved,
IntegrationSubmitted,
}
}
68. Event Envelope
#![allow(unused)]
fn main() {
pub struct EventEnvelope<T> {
pub schema_version: u16,
pub event_id: EventId,
pub occurred_at: Timestamp,
pub actor: ActorRef,
pub payload: T,
}
}
Use append-only audit/event history even when current state is relational.
69. Internal Protocol and Formats
Use:
Postcard -> internal daemon/service messages
RON -> human policy/configuration
JSON -> public/provider APIs where required
70. Storage Model
Distributed mode:
PostgreSQL / Neon
Standalone mode:
Stoolap
Both behind the same store trait.
71. ChangeStore
#![allow(unused)]
fn main() {
#[async_trait]
pub trait ChangeStore {
async fn create_proposal(...);
async fn append_revision(...);
async fn put_review(...);
async fn put_comment(...);
async fn put_check(...);
async fn put_policy_decision(...);
async fn put_candidate(...);
async fn update_queue_entry(...);
}
}
72. Suggested Tables
change_proposals
change_proposal_revisions
change_reviews
change_approvals
change_threads
change_comments
change_suggestions
change_ownership_requirements
change_checks
change_policy_decisions
change_policy_exceptions
integration_candidates
integration_queues
integration_queue_entries
provider_proposal_bindings
provider_sync_state
change_events
change_audit
Bulk artifacts remain in CAS.
73. Concurrency Control
Use optimistic entity versioning for mutable metadata:
#![allow(unused)]
fn main() {
pub struct EntityVersion(u64);
}
Updates require expected version to prevent lost writes.
Proposal revisions themselves are append-only.
74. Review Race Protection
Review submission includes expected proposal revision. If source changed during review, record the review against the old revision and mark it stale according to policy. Never silently attach it to the latest revision.
75. Check Race Protection
Check completion includes proposal revision, source snapshot, and run attempt. Stale results cannot alter the latest proposal status.
76. Integration Race Protection
Candidate submit includes expected target revision and result snapshot. Stale target blocks submission.
77. Notifications
Events may trigger:
review requested
changes requested
approval obtained
check failed
queue entered
queue blocked
proposal integrated
Notification channels are adapters.
Deduplicate by event ID + recipient + notification kind.
78. Review Assignment
Reviewer assignment sources:
manual request
ownership rules
domain rules
load balancing
round-robin
expertise mapping
Eligibility checks permissions, self-approval policy, team membership, conflict policy, and required role.
79. Separation of Duties
Enterprise policy may require:
author != approver
approver != submitter
security approver distinct from code owner
#![allow(unused)]
fn main() {
pub struct ApprovalQuorum {
pub minimum: u16,
pub distinct_teams: Option<u16>,
pub required_roles: Vec<RoleId>,
}
}
80. Labels and Risk
#![allow(unused)]
fn main() {
pub struct Label {
pub key: LabelKey,
pub value: Option<LabelValue>,
}
pub enum ChangeRisk {
Low,
Medium,
High,
Critical,
}
}
Risk can derive from paths, dependency changes, sensitive modules, migration/schema changes, binary changes, and release files.
81. High-Risk Policy Example
risk:critical
requires:
2 owners
security approval
full integration tests
reproducibility
integration queue
82. Breaking Change Evidence
Ecosystem adapters can contribute:
public API diff
schema diff
protocol diff
ABI diff
as check evidence.
83. Migration / Protocol Review
If migration files change, require migration validation and rollback/compatibility evidence.
If Postcard/API schema changes, require rolling-upgrade/protocol compatibility checks.
84. Security-Sensitive Paths
Examples:
auth/**
crypto/**
policy/**
secrets/**
sandbox/**
signing/**
can require stronger approvals and checks.
85. Secret Detection
Secret scan can produce blocking check evidence before integration.
86. Generated File Awareness
If generated files changed, ecosystem adapters can verify their generator inputs also changed or regenerate-and-compare.
87. Docs-Only Optimization
Policy may reduce checks only after VCS-neutral snapshot diff proves the proposal is truly docs-only.
88. Reproducibility Evidence
High-assurance proposal can attach:
primary artifact digest
independent rebuild digest
comparison state
Release-critical integration candidates may require their own reproduction evidence.
89. Artifact Previews
Proposal can expose preview artifacts:
web preview
desktop package
APK
docs
benchmark report
SBOM
with retention policy.
Preview deployments use ephemeral environments and no production secrets by default.
90. Integration Candidate Provenance
#![allow(unused)]
fn main() {
pub struct IntegrationProvenance {
pub proposal_revision: ProposalRevisionId,
pub base_revision: RevisionKey,
pub source_revision: RevisionKey,
pub strategy: IntegrationStrategy,
pub result_snapshot: SourceSnapshotId,
pub backend_version: ToolIdentity,
}
}
91. Integration Result
#![allow(unused)]
fn main() {
pub struct IntegrationResult {
pub resulting_revision: ResolvedRevision,
pub resulting_snapshot: SourceSnapshotId,
pub submitted_at: Timestamp,
pub actor: PrincipalId,
}
}
After submit:
materialize resulting revision
↓
canonicalize
↓
must equal candidate SourceSnapshotId
If not, raise IntegrationContentViolation.
92. Backend Hook Effects
If VCS/provider hooks alter resulting source state, Forgeyard detects snapshot mismatch. It must never claim that the tested candidate was integrated if final source differs.
Commit metadata may differ while tree content stays equal; validate commit/signature metadata separately if policy requires it.
93. Protected Targets
Target protection can require:
queue only
minimum approvals
signed integration
no direct writes
specific submitters
94. Direct Write Detection
If protected target changes outside Forgeyard queue:
audit event
policy alert
optional target freeze
according to repository policy and provider/VCS integration.
95. Emergency Bypass
Break-glass integration may exist with:
special permission
mandatory reason
strong auth/MFA where available
immutable audit
post-submit checks
Never silently bypass requirements.
96. Proposal Dependencies and Stacks
#![allow(unused)]
fn main() {
pub struct ProposalDependency {
pub prerequisite: ChangeProposalId,
pub kind: ProposalDependencyKind,
}
pub enum ProposalDependencyKind {
MustIntegrateFirst,
StackParent,
Related,
}
}
Useful for Jujutsu stacks, Git stacked branches, Mercurial bookmark chains, and logical review dependencies.
97. Stack Update
When parent proposal changes, child source may change. Append a new ProposalRevision and invalidate/reuse evidence according to snapshot impact.
98. Supersession and Duplicate Detection
One proposal may supersede another without destroying history.
Optional duplicate heuristics may compare same target + same SourceSnapshotId or highly similar ChangeSet, but must never auto-close solely on heuristic confidence.
99. Search
Index:
title
description
author
labels
paths
reviewers
status
check failures
revision IDs
SourceSnapshotId
100. REST API
Potential endpoints:
POST /v1/change-proposals
GET /v1/change-proposals/{id}
PATCH /v1/change-proposals/{id}
GET /v1/change-proposals/{id}/revisions
GET /v1/change-proposals/{id}/diff
POST /v1/change-proposals/{id}/reviews
POST /v1/change-proposals/{id}/threads
POST /v1/change-proposals/{id}/comments
GET /v1/change-proposals/{id}/checks
GET /v1/change-proposals/{id}/policy
GET /v1/change-proposals/{id}/mergeability
POST /v1/change-proposals/{id}/integrate
POST /v1/change-proposals/{id}/queue
DELETE /v1/change-proposals/{id}/queue
101. CLI
forgeyard change create
forgeyard change show
forgeyard change list
forgeyard change diff
forgeyard change update
forgeyard change close
forgeyard change review
forgeyard change approve
forgeyard change request-changes
forgeyard change comment
forgeyard change resolve-thread
forgeyard change checks
forgeyard change policy
forgeyard change explain-block
forgeyard change candidate
forgeyard change queue
forgeyard change unqueue
forgeyard change integrate
102. forgeyard change show
Display:
proposal
author
target
source
current snapshot
changed paths
review state
check state
policy state
mergeability
queue state
103. forgeyard change explain-block
Explain exact blocking reasons such as:
missing security owner approval
integration test pending
base revision stale
unresolved review thread
104. Dioxus UI
Primary screen:
Header
├── title
├── author
├── lifecycle
├── source -> target
├── risk
└── mergeability
Tabs
├── Overview
├── Changes
├── Checks
├── Review
├── Dependencies
├── Security
├── Performance
├── Artifacts
├── Integration
└── Activity
105. Overview Panel
Show:
files changed
packages/crates affected
dependency changes
required reviewers
approval count
required checks
policy violations
integration status
106. Changes View
Support:
unified diff
split diff
file tree
symbol navigation
whitespace toggle
generated-file marker
binary-file summary
107. Review UI
Support inline comments, threads, resolve/reopen, suggestions, review summary, approve, and request changes.
108. Checks UI
Each check displays state, duration, runner, logs, artifacts, evidence, retry history, and stale/current marker.
109. Integration UI
Display strategy, candidate snapshot, base revision, candidate checks, queue position, and expected submit target.
110. Activity Timeline
Append-only timeline:
proposal opened
source updated
review submitted
approval invalidated
check failed/passed
policy exception
queue entry
candidate created
submitted
111. Mobile UI
Dioxus mobile view prioritizes status, required action, review comments, approvals, check failures, and queue state. Detailed diff uses file-by-file navigation.
112. Permissions
Suggested permissions:
change.create
change.edit
change.close
change.review
change.approve
change.manage_owners
change.override_policy
change.enqueue
change.submit
change.admin
Roles are convenience bundles; permissions are authoritative.
113. Multi-Tenancy
Every proposal/check/review belongs to tenant, organization, repository. Cross-tenant access is denied.
114. Comment Audit
Edits/deletes preserve audit history. User-facing deletion may show a tombstone while immutable audit records remain.
115. Rate Limits
Protect against comment spam, review spam, candidate churn, provider sync loops, and queue abuse.
116. Provider Sync Loop Prevention
Outbound provider updates carry correlation metadata where possible. Inbound echoes are deduplicated.
117. Observability
Metrics:
proposal_open_duration
review_time
time_to_first_review
check_latency
mergeability_evaluation_latency
queue_wait_time
candidate_failure_rate
integration_success_rate
approval_invalidation_count
provider_sync_failure
118. Tracing
OTLP spans:
change.create
change.resolve_revision
change.compute_diff
change.policy
change.check.plan
change.review
change.candidate.prepare
change.queue.evaluate
change.submit
119. Audit
Immutable audit records proposal creation/update, reviews/approvals, approval invalidation, policy exceptions, queue priority changes, candidate generation, submit/bypass, and provider sync.
120. Failure Model
#![allow(unused)]
fn main() {
pub enum ChangeFailure {
ProposalNotFound,
ProposalClosed,
ProposalRevisionStale,
PermissionDenied,
ReviewNotAllowed,
ApprovalStale,
CheckFailed,
PolicyBlocked,
Conflict,
BaseStale,
CandidateSuperseded,
QueueBlocked,
IntegrationFailed,
ProviderSyncFailed,
VcsBackendFailed,
IntegrityViolation,
}
}
121. Resilience and Reconciliation
Use reconcilers for provider sync, stuck checks, stale candidates, queue entries, lost runner attempts, and incomplete submit verification.
Do not depend on exactly-once messaging.
Use:
at-least-once events
+
idempotent transitions
+
reconciliation
122. Provider Reconciler
Periodically compare external proposal state vs Forgeyard binding state and repair missed webhook state safely.
123. Check Reconciler
If CheckRun says Running but underlying Run is terminal, repair state.
124. Queue Reconciler
If lease expires, return entry to evaluable state unless submission may have partially succeeded.
125. Submit Reconciler
After ambiguous submit:
query target
materialize result
compare candidate
before retrying. This prevents duplicate integration.
126. Conflict Model
#![allow(unused)]
fn main() {
pub struct IntegrationConflict {
pub paths: Vec<CanonicalRepoPath>,
pub native_evidence: NativeConflictEvidence,
}
}
Conflict resolution creates a new proposal/source revision; candidates are immutable.
127. External Review Import
When importing external reviews:
map reviewer
map verdict
bind external review to provider head revision
resolve corresponding SourceSnapshotId
If exact source mapping is not reliable, mark evidence weak/stale rather than overclaiming validity.
128. Provider Approval Semantics
External providers differ in dismissal rules, team reviews, codeowners, and review state. Forgeyard preserves external evidence but evaluates its own policy independently.
129. Check Trust
#![allow(unused)]
fn main() {
pub enum CheckTrust {
ForgeyardNative,
TrustedExternal,
Informational,
Untrusted,
}
}
External check statuses may be evidence without becoming automatically trusted merge authority.
130. Mergeability Cache
Cache key:
proposal revision
target revision
policy digest
review evidence digest
check evidence digest
Any relevant change invalidates decision.
131. Deterministic Decision
Given identical proposal revision, target revision, policy version, evidence, and explicit time inputs, mergeability should be deterministic.
132. Time-Dependent Policy
If policy depends on approval age, release window, or freeze state, include the relevant time/window identifier in the recorded decision.
133. Approval Expiry
Approvals may have explicit expiry timestamps. Do not implement hidden age-based invalidation without recording policy and expiry.
134. Freeze Windows
Policy can block integration during release/maintenance/incident freezes while allowing an explicit exception path.
135. Proposal Templates
Repository may define structured templates:
bugfix
feature
security
breaking change
migration
136. Structured Proposal Metadata
#![allow(unused)]
fn main() {
pub struct ProposalMetadata {
pub issue_refs: Vec<ExternalIssueRef>,
pub risk: Option<ChangeRisk>,
pub rollout: Option<RolloutPlanRef>,
pub migration: Option<MigrationPlanRef>,
}
}
Issue tracker integration remains optional.
137. Release Notes
Policy may require a release-note fragment for selected changes. Release-note aggregation should consume structured proposal metadata rather than only commit-message heuristics.
138. AI Assistance Boundary
AI may optionally summarize diffs, identify likely reviewers, explain failed checks, and summarize discussion. AI is never equivalent to required human approval and never bypasses policy.
#![allow(unused)]
fn main() {
pub enum ReviewerKind {
Human,
AutomatedTool,
AiAssistant,
}
}
139. Security Invariant
Proposal source is untrusted executable code even when author has repository access. CI runs in the normal untrusted workload sandbox.
140. Secret Access
Proposal jobs receive no production secrets by default. Use scoped test/preview credentials only when explicitly allowed.
Fork/external proposals additionally receive no trusted runners, signing keys, release credentials, or deployment credentials.
141. Proposal Trust
#![allow(unused)]
fn main() {
pub enum ProposalTrust {
InternalTrustedAuthor,
InternalUntrustedCode,
ExternalFork,
AutomatedDependencyUpdate,
}
}
Author trust does not make code trusted by default.
142. Bot Proposals and Auto-Integration
Machine principals may create proposals. Policy can require human review.
Auto-integration is allowed only under explicit conditions, for example:
low-risk dependency patch
all checks green
no new licenses
no new build scripts
no new native deps
ownership policy permits
Still use integration candidate + queue + target precondition.
143. Repository Protection Policy Example
(
target: Ref("main"),
direct_write: Denied,
approvals: (
minimum: 2,
),
ownership: Required,
checks: [
Build,
UnitTest,
IntegrationTest,
Security,
],
queue: Required,
integration: (
strategy: Squash,
),
)
144. Security Path Policy Example
(
when: PathsMatch(["crates/security/**", "crates/auth/**"]),
require: (
owners: ["team:security"],
approvals: 2,
checks: [Security, Reproducibility],
),
)
145. Native Toolchain Policy Example
(
when: PathsMatch(["crates/native/**", "crates/assembly/**"]),
require: (
checks: [
Build,
NativeAbi,
RuntimeLinkage,
Reproducibility,
],
),
)
146. Docs-Only Policy Example
(
when: OnlyPaths(["docs/**"]),
require: (
checks: [Docs],
approvals: 1,
),
)
Only apply after safe VCS-neutral snapshot diff.
147. Standalone Mode
local VCS repo
↓
Forgeyard local ChangeProposal
↓
Stoolap metadata
↓
local runner
↓
local review/check/integration
No central server required.
148. Distributed Mode
central daemon
↓
Postgres/Neon metadata
↓
distributed runners
↓
shared review/check state
↓
integration queue
149. Enterprise HA Mode
Adds OIDC/SAML/SCIM, RBAC, HA daemon, queue leadership, audit/SIEM, protected signing, multi-region CAS, and provider federation.
Queue coordination may use Forgeyard’s coordination layer, but proposal business data remains in Postgres/Neon rather than Raft.
150. Compatibility and Migrations
Use versioned envelopes for internal messages, N/N-1 rolling-upgrade compatibility where practical, and expand-contract database migrations so active reviews/queues survive deployment.
151. Retention
Proposal/review metadata is normally retained long-term. Large logs/artifacts follow artifact retention policy. Audit/compliance evidence may have longer retention.
152. Backup / Restore
Backup proposal/review/check metadata, provider bindings, queue state, and audit. CAS backup/replication handles artifacts separately.
After restore, reconcile provider state, queue state, and candidate target revisions before any submit.
153. Performance Strategy
Avoid recomputing everything on every UI request. Materialize current aggregate review/check/policy/mergeability state while preserving immutable evidence/event records.
Reuse snapshot diff cache across ownership, checks, UI, and change-impact analysis.
154. Large Proposal Handling
Use pagination, lazy per-file diff, binary summaries, streaming, and server-side diff chunks. Repositories may define max diff-size policy with exception mechanism.
155. Core Service Trait
#![allow(unused)]
fn main() {
#[async_trait]
pub trait ChangeProposalService {
async fn create(&self, cmd: CreateProposal) -> Result<ChangeProposal>;
async fn update_source(&self, cmd: UpdateProposalSource) -> Result<ProposalRevision>;
async fn review(&self, cmd: SubmitReview) -> Result<Review>;
async fn evaluate(&self, id: ChangeProposalId) -> Result<ChangeProposalStatus>;
async fn prepare_candidate(&self, cmd: PrepareCandidate) -> Result<IntegrationCandidate>;
async fn enqueue(&self, cmd: EnqueueProposal) -> Result<QueueEntry>;
}
}
156. Policy Evaluator Trait
#![allow(unused)]
fn main() {
#[async_trait]
pub trait ChangePolicyEvaluator {
async fn evaluate(
&self,
input: &ChangePolicyInput,
) -> Result<ChangePolicyDecision>;
}
}
157. Boundary Rules
Review services do not call SQL directly; they use store interfaces.
Provider-specific tokens/status semantics stay outside core domain crates.
Dioxus UI is a client, never mergeability authority.
Daemon/service owns policy evaluation, review validation, queue transitions, candidate creation, and submit authority.
158. Trust Boundary
Browser/UI
↓
API
↓
Change service
↓
Policy / VCS / Scheduler
↓
Runner
Proposal code never executes inside the daemon process.
159. Security Threat Model
Threats include:
stale approval replay
stale check replay
provider webhook spoofing
queue race
double submit
privilege escalation
review tampering
secret leakage
fork pipeline attack
candidate substitution
160. Security Protections
Approval lookup requires matching proposal revision + snapshot + scope + policy context.
Check pass never attaches by proposal ID alone.
Candidate digest/result snapshot binds queue/check/submit.
Double-submit protection uses lease + idempotency key + expected target + post-submit verification.
Provider webhook signatures are verified before normalization.
Audit events are append-only with integrity/retention policy.
161. Property Tests
Required properties:
approval never applies to unrelated snapshot
stale passed check never marks latest proposal green
candidate result equals tested snapshot
submit fails if target precondition violated
queue transition graph remains valid
162. Fuzzing
Fuzz provider event normalization, diff anchors, RON policy parsing, state transition commands, and suggestion patch parsing.
163. Integration Tests
Test:
Git + external provider
Git + Forgeyard-native review
Mercurial-native proposal
Jujutsu logical change
patch-centric VCS adapters
as those backends mature.
164. End-to-End Test
create proposal
update proposal
run checks
request changes
update source
invalidate approval
approve
create candidate
candidate test
queue
target drift
reprepare
submit
verify resulting snapshot
165. Failure Injection
Inject runner loss, database failover, provider timeout, VCS timeout, queue-worker crash, and ambiguous submit responses. Reconciliation must recover without double-submit or stale green state.
166. Self-Hosting Forgeyard
Forgeyard should eventually use its own Change Proposal system:
Forgeyard source change
↓
Forgeyard ChangeProposal
↓
Forgeyard Rust CI
↓
review + policy
↓
Forgeyard IntegrationCandidate
↓
Forgeyard queue
↓
submit exact tested candidate
167. Bootstrap Transition
Phase A:
external PR + Forgeyard checks
Phase B:
external PR + Forgeyard policy/queue
Phase C:
Forgeyard-native review optional
Phase D:
Forgeyard-native review/integration for Forgeyard itself
168. Implementation Phase 1 — Core Model
Implement ChangeProposalId, ProposalRevision, composite status, review/comment/approval types.
169. Phase 2 — Snapshot/Diff Integration
Implement base/head resolution, SourceSnapshotId binding, snapshot diff, and append-only proposal revisions.
170. Phase 3 — Checks
Implement CheckRun, Pipeline IR linkage, required checks, stale-evidence rules.
171. Phase 4 — Review
Implement threads, comments, review verdict, approval binding, invalidation.
172. Phase 5 — Ownership
Implement path rules, team/role ownership, CODEOWNERS import, required approvals.
173. Phase 6 — Policy
Implement policy decisions, risk classification, exceptions, and conditional requirements.
174. Phase 7 — Integration Candidate
Implement VCS integration backend trait, candidate snapshot, candidate CI, post-build evidence.
175. Phase 8 — Serial Queue
Implement serial queue, leases, compare-and-submit target precondition, retry/reconciliation.
176. Phase 9 — External Provider
Implement first provider adapter for proposal import, webhooks, status/check publishing, and comments while keeping provider types outside core.
177. Phase 10 — Advanced Queue
Implement speculative queue, batching, adaptive bisect, priorities.
178. Phase 11 — Native Review UI
Implement full Dioxus diff, threads, approvals, checks, candidate, and queue UI.
179. Phase 12 — Enterprise Hardening
Implement OIDC/RBAC, separation of duties, audit/SIEM, HA queue, break-glass, and multi-tenant protections.
180. Acceptance Tests
- Approval binds to exact snapshot.
- New source revision invalidates approval according to policy.
- Unchanged approval scope may remain valid when scoped policy permits.
- Stale check cannot mark new proposal revision green.
- Required failed check blocks mergeability.
- Missing owner approval blocks mergeability.
- Policy exception preserves violation + grant separately.
- Integration candidate has immutable result snapshot.
- Candidate CI runs against integration result, not only proposal head.
- Target movement supersedes stale candidate.
- Queue never submits against unexpected target revision.
- Post-submit snapshot equals candidate snapshot.
- Provider duplicate webhook is idempotent.
- Provider review maps to exact source snapshot or is marked weak/stale.
- Fork proposal receives no privileged secrets by default.
- Queue worker crash does not double-submit.
- Ambiguous submit is reconciled before retry.
- CODEOWNERS import maps to Forgeyard ownership model.
- Jujutsu change update creates new ProposalRevision without losing ChangeId.
- Patch-centric VCS can provide proposal source without fake Git commit semantics.
- Integration queue works in standalone mode.
- Same core model works with Postgres distributed mode.
- Forgeyard can process a proposal for its own codebase.
- Audit reconstructs all review/queue/submit decisions.
181. Production Readiness Gates
Do not call Change Proposal production-ready until:
snapshot-bound approvals are correct
stale check isolation is correct
ownership matching is deterministic
policy versioning works
candidate/result snapshot identity is stable
target compare-and-submit is safe
serial integration queue is reliable
post-submit verification works
reconciliation handles crashes
provider webhook verification/idempotency works
permissions/RBAC are enforced
audit is complete
Speculative/batched queue can remain optional after initial production readiness.
182. Architectural Invariants
- Change Proposal is VCS-neutral.
- Proposal number/provider ID is not internal identity.
- Proposal updates append immutable ProposalRevision records.
- Approval binds to ProposalRevision + SourceSnapshotId.
- Check result binds to ProposalRevision + SourceSnapshotId.
- Lifecycle/review/check/policy/integration states are separate.
- CODEOWNERS-like rules normalize into Forgeyard ownership.
- Policy decision records policy digest.
- Mergeability is derived.
- Mutable branch/ref names are never enough for approval/check authority.
- Integration semantics belong to VCS backend.
- Integration candidate is immutable.
- Candidate CI validates result snapshot.
- Queue submit uses expected-target precondition.
- Target drift invalidates/supersedes candidate.
- Post-submit result is snapshot-verified.
- Provider and VCS are separate abstractions.
- External provider approval does not automatically equal Forgeyard approval.
- Stale evidence never silently applies to latest source.
- Required/optional checks remain distinct.
- Policy exceptions do not erase policy violations.
- Break-glass bypass is explicit/audited.
- Proposal code runs as untrusted workload.
- Fork proposals do not receive privileged secrets by default.
- Queue transitions are lease/idempotency protected.
- Reconciliation handles at-least-once events.
- UI is never policy authority.
- Postgres/Stoolap are hidden behind store interface.
- CAS stores evidence/artifacts; SQL stores metadata/state.
- Forgeyard should eventually dogfood this subsystem on Forgeyard itself.
183. Final Target Architecture
VCS-Neutral Source Layer
│
▼
ChangeProposal
│
▼
ProposalRevision
├── source snapshot
├── target snapshot
└── change set
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Review Checks Policy
│ │ │
└──────────────┼──────────────┘
▼
Mergeability
│
▼
IntegrationCandidate
├── exact base
├── exact source
├── strategy
└── result snapshot
│
▼
Candidate CI
│
▼
Integration Queue
│
▼
compare-and-submit target
│
▼
Resulting Revision
│
▼
Snapshot Verification
│
▼
Integrated Proposal
184. Final Architectural Position
Proposal identity:
Repository
+
Source selection
+
Target selection
+
Author/metadata
=
ChangeProposal
Immutable proposal state:
resolved source revision
+
source SourceSnapshotId
+
resolved target revision
+
target SourceSnapshotId
+
ChangeSet
=
ProposalRevision
Mergeability:
ProposalRevision
+
Review evidence
+
Approval evidence
+
Check evidence
+
Policy decision
+
Current target revision
=
MergeabilityDecision
Integration:
ProposalRevision
+
Exact target revision
+
VCS-native integration strategy
=
IntegrationCandidate
+
Result SourceSnapshotId
Submission:
Candidate passed CI
+
Target still equals expected base
+
Queue/policy permits submit
↓
submit candidate
↓
resulting VCS revision
↓
canonical snapshot
↓
must equal tested candidate snapshot
Forgeyard does not merely merge code that passed CI. Forgeyard integrates the exact source state that passed the required review, policy, and candidate checks.
This turns Change Proposal into a trustworthy control plane between source control and production delivery rather than a thin wrapper around provider-specific Pull Requests.
Appendix A — Example Change Proposal Policy
(
target: Ref("main"),
lifecycle: (
draft_merge: Denied,
),
review: (
approvals: 2,
dismiss_on_source_change: Scoped,
unresolved_threads: Denied,
),
ownership: Required,
checks: (
required: [
Build,
UnitTest,
IntegrationTest,
Security,
],
),
policy: (
exceptions: MaintainersOnly,
),
integration: (
strategy: Squash,
queue: Required,
candidate_ci: Required,
),
)
Appendix B — Example Approval Record
(
proposal: "cp:01...",
revision: "cpr:01...",
snapshot: "blake3:...",
reviewer: "principal:alice",
scope: Paths([
"crates/security/**",
]),
verdict: Approve,
)
Appendix C — Example Integration Candidate
(
proposal: "cp:01...",
proposal_revision: "cpr:01...",
base_revision: "vcsrev:...",
source_revision: "vcsrev:...",
strategy: Squash,
result_snapshot: "blake3:...",
state: CandidateReady,
)
Appendix D — Example Queue Policy
(
queue: (
mode: Speculative,
batching: Adaptive(
max_size: 4,
failure_bisect: true,
),
stale_base: Reprepare,
failure: RetryTransient,
max_parallel_candidates: 4,
),
)
Appendix E — External Provider Mapping
| Forgeyard | GitHub | GitLab | Forgejo/Gitea | VCS-native |
|---|---|---|---|---|
| ChangeProposal | Pull Request | Merge Request | Pull Request | native proposal/change |
| ProposalRevision | PR head state | MR source state | PR head state | resolved source state |
| Review | Review | Approval/review | Review | Forgeyard review |
| CheckRun | Check/status | Pipeline/status | Check/status | Forgeyard check |
| IntegrationCandidate | merge candidate | merge result candidate | merge candidate | backend candidate |
| IntegrationQueue | merge queue | merge train analogue | queue if available | Forgeyard queue |
| Integrated | merged | merged | merged | backend submit result |
Forgeyard mapping never makes provider terminology part of the core domain.
Appendix F — Self-Hosting Target
Eventually Forgeyard’s own development process should be:
Git source repository
↓
Forgeyard ChangeProposal
↓
Forgeyard Rust CI
↓
Review + policy
↓
Forgeyard IntegrationCandidate
↓
candidate reproduction
↓
Forgeyard IntegrationQueue
↓
submit exact tested candidate
↓
Forgeyard release pipeline
This becomes a continuous proof that Forgeyard’s VCS-neutral source, Rust ecosystem, CI, review, queue, provenance, and release architectures work together as one production system.