Skip to content

BookingZone Spec Automation — How It Triggers

This document explains, end to end, what fires on every Issue change, PR, and merge, and how specs stay synchronised across reservation-api-server, admin-web-app, venuplus-app, customer-web-app, and this repo.

If you're new to the platform: read README.md, constitution/system-constitution.md, and PROJECT-MANAGEMENT.md first.


The mental model in 1 sentence

Issues drive lifecycle. PRs drive code. Specs capture intent. The automation keeps all three in sync without humans having to remember.

GitHub Issues + ProjectV2 are the lifecycle source of truth. Branches and PRs are implementation artifacts. Spec frontmatter status is a projection of Issue state, not branch state.


Process-guard caller contract

Process-guard workflows (branch-name-guard, pr-auto-tracking-issue, spec-discipline) are reusable workflows in this repo. Consumer repos call them, but triggers (on:) must be defined in the caller, not the reusable.

To avoid burning Actions minutes on PRs that don't need guards (e.g. drafts into integration/* branches), callers MUST:

  1. Filter to canonical branches: branches: [develop, main]
  2. Use minimal event types (see table in linked doc)
  3. Set concurrency: cancel-in-progress: true keyed by PR number

Full pattern and copy-paste snippets: docs/PROCESS-GUARD-CALLERS.md

Canonical policy: ADR-0012


Triggers and what they do

0. Every Issue opened with a spec template → issue-lifecycle.yml

