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: behavior, observable result, source provenance, and epistemic tier.
ID Behavior Kind Result Provenance Tier
attachment.create-attachment.happy-path Authenticated user who can see the bug and has product edit access uploads a file attachment happy-path emits AttachmentCreated Bugzilla/Attachment.pm

Attachment creation and field validation

Traced in source
attachment.create-attachment.empty-description Attachment creation requires a non-empty description validation rejects · MISSING_ATTACHMENT_DESCRIPTION Bugzilla/Attachment.pm

ATT-DR-7 — description must be non-empty

Traced in source
attachment.create-attachment.authorization-denied User without permission to see the bug or edit the product is denied attachment creation authorization rejects · PERMISSION_DENIED Bugzilla/Attachment.pm

ATT-DR-1 — requires can_see_bug and can_edit_product

Traced in source
attachment.create-attachment.patch-forces-text-plain When isPatch is true, the MIME type is forced to text/plain regardless of user input business-rule emits AttachmentCreated Bugzilla/Attachment.pm

ATT-DR-4 — patch attachments force text/plain

Traced in source
bug.add-bug-group.happy-path User with editbugs/assignee/QA role adds a group restriction to a bug happy-path emits BugGroupAdded Bugzilla/Bug.pm

AddBugGroup command - group restriction addition

Traced in source
bug.add-bug-group.invalid-group-for-product Cannot add a group that is not valid for the bug's product validation rejects · INVALID_GROUP Bugzilla/Bug.pm

Group validation via GroupControlMapReadModel

Traced in source
bug.add-bug-group.authorization-denied User without editbugs/assignee/QA role cannot add group restrictions authorization rejects · PERMISSION_DENIED Bugzilla/Bug.pm

AddBugGroup requires editbugs/assignee/QA role

Traced in source
bug.add-bug-group.already-member Adding a group the bug already has is a no-op or returns success idempotently business-rule returns success without duplicating the group Bugzilla/Bug.pm

Group addition idempotency

Traced in source
notification.get-notification-preferences.happy-path An authenticated user retrieves their own notification preference matrix, including email-disabled flag, ignored bug IDs, and the full role × change-type preference matrix happy-path returns the preference matrix Bugzilla/BugMail.pm

Notification preference resolution for bugmail recipients

Traced in source
notification.get-notification-log.filter-by-date-range Notification log query supports optional date range filtering to narrow results to a specific time window validation returns records within the date range Bugzilla/BugMail.pm

Notification log filtering by date

Traced in source
notification.get-notification-preferences.unauthorized A user without notifications:read permission is denied access to notification preferences authorization rejects · PERMISSION_DENIED Bugzilla/User.pm

Authorization gate for user notification preferences

Traced in source
notification.get-notification-preferences.owns-preferences-policy A user can only retrieve their own notification preferences; attempting to read another user's preferences is denied by OwnsPreferencesPolicy business-rule rejects · PERMISSION_DENIED Bugzilla/User.pm

OwnsPreferencesPolicy — users can only read their own preferences

Traced in source
product.create-component.happy-path A user with components:manage permission successfully creates a new component under a product happy-path emits ComponentCreated Bugzilla/Component.pm

Component creation logic and required fields

Traced in source
product.create-component.name-required Component creation fails when name is empty or blank validation rejects · component_blank_name Bugzilla/Component.pm

_check_name validator rejects empty strings with component_blank_name

Traced in source
product.create-component.unauthorized-caller Component creation is denied when caller lacks components:manage permission authorization rejects · PERMISSION_DENIED Bugzilla/User.pm

check_can_admin_product method gates component management

Traced in source
product.create-component.name-unique-per-product Component creation fails when name already exists within the same product business-rule rejects · component_name_not_unique Bugzilla/Component.pm

_check_name validator checks uniqueness within product via name collision detection

Traced in source
user.create-user.happy-path Admin creates a new user account with a unique email and valid password happy-path emits UserCreated Bugzilla/User.pm

User creation logic, email uniqueness validation, password complexity check

Traced in source
user.create-user.email-not-unique Cannot create user with an email that already belongs to an active user validation rejects · email_already_exists Bugzilla/User.pm

Email uniqueness validation on create — INV-1

Traced in source
user.create-user.unauthorized User without users:create permission cannot create users authorization rejects · PERMISSION_DENIED Bugzilla/User.pm

Layer 1 permission: users:create required on CreateUser command

Traced in source
user.update-user-profile.disabled-user-self-modify Disabled users cannot update their own profile through self-service business-rule rejects · account_disabled Bugzilla/User.pm

INV-6: Disabled accounts cannot self-modify — admin override allowed

Traced in source
An excerpt, not the catalog: 20 of 419 behaviors across the five services. Provenance is the source file and the catalogue's note; the real catalog carries no line numbers, so neither do we. Every row is traced in source: read from code, not yet run. (Proven means a test ran green; 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.