Skip to main content
Greenfield Production Systems

Teardown · Factory v2

The Bugzilla teardown

A 25-year-old open-source codebase, run through the Greenfield Production System end to end, with the output published here. Bugzilla's source is public, so every citation on this page points at code anyone can read.

Bounded contexts
7
Cross-service events
18
Services built
5

01 · Selection

Why Bugzilla

Client estates are confidential by default, which rules them out as public demonstrations. Bugzilla solves that. First released in 1998 and written in Perl, it's still running real installations, and it's gnarly in the specific way 25-year-old production code is gnarly: behavior accreted across decades, enforced partly in CGI scripts and partly deep in module internals.

It's also recognizable. Most engineering leaders have used it or have inherited something shaped like it. Because the source is open, there's no confidentiality screen between you and the evidence: clone the original and check the cited files yourself.

02 · Discovery

What discovery found

Before anything was built, the discovery track read the Bugzilla source and mapped it: 7 bounded contexts and 18 cross-service events. Five of the contexts became services; two were deliberately left behind, and the honest section says why.

Traced in source
  • Rebuilt as the user service

    Users & groups

    Accounts, login validation, group membership, permission grants.

  • Rebuilt as the product service

    Products & components

    Products, components, versions, milestones, default assignees.

  • Rebuilt as the bug service

    Bug lifecycle

    Filing, triage, the status workflow, comments, duplicates, CC lists.

  • Rebuilt as the attachment service

    Attachments & flags

    Uploads, size limits, obsoletion, review-flag requests.

  • Rebuilt as the notification service

    Notifications

    Change mail, recipient resolution, self-mail suppression.

  • Not rebuilt

    Search & reporting

    Stored queries, charts, tabular reports. Found in discovery; not rebuilt.

  • Not rebuilt

    Administration & parameters

    Installation parameters, custom fields, sanity checks. Found in discovery; not rebuilt.

The 18 cross-service events discovery found, with the context that publishes each and the contexts that consume it.
Event Published by Consumed by
user.Events.UserCreated Users & groups Notifications
user.Events.UserDisabled Users & groups Bug lifecycle, Attachments & flags, Notifications
user.Events.UserAddedToGroup Users & groups Bug lifecycle
product.Events.ProductCreated Products & components Bug lifecycle
product.Events.ComponentCreated Products & components Bug lifecycle
product.Events.ComponentUpdated Products & components Bug lifecycle
product.Events.GroupControlsChanged Products & components Bug lifecycle
bug.Events.BugCreated Bug lifecycle Notifications
bug.Events.BugStatusChanged Bug lifecycle Notifications
bug.Events.BugMarkedDuplicate Bug lifecycle Notifications
bug.Events.CommentAdded Bug lifecycle Notifications
bug.Events.BugFieldUpdated Bug lifecycle Notifications
bug.Events.BugGroupAdded Bug lifecycle Notifications
bug.Events.BugMovedToProduct Bug lifecycle Attachments & flags, Notifications
attachment.Events.AttachmentCreated Attachments & flags Bug lifecycle, Notifications
attachment.Events.AttachmentMarkedObsolete Attachments & flags Notifications
attachment.Events.FlagRequested Attachments & flags Notifications
attachment.Events.FlagGranted Attachments & flags Notifications

03 · Build

What was built

Five services: user, product, bug, attachment, and notification. Each follows the same internal structure, with typed contracts at the edge, commands and queries behind them, aggregates holding the rules, and read models serving the views.

services/service-bug
├── packages/contracts/    command, query, and event schemas, typed at the edge
├── service/src/commands/  create-bug · assign-bug · update-bug-status
│                          mark-bug-duplicate · set-bug-resolution · add-comment
├── service/src/queries/   get-bug · get-bug-history · get-bug-comments
├── service/src/domain/    BugAggregate · WorkflowConfigAggregate
└── service/src/read-models/  bug-detail · bug-activity · bug-dependency
The bug service's layout. The other four services follow the same shape.

A web frontend was built against the same contracts, and end-to-end journeys exercise the rebuilt stack the way a user would: filing a bug, triaging and assigning it, resolving and verifying, attaching a patch and requesting review, marking a duplicate.

The run is versioned in the factory's release notes as v2: the port ran end to end through roughly 30 stations and 40 gates, without an architect reviewing between them.

04 · Behavior catalog

The catalog, browsable

This is the artifact the rest of the teardown hangs off. Every behavior carries typed semantics, a provenance citation into Bugzilla's source, and its epistemic tier. Filter the excerpt by context or kind; the full catalog ships with engagements, and the catalog sample page shows the same rows with their migration decisions and the enforcement matrix.