When an Issue with label spec:local, spec:trace, or spec:master is opened:

  1. issue-to-spec-reusable.yml runs in the consuming repo
  2. Picks the next free spec ID (NNN for local/trace, SYS-NNN for master)
  3. Creates branch feat/issue-NNN-<slug>
  4. Scaffolds specs/NNN-<slug>/spec.md with frontmatter pre-filled (status: Draft, linked_issues: [#NNN], constitution_version)
  5. Adds plan.md and tasks.md placeholders
  6. Pushes the branch and opens a PR linked to the Issue
  7. The PR is the contract for the spec content; review/merge as normal

This means: opening an Issue is the only action a team member takes to start a new feature. Everything else cascades.

0b. Every Issue status / state change → issue-lifecycle.yml

Triggers: issues:[edited|closed|reopened] and projects_v2_item:[edited].

Calls issue-status-sync-reusable.yml:

  1. Resolves the spec path from the Issue body (or stored mapping)
  2. Reads the Project Status field value
  3. Maps Project Status → spec frontmatter status (table below)
  4. Updates the spec.md frontmatter and commits to default branch with [skip ci]

Mapping:

Project Status Spec status
Backlog / Refining / Ready Draft
In Progress / In Code Review InProgress
In QA InQA
Done Completed
Cancelled Cancelled
Blocked (no change)
(Issue closed completed) Completed
(Issue closed not_planned) Cancelled

A. Every PR opened in any repo → spec-discipline.yml

Calls the reusable workflow spec-discipline-reusable.yml in this repo. It runs five checks:

# Check Action
1 Spec frontmatter lint Every spec under specs/ must have valid YAML frontmatter (spec_id, type, status, master_spec_path for traces, etc.). Tool: tools/lint_specs.py. Hard fail.
2 Platform-version match Repo's AGENTS.md "Validated Against" line must match this repo's system-constitution.md **Version**:. Tool: tools/check_platform_version.py. Warns until major version drift.
3 Anti-pattern term scan Greps changed files for "Reservation", "Branch", "Tenant", "Operator". Warns. (Hard-fail later, once team is bedded in.)
4 PR auto-tracking issue If a PR has no Closes/Fixes/Resolves #N link, the workflow auto-creates a tracking Issue, applies a type:* label inferred from the PR title prefix, edits the PR body to insert Closes #M, and (with PROJECT_TOKEN) attaches the Issue to Project #1. Replaced the older strict "must reference an Issue or fail" enforcer (since auto-tracking guarantees the link).
5 Summary Posts a checks summary into the PR.

B. Every merge to main in any repo → (mostly archival)

In the new Issue-driven model, merge events are not the trigger for spec status changes. Issues are. Merges still:

  • Run the test/build pipeline
  • Trigger contract extraction (backend only — see below)
  • Optionally post a comment on the linked Issue (PR #X merged into main)

The legacy post-merge-status.yml flips status from commit messages as a backstop, but it should be redundant once Issues are in active use.

C. Backend merge to main → extract-contracts.yml (reservation-api-server only)

Triggers when any *.py under bookingzone/ or any DocType JSON changes.

reservation-api-server@main
        │
        ▼
.github/scripts/extract_api_contract.py
        │
        ├─→ artifacts/openapi.json         (476 whitelisted methods)
        └─→ artifacts/doctype-schemas.json (150 BZ* DocTypes)
        │
        ▼
content hash vs contracts/latest/  ──── unchanged? ──→ exit
        │ changed
        ▼
semver bump:
  - major: method removed / required field removed
  - minor: method added / optional field added
  - patch: docstring-only change
        │
        ▼
commit artifacts to bz-platform-context/contracts/v<X.Y.Z>/
update contracts/latest/
bump @bookingzone/api-types to v<X.Y.Z>
        │
        ▼
repository_dispatch ──→ admin-web-app
                  ──→ venuplus-app
                  ──→ customer-web-app

D. Frontend receives api-contract-update event → bump-api-types.yml

  • Bumps @bookingzone/api-types in package.json
  • Regenerates lockfile
  • Opens an auto-PR labelled api-contract automated
  • Resulting PR's CI runs the frontend's normal build/typecheck/test
  • CI passes → forward-compatible. Merge.
  • CI fails → a typed call site broke. Treat the failure list as the work to do. Open a trace spec in specs/ referencing the relevant master, fix, then merge.

E. High-signal repo updates → platform-context-sync-reusable.yml

To keep bz-platform-context continuously fresh without mirroring every code change, source repos can call:

  • .github/workflows/platform-context-sync-reusable.yml

The caller decides when to sync (typically push to main with path filters for high-signal files). The reusable workflow then:

  1. Updates sync-state/<repo>.json with source SHA/ref, mode, tags, summary
  2. Keeps a rolling history (last 25 events) for traceability
  3. Reuses one rolling branch (sync/<repo>) and one open PR per repo

This gives continuous signal freshness with low PR noise. See docs/PLATFORM-CONTEXT-SYNC.md for the full policy and caller example.


What is NOT automated (by design)

These remain human-driven; AI in Cursor/Claude Code helps but doesn't auto-merge:

Activity Why manual
Writing the content of specs (problem, user stories, success criteria) Captures intent — only humans + AI in IDE understand intent
Constitution amendments Architectural decisions need human review and 2 maintainer approvals
Glossary additions Domain naming is high-stakes, slow-changing
ADRs Documenting why requires context machines don't have
Moving Issues through Project columns The team does this in stand-up; bot reflects it
Deciding In QA → Done QA sign-off is a human decision
Sprint planning Selecting next sprint's capacity is a team conversation

The automation handles the structural plumbing. Humans handle the semantic content. This is the only sustainable split.


Required GitHub setup (one-time)

For the automation to work, the following must exist:

1. GitHub App or PAT: bz-platform-bot

A bot identity with these scopes/permissions:

  • Read on every BookingZone repo
  • Write (contents + pull-requests) on bz-platform-context
  • Write (workflows + pull-requests) on admin-web-app, venuplus-app, customer-web-app

Recommended: GitHub App (more granular, auditable). Alternative: a fine-grained PAT on a dedicated bot user.

2. Repo secrets

Add the bot token as a secret named PLATFORM_CONTEXT_TOKEN in:

  • reservation-api-server
  • admin-web-app
  • venuplus-app
  • customer-web-app
  • Any repo that calls platform-context-sync-reusable.yml

3. Branch protection

On bz-platform-context main:

  • Required PR review (1 maintainer for spec changes, 2 for constitution changes)
  • Required status checks: spec-lint
  • No direct pushes (bot included — bot pushes via PR for non-trivial changes)

On consumer repos:

  • Required status check: Spec Discipline / discipline
  • Required status check: existing build/test workflows

4. Repo registry

The list of repos to dispatch to lives in extract-contracts.yml under strategy.matrix.repo. Update it when adding a new consumer.


Local development workflow

As an engineer working on a repo

  1. Pull main; create a feature branch.
  2. Either:
  3. Repo-local feature — pnpm specify spec (or /speckit-specify in Cursor) → write specs/NNN-<slug>/spec.md
  4. Cross-cutting feature — open a PR to bz-platform-context adding specs/SYS-NNN-<slug>/spec.md (master) + a repo-impact/<repo>.md for each impacted repo. Once merged, create trace specs in each repo.
  5. Implement. Push. PR.
  6. CI runs spec-discipline. Address any failures.
  7. Merge. Status auto-flips. Move on.

As a platform maintainer (changing the constitution / glossary / ADRs)

  1. PR to bz-platform-context.
  2. Bump constitution version per the rules in system-constitution.md.
  3. Add an Amendment Log entry.
  4. Two approvals required.
  5. After merge, consumer repos see the version mismatch in their next CI run; update each repo's AGENTS.md "Validated Against".

Failure modes and recovery

Failure Symptom Recovery
lint_specs.py fails PR red on "Spec Discipline / discipline" Read the error; fix the frontmatter; force-push the branch.
Anti-pattern term flagged Warning in CI logs Rename in code/docs. (Currently warn-only; will be hard fail.)
Large-PR without spec link PR red Either link a spec, or add No spec needed: <reason> in PR body.
Contract extractor fails extract-contracts.yml red on backend main This blocks frontend bump but not the merge itself. Investigate via job logs; usually a DocType JSON parse error.
Repository dispatch fails Frontend doesn't get a bump PR Check PLATFORM_CONTEXT_TOKEN scopes. Manually re-run extract-contracts.yml once fixed.
Auto-bump PR CI fails Frontend has typed drift Treat as work-to-do. Open a trace spec referencing the master that caused the bump. Fix calls.
Stale spec status Spec stays Draft despite work happening The PR body didn't link the spec. Edit the PR body, or directly edit specs/NNN/spec.md frontmatter.

Performance and cost

  • Reusable workflows mean ~30 lines of YAML per consumer repo, not 300.
  • All checks run on ubuntu-latest, free tier on public repos. Private repos: ~2 minutes per PR. Negligible.
  • Contract extraction: ~6 seconds for 476 methods + 150 DocTypes (measured against real bookingzone/ package).
  • Repository dispatch latency: ~10s end to end.

Migration / rollout (from current state)

You are HERE. Rollout sequence (revised for Issue-driven model):

Phase Action Owner Effort
0 Push bz-platform-context to GitHub at aerospace-apps/bz-platform-context platform 15 min
1 Create the GitHub org Project BookingZone Engineering (UI or tools/setup_project.py); add Sprint iteration field via UI platform 30 min
2 Create bz-platform-bot identity (GitHub App preferred); add PLATFORM_CONTEXT_TOKEN secret to each repo DevOps 30 min
3 PR each consumer repo (reservation-api-server, admin-web-app, venuplus-app): adds AGENTS.md, .github/PULL_REQUEST_TEMPLATE.md, .github/ISSUE_TEMPLATE/, .github/workflows/{spec-discipline,issue-lifecycle}.yml each repo owner 1 hr/repo
4 Merge to main. Automation goes live for new PRs and new Issues. each repo owner —
5 Update projects: reference in each repo's Issue templates to point to the real Project URL/number platform 10 min
6 Train the team (30-min walkthrough): how to open Issues, board flow, Project columns, what auto-happens tech lead 30 min
7 PR reservation-api-server adding extract-contracts.yml + extract_api_contract.py. First run publishes v0.1.0 of the API contract platform 1 hr
8 PR each frontend adding bump-api-types.yml each frontend owner 30 min/repo
9 Optional: incrementally migrate frontends to actually import from '@bookingzone/api-types', starting with one tRPC procedure each frontend gradual
10 Optional: Slack/Jira bridge if PMs need it DevOps half day

After Phase 6 the team is on the new model. Phases 7–9 add the API contract layer, which is independent of the Issue lifecycle work.