At Level 4 Integrator, a code change is not delivered when it works on your machine. It is delivered when it has a known base, a narrow branch, reproducible local evidence, an inspectable pull request, an independent review, required checks, and a merge path that protects everybody else's work.
2. The missing change
You update a validation function on Monday and tell a colleague it is ready. On Tuesday they edit the same file to fix a rounding problem. Both of you upload your local copy to the shared folder. Their upload is last, so your validation disappears. The application still starts, and nobody notices until the following week when an invalid synthetic quantity passes through the pipeline.
There is no reliable answer to four basic questions: which version was the shared starting point, what each person changed, whether the changes conflicted, and which checks ran before the final file replaced the earlier one. Email attachments and shared folders preserve files, not a reviewable sequence of proposals.
You move the work into a shared Git repository. Each change begins from the current protected branch, travels on its own topic branch, and becomes a pull request whose diff, tests, discussion, approval, and final commit remain connected. The forge does not make the code correct, but it makes silent replacement much harder.
3. After this you can
- Deliver one small code change on a topic branch as a reviewable pull request.
- Validate behavior, failure cases, diff scope, and repository state locally before pushing.
- Reconcile an upstream change without silently discarding either person's work.
- Configure or verify a protected shared branch with required checks and human review.
- Distinguish Git history from the collaboration controls supplied by Gitea or GitHub.
4. Prerequisites
T08-L03· Spec-driven development, including a narrow change contract, numbered acceptance criteria, protected behavior, and evidence-based rejection.- Git 2.23 or later, Python 3.10 or later, and Git Bash on Windows, macOS, or Linux shell equivalents.
- An owner-authorised disposable Gitea or GitHub repository containing only the synthetic project below, with a displayed default branch and no unrelated work.
- Permission to create and push a topic branch and open a pull request. Branch-rule changes require separate owner authorization.
- One independent reviewer who did not author the change and can judge the tiny synthetic rule.
- A pre-existing CI job named
unit-tests, or permission for the repository owner to add that job separately before the exercise. Allow about 90 minutes.
Never practise in an unfamiliar work repository. Do not paste a token into a clone URL, command, script, pull-request description, issue, or CI file. Use the forge's approved credential helper, SSH agent, or interactive sign-in. If a secret is committed, stop and notify the owner so it can be revoked; deleting it in a later commit does not remove it from history or make it safe again.
5. The idea in one page
Git and a forge solve related but different problems. Git records local snapshots and branch relationships. A forge such as Gitea or GitHub stores a shared copy and adds pull requests, identities, review discussions, checks, and branch policy. A remote URL is a destination, not proof that you are authorised to use it.
protected default branch at known commit
|
+-> topic branch -> small commit -> local evidence -> push
|
v
pull request: diff + check + review
|
policy permits merge only when ready
|
v
new shared branch commit
A branch isolates a proposal from the shared line. It does not isolate data, confer permission, or approve the change. Start from the current remote default branch, give the branch one purpose, and keep unrelated formatting, generated files, dependencies, and cleanup out of it.
A diff is the primary review surface. Review the base-to-tip diff, not only the last commit, because a pull request may contain several commits. Map each hunk to an acceptance criterion. Check changed filenames before reading code; an unexpected path is often the fastest sign that scope escaped.
Local validation comes before push. Run focused behavior tests, the broader repository checks required by the contract, and git diff --check. Record commands and results from the exact topic commit. CI then repeats checks in a clean environment for that pushed commit. A green CI badge does not prove that the tests express the right requirement, and a local pass does not prove that the branch policy will enforce anything.
A pull request is the unit of review: exact source and target, total diff, linked requirement, evidence, assumptions, reviewer discussion, approval, check results, and merge commit. Keep the description factual. "AI says complete" is not evidence. If an agent contributed, the human author still owns every line and must disclose material assistance according to repository policy.
Branch protection is server-side policy. For this exercise, the default branch should reject direct updates, require the exact unit-tests check for the current commit, require one independent approval, and invalidate or require review again after substantive changes. Product names differ and plan tiers may limit controls, so inspect the effective rule in the actual repository. Never claim protection from documentation or a settings screenshot alone.
When the remote default branch moves, fetch it before merging. Rebase or merge according to team policy, inspect every conflict, rerun all required validation, push the reconciled commit normally, and obtain fresh review where policy requires it. Conflict markers disappearing does not prove both intentions survived.
6. The worked example: one change through the shared gate
Choose one of the two complete flows below. Each uses the same small function and the same review gate, but the contract language, branch, reviewer question, and consequence of failure remain specific to its setting. Do not put both changes in one pull request. The range of 1 through 100 is synthetic exercise policy, not a scientific, safety, procurement, or commercial recommendation.
Confirm the review destination visually
Before cloning, open the owner-provided repository page and match its owner, repository name, and displayed default branch to the authorization you received. The exact interface may differ, but those three observations must agree.