Behavior-catalog excerpt from the Bugzilla teardown, with typed semantics, provenance citations, and epistemic tiers.
ID Behavior Kind Semantics Provenance Tier
bug.create-bug.missing-summary A bug cannot be filed with an empty summary. validation cmd create-bug · require summary ≠ '' Bugzilla/Bug.pm:1163 Proven
bug.create-bug.dependency-cycle A bug's dependencies may not form a cycle. business-rule cmd create-bug · reject cycle(dependsOn) Bugzilla/Bug.pm:1402 Proven
bug.assign-bug.strict-isolation Under strict isolation, the assignee must have edit access to the bug's product. business-rule cmd assign-bug · require productAccess(assignee) when strict_isolation Bugzilla/Bug.pm:2241 Traced in source
bug.mark-bug-duplicate.missing-dup-id Resolving a bug as a duplicate requires the id of the bug it duplicates. validation cmd mark-bug-duplicate · require dupId present Bugzilla/Bug.pm:1488 Proven
bug.mark-bug-duplicate.auto-cc-reporter Marking a bug as a duplicate adds its reporter to the target bug's CC list. business-rule on mark-bug-duplicate · effect target.cc += reporter Bugzilla/Bug.pm:1530 Traced in source
bug.update-bug-status.authorization-assignee Only the assignee, QA contact, or an editbugs member may change a bug's status. authorization cmd update-bug-status · require actor ∈ {assignee, qaContact} ∨ editbugs Bugzilla/Bug.pm:1377 Traced in source
bug.update-bug-status.sideeffect-remaining-time Closing a bug zeroes its remaining time. state-transition cmd update-bug-status(closed) · effect remainingTime → 0 Bugzilla/Bug.pm:1455 Proven
bug.add-comment.body-max-length A comment body may not exceed 65,535 characters. validation cmd add-comment · require len(body) ≤ 65535 Bugzilla/Bug.pm:2840 Proven
user.create-user.invalid-email-regexp A login name must match the system's email pattern. validation cmd create-user · require login ~ emailregexp Bugzilla/User.pm:1684 Proven
user.update-user-profile.disabled-user-self-modify A disabled user cannot edit their own profile. business-rule cmd update-user-profile · deny actor when disabled(actor) Bugzilla/User.pm:2105 Traced in source
product.create-component.assignee-required A component cannot be created without a default assignee. business-rule cmd create-component · require defaultAssignee present Bugzilla/Component.pm:142 Proven
product.create-component.name-unique-per-product A component name must be unique within its product. business-rule cmd create-component · require unique(name) within product Bugzilla/Component.pm:171 Traced in source
attachment.create-attachment.file-too-large An attachment larger than the configured limit is rejected. validation cmd create-attachment · require size ≤ maxattachmentsize Bugzilla/Attachment.pm:512 Proven
attachment.create-attachment.non-insider-cannot-set-private A non-insider user cannot create a private attachment. authorization cmd create-attachment · deny isPrivate when ¬insider(actor) Bugzilla/Attachment.pm:540 Traced in source
attachment.mark-attachment-obsolete.cancels-pending-flags Obsoleting an attachment cancels its pending review flags. business-rule on mark-attachment-obsolete · effect flags(pending) → cancelled Bugzilla/Flag.pm:611 Traced in source
attachment.request-flag.type-not-requestable A flag request is rejected when its flag type is not requestable. validation cmd request-flag · require flagType.isRequestable Bugzilla/Flag.pm:388 Traced in source
notification.get-notification-preferences.default-preferences When a user has set no preferences, the system applies the defaults. business-rule query get-notification-preferences · default when none(prefs) Bugzilla/BugMail.pm:204 Traced in source
notification.send-test-notification.email-transport-not-configured A test notification fails when no email transport is configured. validation cmd send-test-notification · require emailTransport configured Bugzilla/BugMail.pm:331 Probe candidate
An excerpt, not the catalog. Tiers are the factory's, not editorial: proven means a test ran green, traced in source means read from code but not yet run, probe candidate means a live environment is needed to settle it.

05 · Gates

What the gates caught

Work that fails a gate doesn't move forward: it's rejected with the reason, fixed, and resubmitted, and the transcript keeps all of it. Two excerpts from the Bugzilla run follow, failures included, because a transcript with no red entries reads as theater.

Proven
Conventions station run 2026-04-18 · bugzilla-port · service-bug
  1. handler-casing det ✓ pass 102 ms
  2. mapfromevent-namespace det ✕ fail 388 ms
    33 @MapFromEvent calls use short event names; expected fully-qualified bug.Events.*
    Annotation: The read-model gate rejects short event names: a handler that subscribes to BugAssigned instead of bug.Events.BugAssigned would never catch up, so it's a hard failure, not a warning.
  3. mapfromevent-namespace det ✓ pass 391 ms
    re-run after namespacing; all 41 @MapFromEvent calls fully qualified
  4. subscription-events-exist det ✓ pass 96 ms
  5. judge-score llm ✓ pass 8.4 s
    rubric 94/100
5 gates 4 pass 1 fail det = deterministic · llm = judged against a rubric
Scenarios station run 2026-04-22 · bugzilla-port · service-bug
  1. scenarios-typecheck det ✓ pass 6.2 s
  2. assertion-floor det ✕ fail 540 ms
    scenario asserts only success:true; below the floor for a business-rule behavior
    Annotation: The floor rejects a test that only checks the command returned success; a business rule has to assert the emitted event or the rejection code it claims.
  3. assertion-floor det ✓ pass 530 ms
    re-run after asserting the emitted event and the rejection code
  4. no-implement-todos det ✓ pass 88 ms
  5. scenario-scoped-ids det ✓ pass 120 ms
5 gates 4 pass 1 fail det = deterministic · llm = judged against a rubric

A full run's transcript, annotated gate by gate, is at /proof/transcript.

06 · Boundary

What wasn't built

A teardown that only lists what worked is advertising. This one diverges from upstream Bugzilla in ways worth stating plainly.

  • Search and reporting weren't rebuilt

    Bugzilla's stored queries, charting, and tabular reports stayed behind. List views in the rebuild are read models projected from the catalog, not a port of the query engine.

  • Administration collapsed into configuration

    Installation parameters became service configuration rather than a rebuilt admin context. Behaviors keyed to a parameter were either fixed one way or excised, and each decision is recorded in the catalog's migration column.

  • Mail delivery stays a probe candidate

    The notification service consumes the events and resolves recipients, but delivery against live mail infrastructure hasn't run, so those behaviors keep their probe candidate tag instead of being promoted.

  • Scheduled reminder mail was excised

    Bugzilla's "whining" feature (cron-driven nag mail) was excised rather than ported; it is cataloged, with the excision recorded as its migration decision.

Tell us what you have. We'll tell you what proof looks like.