Use the repository identity and branch selector to confirm the intended review destination before cloning or pushing; the image is not proof of authorization or branch protection. Source: 32dots Gitea course instance (opens in a new tab).
This public course-authored capture contains no exercise credentials or private records. It was reviewed for privacy and interface relevance on 2026-09-04. Treat it as an orientation image only: inspect the live owner-authorised repository because names, navigation, and branch controls can change.
Begin with a known shared repository
The repository owner creates the disposable remote from this baseline on its displayed default branch:
quantity-review/
quantity.py
test_quantity.py
CHANGE-CONTRACT.md
.gitignore
quantity.py initially contains:
def normalize_quantity(value):
return int(value)
test_quantity.py initially protects known behavior:
import unittest
from quantity import normalize_quantity
class QuantityTests(unittest.TestCase):
def test_integer_text_is_normalized(self):
self.assertEqual(normalize_quantity("12"), 12)
if __name__ == "__main__":
unittest.main()
The owner commits one of the contracts below as CHANGE-CONTRACT.md. The CI configuration already runs python -m unittest -v in a job whose displayed required-check name is unit-tests. Do not add or edit CI in this pull request; that would be a separate infrastructure change with a different reviewer and blast radius.
Clone only the exact owner-provided disposable URL. Keep the URL out of the manuscript and let the credential helper handle authentication:
git clone "$AUTHORIZED_REPO_URL" quantity-review
cd quantity-review
git remote -v
git status --short --branch
git branch --show-current
You should see the approved destination, a clean working tree, and the displayed default branch. If the URL, repository identity, status, or branch differs, stop. Do not remove an unknown remote, reset unknown work, or guess whether main, master, or another name is the default.
Fetch and record the exact base:
git fetch --prune origin
BASE_BRANCH="$(git remote show origin | sed -n '/HEAD branch/s/.*: //p')"
test -n "$BASE_BRANCH"
git switch "$BASE_BRANCH"
git pull --ff-only origin "$BASE_BRANCH"
BASE_COMMIT="$(git rev-parse HEAD)"
printf 'Base: %s %s\n' "$BASE_BRANCH" "$BASE_COMMIT"
--ff-only refuses to invent a local merge when the branch relationship is unexpected. If it fails, inspect and ask the owner; do not force, reset, or overwrite. Continue with either the Lab flow or the Company flow, not both.
Lab worked-example flow: bound a synthetic analysis batch
The Lab repository represents a small analysis pipeline. An unbounded requested sample count can waste shared compute and make a collaborator's queued run wait; it does not determine scientific validity. The fixture's committed contract is:
ID: LAB-SAMPLE-RANGE-1
Owner: synthetic analysis-pipeline owner
Outcome: normalize_quantity accepts an integer sample count from 1 through 100 inclusive.
AC1: "1" and "100" return 1 and 100.
AC2: "0", "101", and "-2" raise ValueError containing "between 1 and 100".
AC3: "twelve" raises ValueError; no quantity is returned to the analysis caller.
AC4: existing "12" behavior remains 12.
Allowed paths: quantity.py and test_quantity.py only.
Must not change: function name, one-argument interface, dependencies, repository configuration.
Stop: ask the pipeline owner if zero or batches above 100 are actually required.
Verify that this exact ID and outcome are committed at $BASE_COMMIT, then create the Lab branch:
CONTRACT_ID="LAB-SAMPLE-RANGE-1"
TOPIC_BRANCH="lab-sample-range"
grep -F "ID: $CONTRACT_ID" CHANGE-CONTRACT.md
git switch -c "$TOPIC_BRANCH"
Use the implementation and validation below. In the pull request, ask the independent reviewer to decide whether AC1-AC4 reject an invalid batch count before returning a value while preserving the existing synthetic count. The affected people are the submitter and collaborators sharing the queue. Connecting the function to a real scheduler is outside this exercise.
Company worked-example flow: bound a synthetic training order
The Company repository represents an internal training-order tool. An unbounded quantity can create a bad internal request and manual correction work; this exercise never submits a real order. The fixture's committed contract is:
ID: COMPANY-ORDER-RANGE-1
Owner: synthetic internal-tool owner
Outcome: normalize_quantity accepts an integer training-order quantity from 1 through 100 inclusive.
AC1: "1" and "100" return 1 and 100.
AC2: "0", "101", and "-2" raise ValueError containing "between 1 and 100".
AC3: "twelve" raises ValueError; no quantity is returned to the order caller.
AC4: existing "12" behavior remains 12.
Allowed paths: quantity.py and test_quantity.py only.
Must not change: function name, one-argument interface, dependencies, repository configuration.
Stop: ask the tool owner if zero or quantities above 100 are actually required.
Verify that this exact ID and outcome are committed at $BASE_COMMIT, then create the Company branch:
CONTRACT_ID="COMPANY-ORDER-RANGE-1"
TOPIC_BRANCH="company-order-range"
grep -F "ID: $CONTRACT_ID" CHANGE-CONTRACT.md
git switch -c "$TOPIC_BRANCH"
Use the same implementation and validation below. In the pull request, ask the independent reviewer to decide whether AC1-AC4 reject an invalid synthetic order quantity before returning a value while preserving the existing quantity. The affected people are the requester and the operations colleague who would otherwise reconcile it. Connecting the function to a real order system is outside this exercise.
Implement only the chosen contract
Keep the remaining commands in the same shell so $BASE_BRANCH, $CONTRACT_ID, and $TOPIC_BRANCH retain the values set above. If the shell closes, recover the displayed base branch and current topic branch, then re-enter the matching contract ID; do not guess among the two variants.
Replace quantity.py with:
def normalize_quantity(value):
try:
quantity = int(value)
except (TypeError, ValueError) as error:
raise ValueError("quantity must be an integer") from error
if not 1 <= quantity <= 100:
raise ValueError("quantity must be between 1 and 100")
return quantity
Replace test_quantity.py with:
import unittest
from quantity import normalize_quantity
class QuantityTests(unittest.TestCase):
def test_ac1_accepts_boundaries(self):
self.assertEqual(normalize_quantity("1"), 1)
self.assertEqual(normalize_quantity("100"), 100)
def test_ac2_rejects_values_outside_range(self):
for value in ("0", "101", "-2"):
with self.subTest(value=value):
with self.assertRaisesRegex(ValueError, "between 1 and 100"):
normalize_quantity(value)
def test_ac3_rejects_non_integer_text(self):
with self.assertRaisesRegex(ValueError, "must be an integer"):
normalize_quantity("twelve")
def test_ac4_preserves_known_behavior(self):
self.assertEqual(normalize_quantity("12"), 12)
if __name__ == "__main__":
unittest.main()
The implementation catches only conversion failures it intends to normalize. It checks both inclusive boundaries, preserves the interface, and adds no dependency. It deliberately does not decide whether strings such as "1.0", booleans, or whitespace need different rules; if those matter, the relevant pipeline or tool owner must add criteria before code changes.
Validate before creating history
Run the exact local checks:
python -m unittest -v
git diff --check
git diff --name-only
git diff -- quantity.py test_quantity.py
Expected test ending:
Ran 4 tests
OK
git diff --check should print nothing. git diff --name-only should print exactly quantity.py and test_quantity.py. Read the full diff and map each implementation and assertion to AC1 through AC4. If another path appears, stop and understand its origin; do not commit around unexplained work.
Create one focused commit:
git add -- quantity.py test_quantity.py
git diff --cached --check
git diff --cached --name-only
git commit -m "Validate synthetic quantity range"
git status --short --branch
git log --oneline --decorate -2
The staged path command must name only the two allowed files. After the commit, status should show your chosen $TOPIC_BRANCH with no changed-file lines. Record the commit ID; local evidence applies to this commit, not to future edits.
Reconcile the second person's change
Before pushing, suppose another person has merged a test-only clarification into the remote default branch. Fetch and compare:
git fetch origin
git log --oneline --left-right --graph HEAD..."origin/$BASE_BRANCH"
git diff --name-status HEAD..."origin/$BASE_BRANCH"
If the remote changed, follow the repository's stated integration policy. For a rebase policy:
git rebase "origin/$BASE_BRANCH"
On a conflict, open each marked file, compare the contract, your change, and the incoming change, and ask the relevant owner when intent conflicts. After an authorised resolution, stage only the resolved files and run git rebase --continue. Never choose "ours" or "theirs" merely to remove markers. Never force-push a shared branch; this topic branch belongs only to this exercise.
Whether or not a conflict occurred, rerun:
python -m unittest -v
git diff --check "origin/$BASE_BRANCH"...HEAD
git diff --name-only "origin/$BASE_BRANCH"...HEAD
The tests must still end with Ran 4 tests and OK, and the total pull-request diff must still name only the two contracted paths. This is the evidence that both the upstream base and your proposal were considered after reconciliation.
Push the topic branch, not the default branch
git push -u origin "$TOPIC_BRANCH"
Use the approved credential flow. If authentication fails, do not put a token in the URL or command. If the server rejects the branch, read the error and ask the owner rather than changing protection. Do not run git push --force.
Open a pull request from the chosen $TOPIC_BRANCH to the displayed default branch. Before submitting, verify the base and compare views show only the intended two files. Replace the bracketed context with synthetic sample count for Lab or synthetic training-order quantity for Company, and use this description:
Change: enforce [context] range 1..100.
Contract: [value of CONTRACT_ID].
Scope: quantity.py and test_quantity.py only.
Local validation: python -m unittest -v -> 4 tests, OK; git diff --check -> no output.
Boundaries tested: 1 and 100 pass; 0, 101, -2, and non-integer text fail.
Preserved: one-argument interface and "12" -> 12.
Data: synthetic only. Dependencies: none.
Decision requested: review AC1-AC4, the context-specific no-downstream-effect claim, and the complete base-to-tip diff.
Do not claim the CI result before it runs. Once it runs, match unit-tests to the current pull-request commit. A passing result attached to an older commit is stale evidence.
Review meaning and enforcement separately
The reviewer first reads CHANGE-CONTRACT.md, then the Files changed view, then each test assertion. They verify that int("1.5") failing is consistent with "integer text," that both boundaries are inclusive, that errors return no quantity, and that no policy or dependency changed. A useful review comment cites an acceptance criterion and observed code; "looks good" alone does not show what was reviewed.
Separately, the repository owner inspects the effective rule for the displayed default branch. Record whether direct updates are restricted, one independent approval is required, unit-tests is required for the current commit, and approval is dismissed or made stale after relevant changes. In Gitea this is commonly configured under protected branches; in GitHub it may be branch protection or a ruleset. Labels and availability vary. If the owner cannot observe or configure a required control, write not enforced and do not represent the repository as protected.
After any review-requested code change, rerun local validation, commit normally, push, wait for the current check, and obtain renewed approval as policy requires. Conversation resolution is not a substitute for changing failing code.
When all required conditions apply to the current commit, an authorised person merges using the repository's agreed strategy. Record pull-request URL, source and target, final diff, author commit, check run, reviewer identity and decision, merge commit, and merge time. Then update your local view without rewriting history:
git switch "$BASE_BRANCH"
git pull --ff-only origin "$BASE_BRANCH"
python -m unittest -v
git branch --contains "$(git rev-parse HEAD)"
Confirm the merged default branch runs the same four tests. The final branch listing is only supporting local evidence; the forge's merged pull request and merge commit establish the shared result.
Troubleshoot without destroying evidence
If git pull --ff-only fails, stop and inspect branch relationships; do not reset. If tests fail locally but CI passes, compare commit IDs, Python versions, commands, dependencies, and environment variables. If CI never appears, verify that its workflow exists on the target branch, its trigger includes pull requests, and the displayed job name matches the required rule; do not remove the requirement. If a reviewer approved an old commit, request review of the current one. If merge is blocked, read each unmet condition rather than bypassing policy.
7. What goes wrong
You commit directly to the shared branch
Symptom: the change reaches the default branch without a proposal or review surface.
Fix: protect the branch server-side and work from a fresh topic branch. Do not rewrite shared history to manufacture a pull request after the fact.
The pull request contains four changes
Symptom: quantity validation arrives with formatting, dependency updates, generated files, and a rename.
Fix: keep only work mapped to the contract. Move genuinely necessary prerequisites into separately reviewed changes and rebase on them after merge.
You push before running anything
Symptom: CI becomes the first place a syntax error or obvious failure is discovered.
Fix: run focused behavior tests, required repository checks, whitespace validation, and path inspection locally on the exact commit before push.
Review is a rubber stamp
Symptom: approval appears seconds after opening, with no evidence that criteria, tests, or the total diff were inspected.
Fix: ask for criterion-based review by an independent person. Keep automated checks and semantic approval as separate required conditions.
A secret is removed in the next commit
Symptom: the current diff looks clean, but the token remains in an earlier commit, cache, log, or pull-request event.
Fix: stop, revoke first, notify the owner, follow repository incident procedures, and clean history only through an approved coordinated process.
A conflict is resolved by choosing one side
Symptom: conflict markers vanish, but either validation or the colleague's correction silently disappears.
Fix: reconstruct both intents against the current contract, resolve deliberately, inspect the base-to-tip diff, rerun all checks, and obtain fresh review.
Protection exists only in the write-up
Symptom: the report cites product documentation, but direct pushes, stale approvals, or missing checks are still accepted by the actual repository.
Fix: have the owner inspect the effective rule on the exact target branch and record unmet controls honestly. Never bypass a missing control to complete the exercise.
8. Do it yourself: one merged pull request in 90 minutes
Minutes 0-10: obtain the synthetic repository URL, displayed default branch, approved identity, contract, reviewer, check name, and branch-rule owner. Inspect the remote identity and clean starting state. Stop on unknown files, history, or permissions.
Minutes 10-20: fetch the remote, fast-forward the local default branch, record its commit, and create one purpose-named topic branch. Re-read the acceptance criteria and allowed paths before editing.
Minutes 20-38: implement the smallest change and focused tests. Run ordinary, boundary, failure, and preservation checks. Inspect whitespace, changed names, and the complete diff. Keep exact commands and actual outputs.
Minutes 38-48: stage only allowed files, inspect the staged diff independently, and create one focused commit. Confirm the working tree is clean. Fetch again and compare with the remote default branch.
Minutes 48-58: reconcile any upstream movement using team policy. Resolve conflicts by intent, never convenience. Rerun every local check and confirm the total base-to-tip diff remains within scope.
Minutes 58-68: push only the topic branch and open a pull request to the displayed default branch. State contract, scope, local evidence, assumptions, data class, dependencies, and requested decision. Check the current commit ID.
Minutes 68-80: wait for the required check and have the independent reviewer assess criteria, implementation, tests, and complete diff. Apply any requested fix through another normal commit, rerun locally, and require current CI and renewed review.
Minutes 80-87: have the authorised owner verify effective protection for the exact target: direct-update restriction, required current check, independent approval, and handling of later changes. Record unavailable controls as gaps rather than silently changing the claim.
Minutes 87-90: after all current conditions pass, merge through the authorised forge action. Fast-forward a clean local default branch, rerun the tests, and complete one evidence packet linked to the merged pull request.
9. Exit check
Deliver exactly one artifact: one merged pull-request evidence packet containing the frozen change contract, authorised repository and base commit, topic branch and commits, total changed-path list and diff, exact local validation commands and output, reconciliation evidence if the base moved, pull-request URL and current commit, required CI result, criterion-based independent review, observed default-branch rule, merge commit, and post-merge test result.
It passes when the pull request changes only the contracted paths; local validation passed before push and again after any reconciliation; the required check belongs to the reviewed commit; an independent person approved the behavior and tests; the effective target rule prevented an unreviewed or unchecked merge path to the extent the platform supports; the forge records the merge; and the merged default branch passes the same validation. Every unknown or unavailable protection must remain visible.
It fails if work was committed directly to the default branch, the diff includes unrelated changes, CI is the only review, approval applies to an older commit, a conflict discarded work, a secret entered history, policy was bypassed, or the report calls a branch protected without observing its effective rule. Repair through new reviewable commits; never rewrite shared history merely to make the packet look clean.
10. Rule to remember
Small enough to review in ten minutes.
11. Further reading & tools
- Taught:
T08-L03· Spec-driven development - supplies the narrow contract and numbered criteria that define this pull request's scope and review decision. - Taught: Why teams need a forge - separates Git history from the shared proposal, identity, and coordination surface.
- Taught: Git: local state and remote collaboration - establishes working tree, local commit, branch, and remote as distinct states.
- Taught: Branches protect shared work - isolates a proposal from the shared baseline before editing.
- Taught: Diff and local validation - validates exact paths, whitespace, content, commit, and clean state before publication.
- Taught: Review and branch protection - distinguishes human review, current checks, and effective server policy.
- Catalogued: Gitea - the course's owner-authorised disposable forge route for publishing a branch and opening an unmerged proposal.
- Catalogued: GitHub and Gitea forge controls - compares transferable concepts without assuming equivalent labels or enforcement.
- Catalogued: Building complex codebases - maps an unfamiliar repository and marks unsupported assumptions before a change.
- Catalogued: Git book: Distributed Git (opens in a new tab) - primary guidance on shared Git workflows and integration choices.
- Catalogued:
T03-L04· Prompts as versioned assets - applies the same history, evaluation, review, promotion, and rollback discipline to production prompts. - Catalogued:
T08-L05· Agents in CI and supply chain - continues from human-authored pull requests to bounded agents, pipeline evidence, dependencies, and rollback.