Your First Go Service, Contract First
Build and release a secure Go API with an OpenAPI contract and review evidence.
Introduction
30 Second Summary
Software is easy to trust when its happy path works. The harder test arrives when someone asks for proof behind every promise.
In this project, you will build a public Go 1.27.1 HTTP service using contract-first development. Its OpenAPI 3.1.2 document is committed before the implementation.
What You'll Build
Picture demoing a public repository where every route, refusal, design choice, and release claim has evidence beside it.
By the end of this project, you'll have:
- A working Go service where GET /healthz reports a healthy status while POST /echo returns the submitted message.
- A red-and-green contract check that names a route when the document or Go route table drifts.
- Secure refusal evidence showing oversized, extra-field, and empty bodies fail with fixed problem details while the health route stays live.
- Secret Mission: Turn the green branch into a reviewed v0.1.0 release with a two-minute defense of all four design decisions.
Are there any prerequisites?
Project 1 must be complete before you begin. You also need the twelve supplied project files plus an account on GitHub that can create a public repository and Project.
Before We Start
You are committing to ship first-service as a public, reviewed, contract-first development project in Go. Its openapi.yaml document must be committed before the Go route table so the documented API promise leads implementation and review.
Verify the Toolchain and Create the Repository
The service depends on a healthy Windows toolchain. A missing command or mismatched linter can consume the build window before your Go service exists.
You will prove the inherited Project 1 environment first. You will create a local Git repository only after every dependency is accounted for.
In this step, get ready to:
- Verify the inherited Windows toolchain.
- Set the editor plus AI operating rules.
- Create first-service as a sibling local repository.
Verify the inherited toolchain
The doctor task checks the tools inherited from Project 1. This gives you one repeatable place to catch missing commands before they interrupt the service build.
- Press the Windows key to open Windows Search.
- Type PowerShell 7.
- Press Enter to open PowerShell.
- Check the PowerShell version by running this command:
$PSVersionTable.PSVersion
What does this check?
The result identifies the PowerShell session you are using. This project requires PowerShell 7.3 or newer so later curl.exe commands behave consistently.
✔️ I see the required version
Your PowerShell version is 7.3 or newer. Keep this window open for the toolchain checks.
ⓧ I see an older version
PowerShell 7 installs beside Windows PowerShell 5.1. Installing the current release preserves the older system shell.
- Install the current PowerShell release by running this command:
winget install --id Microsoft.PowerShell --source winget
What does this command do?
WinGet installs the stable PowerShell package from its package source. The installer may request permission to update your device.
- Close the older PowerShell window.
- Repeat the Windows Search steps to open PowerShell 7.
- Run the version check again.
ⓧ PowerShell 7 is missing
Use the built-in Windows shell once to install PowerShell 7. WinGet may request permission before it changes your device.
- Press the Windows key to open Windows Search.
- Type Windows PowerShell.
- Press Enter to open the built-in shell.
- Install PowerShell 7 by running this command:
winget install --id Microsoft.PowerShell --source winget
What does this command do?
WinGet adds PowerShell 7 as a separate application. You can keep Windows PowerShell 5.1 installed.
- Close the built-in shell after installation.
- Open PowerShell 7 through Windows Search.
- Run the version check again.
- Move PowerShell into Project 1 by running this command:
Set-Location '[[PROJECT_ONE_PATH="<path to project 1>"]]'
What does this command do?
PowerShell changes its current location to Project 1. The inherited Taskfile.yml is now available to the doctor task.
- Run the inherited doctor task plus the GitHub authentication check:
task doctor
gh auth status
What do these checks cover?
- The doctor task prints the installed Go, Git, GitHub CLI, Task, golangci-lint, and curl details.
- The authentication check confirms whether the GitHub CLI has an active account.
Choose the tab that matches the doctor output. The pinned results are Go 1.27.1, Task v3.53.1, and golangci-lint v2.13.2.
✔️ All required versions match
That is the first risk cleared. Your inherited toolchain has the pinned builds needed by the service.
- Continue if the output also shows Git plus curl.
- Continue if GitHub CLI reports an authenticated account.
ⓧ One or more versions are older
Update only the tools whose versions miss the project pins. This keeps the working parts of Project 1 unchanged.
- Download go1.27.1.windows-amd64.msi from the official Go downloads page if Go does not report go1.27.1.
- Run the Go installer.
- Use the official WinGet upgrade guide with package ID Task.Task if Task is older than v3.53.1.
- Refresh golangci-lint through its verified package by running these commands:
winget uninstall GolangCI.golangci-lint
winget install GolangCI.golangci-lint
What do these commands do?
The first command removes the older package-manager copy. The second command installs the current verified package.
- Close the current PowerShell window after the updates.
- Open a new PowerShell 7 window through Windows Search.
- Return to Project 1.
ⓧ A command is missing
A missing command means its installer did not add the tool to your system. These installers can request device permission.
- Download go1.27.1.windows-amd64.msi from the official Go downloads page if Go is missing.
- Run the Go installer if you downloaded it.
- Install any missing packaged tools by running the matching command below:
winget install --id Git.Git -e --source winget
winget install --id GitHub.cli --source winget
winget install Task.Task
winget install GolangCI.golangci-lint
What do these commands install?
- The first command installs Git for Windows.
- The second command installs GitHub CLI.
- The third command installs Task.
- The fourth command installs golangci-lint.
- Use the official curl for Windows page if curl.exe is missing from Windows.
- Close the current PowerShell window after installation.
- Open a new PowerShell 7 window through Windows Search.
- Return to Project 1.
GitHub CLI uses an interactive browser flow when no account is authenticated. The flow does not require you to paste a token into this project.
- Start GitHub authentication if the status check showed no active account by running this command:
gh auth login
What does this command do?
GitHub CLI starts an interactive sign-in flow. Complete the browser authorization with the GitHub account you plan to use for the public repository.
The final inherited check proves that the setup path worked. Project 1 may still contain lint findings that belong to its existing code.
- Recheck Project 1 by running these commands:
task doctor
gh auth status
golangci-lint run
How do I read the result?
- Continue if golangci-lint exits successfully.
- Continue if golangci-lint lists findings in Project 1 code that you leave unchanged.
- Use the fallback below only if golangci-lint reports a Go-version mismatch.
🙋♀️ Seeing a Go-version mismatch?
The package-manager build can lag behind the Go release installed on your machine. The pinned source build is the mismatch-only fallback.
- Replace the package-manager copy with the pinned source build by running these commands:
winget uninstall GolangCI.golangci-lint
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2
golangci-lint run
The first command removes the incompatible package. The second command installs the pinned linter from source before the final check runs.
Need help with a machine-specific result? Help me diagnose my Project 1 doctor or golangci-lint output.
Set the operating rules
Your editor changes repository files. Your AI client serves as an explainer plus reviewer during this project.
Use the Windows shell rule
- Use curl.exe for later HTTP calls in PowerShell 7.3 or newer.
- Keep every JSON argument inside single quotes.
- Keep every file argument inside single quotes.
- Replace an angle-bracket placeholder as one complete value.
- Press the Windows key to open Windows Search.
- Type Visual Studio Code.
- Press Enter to try opening Visual Studio Code.
✔️ Visual Studio Code opens
Your editor is ready. Close it for now so the repository can be created from PowerShell.
ⓧ Visual Studio Code is missing
The editor needs to be available before you begin adding supplied project files. Install the current stable Windows build.
- Visit the Visual Studio Code download page.
- Download the current stable Windows installer.
- Run the installer.
- Open Visual Studio Code through Windows Search.
- Close Visual Studio Code after it opens.
The AI boundary keeps repository ownership with you. It also preserves the five-step handler exercise for later.
- Use the AI client already available on your device.
- Use ChatGPT Free in your browser if no AI client is available.
- Start a blank chat.
- Set the project boundary by sending this rule:
Explain only the file or diff I give you. Do not create or edit repository files. Wait for my five numbered steps before drafting handleEcho. Be strict when I later ask for review.
Why set this boundary?
The rule keeps AI in an explanation plus review role. The supplied exercise files remain the source of truth.
The five-step pause protects your ownership of handleEcho. You will type its final implementation from the numbered requirements.
Create the sibling repository
Project 1 remains intact while the new service gets its own history. Creating first-service from Project 1's parent folder places both repositories beside each other.
- Return to the PowerShell 7 window in Project 1.
- Create the sibling repository plus its three dot files by running these commands:
Set-Location ..
New-Item -ItemType Directory first-service
Set-Location first-service
git init -b main
Copy-Item '[[PROJECT_ONE_PATH="<path to project 1>"]]\.gitattributes', '[[PROJECT_ONE_PATH="<path to project 1>"]]\.editorconfig' .
Set-Content .gitignore 'big.json'
What do these commands do?
- The parent-folder move makes first-service a sibling of Project 1.
- The Git command initializes a local repository on main.
- The copy command preserves .gitattributes plus .editorconfig unchanged.
- The final command adds big.json to .gitignore for a later refusal test.
Before you run the final check, do you expect every pinned version to match after the doctor work?
- Verify the new local repository plus its required tool versions by running these commands:
go version
task --version
golangci-lint --version
git status
gh auth status
What should you see?
- Go reports go1.27.1.
- Task reports v3.53.1.
- golangci-lint reports v2.13.2.
- Git reports an empty repository on main with the three dot files untracked.
- GitHub CLI reports an authenticated account.
Your repository is local at this point. No GitHub repository has been created.
🙋♀️ Missing a dot file or wrong branch?
- Confirm the PowerShell prompt ends inside first-service.
- Check that git init -b main completed without an error.
- Check that <path to project 1> points to the folder containing the inherited dot files.
Still stuck? Help me diagnose my first-service repository setup.
✔️ Awesome, I've got everything!
Great. The three dot files now protect the local repository before any contract or service code arrives.
The repository remains local on main.
ⓧ I'd like to double check the full code
.editorconfig is copied unchanged from Project 1.
.gitattributes is copied unchanged from Project 1.
big.json
The single ignore rule keeps the later oversized-body fixture out of Git history.
Your local foundation is ready. Next up, you will publish the API promise before any Go service code.
Publish the Contract and Design Record
Your first-service repository now has a clean local history. Its inherited files give this project the same base as Project 1.
Contract-first development puts promised behavior under review before implementation. This step publishes that promise to GitHub before recording the decisions that guide the service.
In this step, get ready to:
- Publish the API contract as the first commit in a public repository.
- Track the work through issue #1 in a public GitHub Project.
- Commit the supplied design documents before any Go source files.
Publish the contract first
An OpenAPI 3.1.2 document describes the service before its handlers exist. It gives reviewers one place to inspect the routes plus their accepted inputs.
- In Visual Studio Code, switch back to the first-service repository from Step 1.
- Create openapi.yaml at the top level of first-service by using the file sidebar.
- Paste the supplied OpenAPI contract into openapi.yaml without changes.
- Save openapi.yaml.
- Give openapi.yaml to the AI client from Step 1.
- Ask the AI client to explain the routes, request body, responses, and problem schema.
- Read the explanation without asking the AI client to rewrite the file.
What does the contract promise?
The contract promises a health check at GET /healthz. It also promises an echo operation at POST /echo.
Problem details give refused requests a fixed JSON shape. The contract documents HTTP 400 plus HTTP 413 responses for the echo operation.
Contract drift happens when the document disagrees with the registered routes. A published contract makes that disagreement reviewable.
- Commit the contract and publish the public repository by running these commands:
git add .gitattributes .editorconfig .gitignore openapi.yaml
git commit -m "docs: publish the contract so behavior leads code"
gh repo create first-service --public --source=. --push
What do these commands record?
- The first line stages the inherited files plus openapi.yaml.
- The second line creates a commit whose message explains why the contract leads the code.
- The final line creates the public repository from the current folder. It also pushes the main branch.
- Follow the repository URL printed in the terminal.
- Use the repository page to confirm that openapi.yaml appears on main.
- Use the repository history to confirm that the first commit contains the contract before any Go source.
That first public commit already proves your contract led the implementation.
Repository creation failed?
Confirm that the terminal is still inside the local first-service repository. Confirm that your GitHub CLI authentication remains active.
Check whether your account already contains another repository named first-service.
Help me diagnose why my public repository was not created or pushed.
Create the issue and Project
Issue #1 turns the supplied acceptance criteria into visible work. A GitHub Project shows that work moving from planning through review.
- Start a new issue from the repository's issue area.
- Enter Build Your First Go Service, Contract First as the issue title.
- Copy the six acceptance criteria from the supplied docs/requirements.md into the issue body word for word.
- Submit the issue to create issue #1.
- Open your GitHub profile.
- Select Projects.
- Select New project.
- Choose a blank Board layout.
Why use a board?
The board makes the review state visible beside the code. Moving issue #1 through each status leaves a clear delivery story.
Public visibility makes the workflow available to reviewers. That exposure is intentional for this portfolio artifact.
- Open the Project's Settings.
- Set the Project visibility to Public in the Danger zone.
- Configure the Status options as To do, Doing, Review, and Done.
- Return to the first-service repository from earlier.
- Open the repository's Projects area.
- Use Link a project to select your user-owned Project.
- Add issue #1 to the linked Project.
- Place issue #1 in To do.
The implementation work needs its own branch. The contract remains visible on main while the design record develops on contract-first.
- Create the local contract-first branch by running this command:
git switch -c contract-first
What does this branch change?
The command creates contract-first from the current main commit. Your terminal also switches to the new branch.
- Return to the linked Project from earlier.
- Move issue #1 to Doing.
Your work now has a public plan plus an active delivery state.
Project state looks different?
Confirm that the Project belongs to the same GitHub account as the repository. GitHub requires matching ownership for this repository link.
Check the spelling and capitalization of To do, Doing, Review, and Done.
Help me fix my linked GitHub Project or issue status.
Commit the design record
Design records make each engineering choice inspectable before service code appears. The supplied documents capture requirements, trade-offs, architecture, limits, and sources.
- Create the docs folder inside first-service by using the Visual Studio Code file sidebar.
- Create docs/requirements.md inside the new folder.
- Confirm that docs/requirements.md appears in the file sidebar.
- Paste the supplied Requirements document into docs/requirements.md without changes.
- Save docs/requirements.md.
- Confirm that the editor shows six numbered acceptance criteria.
- Confirm that the security mapping appears below the criteria.
What do the requirements record?
Each acceptance criterion names evidence that a reviewer can verify. The security mapping connects input limits plus fixed refusal behavior to published standards.
- Create docs/decisions.md inside the docs folder.
- Paste the supplied Decision Records document into docs/decisions.md without changes.
- Confirm that the editor shows DR-001 through DR-004.
- Save docs/decisions.md.
- Create docs/architecture.md inside the docs folder.
- Paste the supplied Architecture document into docs/architecture.md without changes.
- Confirm that the diagram connects the curl client to the route table plus its handlers.
- Save docs/architecture.md.
What does the design record protect?
Each decision names an alternative, a cost, and a trigger for reconsideration. This structure makes the trade-off reviewable after the original context fades.
The architecture document connects openapi.yaml to contract_test.go plus routeTable. That relationship becomes the drift check later in the project.
The final document records the boundaries of the evidence. It also preserves the sources behind the design.
- Create docs/limits.md inside the docs folder.
- Paste the supplied Limits and Source Map document into docs/limits.md without changes.
- Confirm that the editor shows the placeholder review details, the route comparison limit, the no-CI limit, the security map, and the source links.
- Save docs/limits.md.
- Read all six acceptance criteria in docs/requirements.md.
- Read all four decisions in docs/decisions.md.
- Recheck the 60-minute clock plan in the supplied exercise.
- Commit only the four design documents by running these commands:
git add docs/requirements.md docs/decisions.md docs/architecture.md docs/limits.md
git commit -m "docs: record constraints so implementation choices stay reviewable"
What does this commit preserve?
The first line stages only the four supplied design documents. The second line records why those constraints belong in history before implementation.
- Confirm that the commit output lists all four files under docs.
- Publish the contract-first branch by running this command:
git push -u origin contract-first
What does the push establish?
The push creates the remote contract-first branch. The -u flag links your local branch to that remote branch for future pushes.
Before you check the repository, do you expect to see any Go source files on either branch yet?
- Return to the public first-service repository from earlier to verify the remote branch.
- Select the contract-first branch by using the repository's branch control.
- Open the docs folder on that branch.
You'll see all four rendered design documents. No Go source files appear because the promised behavior plus its decisions still lead the implementation.
- Use the repository history to confirm that the openapi.yaml commit appears before the design-document commit.
- Return to the linked Project to confirm that issue #1 remains in Doing.
You now have a public contract plus a reviewable design record on the remote branch. The repository tells the right story before the first handler exists.
Missing documents on GitHub?
Confirm that the repository page is showing contract-first instead of main. The design documents exist on the new branch.
Check the terminal output from the push for the remote branch name. Check that each supplied document was saved before the commit.
Help me diagnose why my design documents are missing from the remote branch.
Your contract and design record now lead the repository. Next up, you'll assemble the supplied service skeleton and make its missing echo behavior visible.
Assemble and Run the Service Skeleton
Your contract-first branch now carries the API promise plus four design records. The next goal is a runnable Go service skeleton.
A working health route proves the server wiring. An unfinished echo route makes the boundary of this skeleton visible.
In this step, get ready to:
- Initialize the Go module with its supplied problem writers.
- Add the supplied router, server, handler skeleton, Taskfile, and linter configuration.
- Run the local gates. Compare the health route with the unfinished echo route.
Connect the module to its repository
A Go module gives the service a stable import identity. The module path connects that identity to your public GitHub repository.
- Initialize the module by replacing <your GitHub username> in this command before you run it:
go mod init github.com/<your GitHub username>/first-service
What does this command do?
The command creates go.mod in your first-service repository. Its module path identifies where other Go code can find this service.
- Confirm the terminal reports that it created go.mod.
- Confirm go.mod shows your full GitHub module path.
- Confirm its Go directive shows 1.27.1.
The supplied response helpers centralize successful JSON output. They also centralize fixed problem details for refused requests.
- Ask your AI client to explain only writeProblem plus writeJSON from the supplied problem.go.
- Read the explanation without asking the AI client to rewrite the file.
- Create problem.go inside first-service by pasting the supplied code below:
package main
import (
"encoding/json"
"net/http"
)
type problem struct {
Type string `json:"type"`
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail"`
}
func writeProblem(w http.ResponseWriter, status int, detail string) {
w.Header().Set("Content-Type", "application/problem+json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(problem{Type: "about:blank", Title: http.StatusText(status), Status: status, Detail: detail})
}
func writeJSON(w http.ResponseWriter, value any) {
_ = json.NewEncoder(w).Encode(value)
}
How do the response helpers work?
- The problem type defines the fixed fields returned when a request is refused.
- The writeProblem function sets application/problem+json before writing the status plus safe details.
- The writeJSON function encodes successful response values as JSON.
- Save problem.go.
- Confirm the Explorer sidebar lists go.mod plus problem.go inside first-service.
Missing a module or response file?
- Check that the terminal is still inside the local first-service repository.
- Check that the file is named problem.go with lowercase letters.
Ask for help with the missing file.
Assemble the supplied service files
The next files connect route registration to handler functions. The unfinished handleEcho remains part of the supplied skeleton.
- Type the supplied routes.go content into a new routes.go file inside first-service.
- Type the supplied main.go content into a new main.go file inside first-service.
You should now see routes.go plus main.go in the Explorer sidebar.
- Type the supplied handlers.go skeleton into a new handlers.go file inside first-service.
- Confirm handleEcho still calls writeProblem(w, http.StatusNotImplemented, "not implemented").
The handler file should contain a complete health response plus the unfinished echo response.
- Type the supplied Taskfile.yml content into a new Taskfile.yml file inside first-service.
- Type the supplied .golangci.yml content into a new .golangci.yml file inside first-service.
The Explorer sidebar should now list all five supplied skeleton files.
- Save routes.go, main.go, handlers.go, Taskfile.yml, and .golangci.yml.
✔️ Awesome, I've got everything!
Good progress. Your generated module plus six supplied foundation files are now in place.
ⓧ I'd like to double check the full code
Your generated go.mod should point to the public first-service repository under your GitHub username.
- Compare each supplied file with the complete references below.
problem.go
package main
import (
"encoding/json"
"net/http"
)
type problem struct {
Type string `json:"type"`
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail"`
}
func writeProblem(w http.ResponseWriter, status int, detail string) {
w.Header().Set("Content-Type", "application/problem+json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(problem{Type: "about:blank", Title: http.StatusText(status), Status: status, Detail: detail})
}
func writeJSON(w http.ResponseWriter, value any) {
_ = json.NewEncoder(w).Encode(value)
}
What should match in problem.go?
This file contains the supplied problem shape plus both response helpers. Keep its fixed media type plus error fields unchanged.
routes.go
package main
import "net/http"
type route struct {
Method string
Path string
Handler http.HandlerFunc
}
var routeTable = []route{
{"GET", "/healthz", handleHealthz},
{"POST", "/echo", handleEcho},
}
func newRouter() http.Handler {
mux := http.NewServeMux()
for _, route := range routeTable {
mux.HandleFunc(route.Method+" "+route.Path, route.Handler)
}
return mux
}
How does routes.go work?
- The route type stores one method-path-handler registration.
- The routeTable value lists the two routes promised by the contract.
- The newRouter function registers each route with http.ServeMux.
main.go
package main
import (
"log"
"net/http"
)
func main() {
log.Println("listening on http://localhost:8080")
if err := http.ListenAndServe(":8080", newRouter()); err != nil {
log.Fatal(err)
}
}
How does main.go work?
The main function starts the server on port 8080. A startup message gives you a visible signal that the process is listening.
handlers.go
package main
import "net/http"
type echo struct {
Message string `json:"message"`
}
func handleHealthz(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
writeJSON(w, map[string]string{"status": "ok"})
}
func handleEcho(w http.ResponseWriter, _ *http.Request) {
writeProblem(w, http.StatusNotImplemented, "not implemented")
}
How does the handler skeleton work?
- The echo type represents the documented message shape.
- The handleHealthz function returns the fixed healthy response.
- The handleEcho function deliberately reports unfinished behavior.
Taskfile.yml
version: '3'
tasks:
doctor:
desc: Print required tool versions
cmds:
- go version
- git --version
- gh auth status
- task --version
- golangci-lint --version
- curl --version
test:
desc: Run all Go tests
cmd: go test ./...
lint:
desc: Run golangci-lint
cmd: golangci-lint run
run:
desc: Run the local service
cmd: go run .
What does the Taskfile provide?
The four tasks give the repository consistent commands for tool checks, tests, linting, and local execution. Reviewers can use the same entry points that you use.
.golangci.yml
version: "2"
What does the linter configuration do?
The version field selects the supplied v2 configuration format. It keeps the repository compatible with the verified linter build.
Seeing a mismatch in the supplied files?
- Check every filename for the exact capitalization shown in the references.
- Check that handlers.go still contains the intentional handleEcho skeleton.
- Check that .golangci.yml starts with a dot.
Ask for help comparing the files.
- Commit the generated module plus supplied foundation by running:
git add go.mod problem.go routes.go main.go handlers.go Taskfile.yml .golangci.yml
git commit -m "feat: add the service skeleton so missing behavior is visible"
What does this commit record?
The commit records the generated module plus the supplied service foundation as one baseline. Its message explains why the unfinished handler stays visible.
The commit summary should report a new commit on contract-first.
Commit did not complete?
- Check that every named file appears in the first-service repository.
- Check that the terminal is still on contract-first.
Ask for help with the foundation commit.
Run the incomplete skeleton
The supplied Task commands turn tool checks plus linting into local gates. Running them before the server separates environment problems from service behavior.
- Run the doctor plus linter gates with these commands:
task doctor
task lint
What do the local gates prove?
- The doctor task prints the tool versions plus GitHub authentication state.
- The lint task checks the supplied Go files with golangci-lint.
- Confirm the doctor output lists each required tool.
- Confirm the lint command finishes without a reported finding.
Local gate failed?
- Check that Taskfile.yml uses spaces for YAML indentation.
- Check the filename .golangci.yml if the linter reports a configuration problem.
- Compare the reported tool version with the versions you verified earlier.
Ask for help with the failed gate.
The server keeps its terminal session busy while it listens for requests. Leave that session running until both routes have been checked.
- Start the local service by running:
task run
What starts when this runs?
The task runs the Go program in the current repository. The program builds routeTable into a router before listening on port 8080.
- Confirm the terminal shows listening on http://localhost:8080.
Server did not start?
- Check the first reported filename for a typing difference from the supplied reference.
- Stop any existing process using port 8080 before trying again.
Ask for help starting the server.
- Start a second PowerShell 7.3 or newer session from the Windows search bar.
Before you send both requests, which route do you expect to show complete behavior?
- Send one health request plus one echo request from the second terminal by running:
curl.exe http://localhost:8080/healthz
curl.exe -w '\n%{http_code}\n' -H 'Content-Type: application/json' -d '{"message":"hello"}' http://localhost:8080/echo
What do these requests check?
- The first request checks the registered /healthz route.
- The second request sends the documented message shape to /echo.
- The write-out option prints the echo response status after its body.
You will see {"status":"ok"} from the health route. You will see the fixed problem body followed by 501 from the echo route.
That is the runnable baseline working. The router serves health while echo remains visibly unfinished.
Requests did not return both responses?
- Check that task run is still running in the first terminal.
- Check that both request URLs use port 8080.
- Use curl.exe from PowerShell instead of the older Windows PowerShell alias.
Ask for help with the route checks.
- Return to the first terminal. Stop the service with Ctrl+C.
Your skeleton now proves the server wiring plus the missing echo behavior. Next, you will replace that deliberate 501 with a bounded strict handler.
Secure the Echo Handler
Your service already proves that the router reaches both handlers. The planned 501 response isolates one gap: handleEcho still needs its real behavior.
An AI draft gives you a comparison point. Your own implementation turns the published contract into a bounded HTTP request path.
In this step, get ready to:
- Review one AI draft without adding it to the repository.
- Secure handleEcho with the five-step request boundary.
- Prove that a valid echo request returns HTTP 200.
Review one AI draft
The five security steps define how every echo body moves from an untrusted request to a safe response. The AI draft lets you inspect one possible implementation before you write your own.
- Switch back to the AI client from earlier.
- Request one draft by pasting this prompt:
Draft only handleEcho.
Use this existing type:
type echo struct {
Message string `json:"message"`
}
Use these existing helper signatures:
func writeProblem(w http.ResponseWriter, status int, detail string)
func writeJSON(w http.ResponseWriter, value any)
Use this import block:
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
)
Follow these security steps exactly:
1. Read through http.MaxBytesReader with a 4096-byte limit.
2. Handle *http.MaxBytesError with errors.As before handling other read errors.
3. Decode with json.NewDecoder and DisallowUnknownFields.
4. Reject an empty in.Message.
5. Set application/json before calling writeJSON.
Return only handleEcho.
Why Review a Draft First?
The draft gives you something concrete to challenge. Keeping it outside the repository preserves a clear line between AI-assisted explanation and learner-written code.
- Read the complete draft.
- Compare its sequence with the five numbered steps.
- Note any differences you find.
- Close the draft.
The draft stays uncommitted. You now have the context to type each security decision yourself.
Type the secure handler
The handler is the service's security boundary. Its fixed problem details keep parser internals out of RFC 9457 responses.
- Return to handlers.go in your editor.
- Find the current single-line import:
import "net/http"
Why Does the Import Change?
The 501 skeleton only needs the HTTP package. The secure handler also reads bytes safely, classifies read errors, and decodes the request body.
- Replace the single-line import by typing this block yourself:
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
)
What Do These Packages Support?
- The bytes package turns the stored request bytes into a reader.
- The encoding/json package performs strict JSON decoding.
- The errors package identifies the body-limit error.
- The io package reads the bounded body.
- The net/http package supplies request types, status codes, and the bounded reader.
- Find the intentional handleEcho skeleton:
func handleEcho(w http.ResponseWriter, _ *http.Request) {
writeProblem(w, http.StatusNotImplemented, "not implemented")
}
What Does the Skeleton Prove?
This function deliberately returns the fixed 501 response you observed earlier. Replacing this isolated function adds the promised behavior without changing the working route table.
- Replace the skeleton by typing this function yourself:
func handleEcho(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 4096))
var mbe *http.MaxBytesError
if errors.As(err, &mbe) {
writeProblem(w, http.StatusRequestEntityTooLarge, "body over 4096 bytes")
return
}
if err != nil {
writeProblem(w, http.StatusBadRequest, "body could not be read")
return
}
var in echo
dec := json.NewDecoder(bytes.NewReader(body))
dec.DisallowUnknownFields()
if err := dec.Decode(&in); err != nil {
writeProblem(w, http.StatusBadRequest, "body is not the documented shape")
return
}
if in.Message == "" {
writeProblem(w, http.StatusBadRequest, "message is required")
return
}
w.Header().Set("Content-Type", "application/json")
writeJSON(w, in)
}
What Does This Handler Do?
- The http.MaxBytesReader call limits the whole request body to 4096 bytes.
- The errors.As check identifies *http.MaxBytesError. That path returns the fixed 413 problem.
- The decoder uses DisallowUnknownFields. Extra fields therefore return the fixed 400 problem.
- The in.Message check enforces the required field.
- The success path sets application/json. The writeJSON helper returns the accepted message.
✔️ Awesome, I've got everything!
Your handlers.go file now contains the complete bounded echo handler.
ⓧ I'd like to double check the full code
package main
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
)
type echo struct {
Message string `json:"message"`
}
func handleHealthz(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
writeJSON(w, map[string]string{"status": "ok"})
}
func handleEcho(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 4096))
var mbe *http.MaxBytesError
if errors.As(err, &mbe) {
writeProblem(w, http.StatusRequestEntityTooLarge, "body over 4096 bytes")
return
}
if err != nil {
writeProblem(w, http.StatusBadRequest, "body could not be read")
return
}
var in echo
dec := json.NewDecoder(bytes.NewReader(body))
dec.DisallowUnknownFields()
if err := dec.Decode(&in); err != nil {
writeProblem(w, http.StatusBadRequest, "body is not the documented shape")
return
}
if in.Message == "" {
writeProblem(w, http.StatusBadRequest, "message is required")
return
}
w.Header().Set("Content-Type", "application/json")
writeJSON(w, in)
}
- Save handlers.go.
- Format the file by running the commands below:
gofmt -w handlers.go
task lint
git add handlers.go
git commit -m "feat: enforce the echo contract so invalid bodies fail safely"
What Do These Commands Prove?
- The gofmt command applies Go's standard formatting.
- The task command runs the configured lint gate.
- The first git command stages only handlers.go.
- The final git command records the security boundary in its own commit.
🙋♀️ Does the Lint Gate Fail?
- Compare every imported package with the full-file reference.
- Check that the request parameter is named r inside handleEcho.
- Ask for help with the exact lint output.
Before you inspect the commit, which file do you expect its summary to name? Hold that prediction for the check.
- Inspect the newest commit by running:
git show --stat HEAD
What Does This Check Show?
The command displays the newest commit and its file summary. This gives you direct evidence that the handler commit contains only the intended file.
You should see only handlers.go in the commit summary. That is the ownership proof for your learner-written handler.
Prove the happy path
The lint gate proves the code is structurally sound. A live request now proves that the handler accepts the documented message.
- Return to the first-service terminal from earlier.
- Start the service by running:
task run
What Happens While the Service Runs?
The command starts the service on http://localhost:8080. This terminal remains occupied until you stop the process.
Before you send the requests, do you expect the echo route to keep returning 501 or return the submitted message? Hold that prediction for the live check.
- Switch back to the second terminal from the earlier 501 check.
- Send the health request and valid echo request by running:
curl.exe http://localhost:8080/healthz
curl.exe -w '\n%{http_code}\n' -H 'Content-Type: application/json' -d '{"message":"hello"}' http://localhost:8080/echo
What Do These Requests Check?
- The first request confirms that the existing health route still responds.
- The second request sends the documented echo body.
- The write-out option prints the echo response's HTTP status code after its body.
You should see {"status":"ok"} from the health route. You should then see {"message":"hello"} with HTTP 200 from the echo route.
You have replaced the planned 501 with a bounded echo path that now accepts valid JSON. The published success behavior is live.
Still Seeing the 501 Response?
- Stop any older service process with Ctrl+C.
- Confirm that you saved handlers.go.
- Start the service again from the first-service folder.
- Ask for help comparing the live response with your handler.
- Copy both outputs into a temporary note for the README evidence.
- Switch back to the service terminal.
- Stop the service with Ctrl+C.
Your secure handler now passes lint and serves the documented happy path. Next, you will challenge it with contract drift and deliberately invalid bodies.
Prove Drift and Refusals
Your secure Go handler now passes the happy path. A successful echo response still leaves the route contract and refusal behavior unproven.
This step creates contract drift in both directions so you can watch the tests catch each mismatch. You will also send three invalid bodies to prove the service refuses them safely.
In this step, get ready to:
- Add the supplied contract test with its pinned YAML dependency.
- Capture both route-drift failures and all three refusal responses.
- Publish the measured evidence in an open pull request.
Add the contract test
The supplied test compares the routes in openapi.yaml with the routes in routeTable. It also checks the successful echo path plus the three refusal paths.
- Ask your AI client to explain the route comparison in the supplied contract_test.go file.
- Ask your AI client to explain the valid echo test.
- Ask your AI client to explain the refusal table with its health checks.
- In the Explorer sidebar of Visual Studio Code, create contract_test.go inside first-service by pasting the supplied file unchanged.
- Save contract_test.go.
- Add the pinned YAML dependency and run the tests by entering:
go get go.yaml.in/yaml/v3@v3.0.5
task test
What do these commands do?
- The first command adds go.yaml.in/yaml/v3 v3.0.5 to go.mod. It also generates go.sum.
- The second command runs every Go test in the service.
- The route comparison should pass because the document and route table currently contain the same two routes.
You should see a passing test run. The new test now guards route parity plus the echo behavior you proved manually.
Dependency or test failing?
Confirm that contract_test.go matches the supplied exercise exactly. Confirm that openapi.yaml still contains only the health and echo paths.
Ask for focused help with this dependency or test failure.
- Commit the supplied test with the generated dependency files by running:
git add contract_test.go go.mod go.sum
git commit -m "test: compare both sources so route drift names the missing side"
What enters this commit?
This becomes the fifth ordered commit. It contains the unchanged supplied test plus the dependency metadata generated by Go.
Use the comparison below if you want to check the supplied test before creating the deliberate failures.
✔️ Awesome, I've got everything!
Great. Confirm that contract_test.go is saved before continuing.
ⓧ I'd like to double check the full code
package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"sort"
"strings"
"testing"
"go.yaml.in/yaml/v3"
)
type contractDocument struct {
Paths map[string]map[string]any `yaml:"paths"`
}
func TestContractMatchesRoutes(t *testing.T) {
data, err := os.ReadFile("openapi.yaml")
if err != nil {
t.Fatal(err)
}
var document contractDocument
if err := yaml.Unmarshal(data, &document); err != nil {
t.Fatal(err)
}
documented := make(map[string]struct{})
for path, pathItem := range document.Paths {
for method := range pathItem {
switch method {
case "delete", "get", "head", "options", "patch", "post", "put", "trace":
documented[strings.ToUpper(method)+" "+path] = struct{}{}
}
}
}
registered := make(map[string]struct{})
for _, route := range routeTable {
registered[route.Method+" "+route.Path] = struct{}{}
}
for _, key := range difference(registered, documented) {
t.Errorf("registered but not documented: %s", key)
}
for _, key := range difference(documented, registered) {
t.Errorf("documented but not registered: %s", key)
}
}
func TestEcho(t *testing.T) {
router := newRouter()
recorder := postEcho(t, router, `{"message":"hello"}`)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
}
if got := recorder.Header().Get("Content-Type"); got != "application/json" {
t.Fatalf("Content-Type = %q, want application/json", got)
}
var got echo
if err := json.NewDecoder(recorder.Body).Decode(&got); err != nil {
t.Fatal(err)
}
if got.Message != "hello" {
t.Fatalf("message = %q, want hello", got.Message)
}
}
func TestEchoRefusalsKeepHealthLive(t *testing.T) {
router := newRouter()
tests := []struct {
name string
body string
status int
detail string
}{
{
name: "body too large",
body: `{"message":"` + strings.Repeat("a", 5000) + `"}`,
status: http.StatusRequestEntityTooLarge,
detail: "body over 4096 bytes",
},
{
name: "extra field",
body: `{"message":"hi","extra":1}`,
status: http.StatusBadRequest,
detail: "body is not the documented shape",
},
{
name: "empty body",
body: "",
status: http.StatusBadRequest,
detail: "body is not the documented shape",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
recorder := postEcho(t, router, test.body)
if recorder.Code != test.status {
t.Fatalf("status = %d, want %d", recorder.Code, test.status)
}
if got := recorder.Header().Get("Content-Type"); got != "application/problem+json" {
t.Fatalf("Content-Type = %q, want application/problem+json", got)
}
var got problem
if err := json.NewDecoder(recorder.Body).Decode(&got); err != nil {
t.Fatal(err)
}
if got.Detail != test.detail {
t.Fatalf("detail = %q, want %q", got.Detail, test.detail)
}
assertHealth(t, router)
})
}
}
func postEcho(t *testing.T, router http.Handler, body string) *httptest.ResponseRecorder {
t.Helper()
request := httptest.NewRequest(http.MethodPost, "/echo", strings.NewReader(body))
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
return recorder
}
func assertHealth(t *testing.T, router http.Handler) {
t.Helper()
request := httptest.NewRequest(http.MethodGet, "/healthz", nil)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("health status = %d, want %d", recorder.Code, http.StatusOK)
}
}
func difference(left, right map[string]struct{}) []string {
var result []string
for key := range left {
if _, ok := right[key]; !ok {
result = append(result, key)
}
}
sort.Strings(result)
return result
}
What should match?
The full file contains three core tests plus shared helpers. Keep it unchanged so your evidence measures the supplied test design.
Prove drift and body refusals
A two-way comparison catches code-only routes and document-only routes. You will create each mismatch temporarily so the failure names the missing side.
- In routes.go, find the two entries inside routeTable.
- Add this temporary route below the existing entries:
{"GET", "/undocumented", handleHealthz},
This mismatch is intentional
The router now registers GET /undocumented. The OpenAPI document has no matching path.
- Save routes.go.
Before you run the test, do you expect it to name the route as registered or documented? Keep that prediction in mind.
- Reveal the first drift result by running:
task test
The registered route fails
You should see FAIL plus registered but not documented: GET /undocumented. This proves that a Go-only route cannot hide from the contract test.
- Capture the complete failing output in your temporary evidence note.
- Remove the temporary GET /undocumented entry from routes.go.
- Save routes.go.
- Restore route parity by running:
task test
Route parity is restored
The test should pass again. The route table once more contains only GET /healthz and POST /echo.
- In openapi.yaml, find the paths section.
- Add this temporary path below paths::
/documented-only:
get: {}
The opposite mismatch is intentional
The document now promises GET /documented-only. The Go route table has no matching registration.
- Save openapi.yaml.
Before you run the test, do you expect the message to reverse the missing side? Keep that prediction in mind.
- Reveal the second drift result by running:
task test
The documented route fails
You should see FAIL plus documented but not registered: GET /documented-only. This proves that a document-only route also fails by name.
- Capture the complete failing output in your temporary evidence note.
- Remove the temporary /documented-only block from openapi.yaml.
- Save openapi.yaml.
You have now seen the same test protect both sources. Run it once more to prove the final files agree.
- Confirm the final route contract by running:
task test
Both sources agree again
The final test should pass. Your captured failures remain as evidence while the committed route table and contract return to their correct state.
The automated table also exercises three fixed problem details responses. Manual requests now prove that the running service remains healthy after each refusal.
- Start the service in your first terminal by running:
task run
Keep this process running
The service takes over this terminal while it listens on http://localhost:8080. Use the second terminal for the fixture and requests.
- Switch back to the second PowerShell terminal from the handler check.
- Create big.json with a 5000-letter message by running:
'{'"'"'message'"'"':'"'"''"'"' + ('"'"'a'"'"' * 5000) + '"'"''"'"'}' | Set-Content -NoNewline big.json
What does this fixture contain?
The command writes one JSON object with a message that exceeds the handler's 4096-byte limit. The existing .gitignore entry keeps big.json outside Git.
Before you send these requests, do you expect the health route to survive every refusal? Keep that prediction in mind.
- Send the oversized body by running the first request below.
- Check health by running the next request.
- Send the extra-field body by running the third request.
- Check health again by running the next request.
- Send the empty body by running the fifth request.
- Check health one final time by running the last request:
curl.exe -w '\n%{http_code}\n' -H 'Content-Type: application/json' --data-binary '@big.json' http://localhost:8080/echo
curl.exe http://localhost:8080/healthz
curl.exe -w '\n%{http_code}\n' -H 'Content-Type: application/json' -d '{"message":"hi","extra":1}' http://localhost:8080/echo
curl.exe http://localhost:8080/healthz
curl.exe -w '\n%{http_code}\n' -H 'Content-Type: application/json' -d '' http://localhost:8080/echo
curl.exe http://localhost:8080/healthz
What should the evidence show?
- The oversized request returns HTTP 413 with the fixed detail body over 4096 bytes.
- The extra-field request returns HTTP 400 with the fixed detail body is not the documented shape.
- The empty request returns HTTP 400 with the same fixed shape detail.
- Every later health request returns {"status":"ok"}.
🙋♀️ Refusal output missing?
Confirm that task run is still active in the first terminal. Confirm that every request targets http://localhost:8080.
Ask for help with these refusal requests.
- Capture all six responses exactly in your temporary evidence note.
- Stop the service in the first terminal with Ctrl+C.
Publish the measured evidence
Measured evidence lets a reviewer verify each claim without rerunning your deliberate failures. The final commit records those outputs beside the decisions that explain them.
- In the Explorer sidebar, create README.md inside first-service by pasting the supplied README skeleton.
- Save README.md.
You should now see the supplied README sections in the editor. Fill them with measured evidence from this run.
- Replace the contract-first prompt with your own short explanation of DR-001.
- Paste every captured terminal output into its matching Evidence line exactly as it appeared.
- Save README.md.
- Open the Markdown preview from the editor toolbar to confirm the evidence is readable.
You should see the final test output plus both named drift failures. You should also see the valid calls plus all three refusal and health pairs.
- Complete the measurement lines with the real date, Windows as the operating system, and your actual total minutes.
- Complete the How I used AI section with your actual client name plus the explanation-only rule.
- Record the available reviewer name in the review attribution line.
- Save README.md.
- Open the Markdown preview again to confirm the measurement and attribution lines are complete.
The README now separates the planned 59-minute estimate from your measured total. That distinction keeps the evidence honest.
- Complete the Talk track section with a script that starts with DR-001.
- Name route-only comparison as the first documented limit.
- Name drift detection during local tests as the second documented limit.
- State generation plus CI as the real-team answer.
- Save README.md.
Keep the evidence measured
Copy output from this run exactly. Record your actual minutes instead of repeating the clock estimate.
- In docs/limits.md, replace the reviewed-date placeholder with the real date.
- Set the recorded operating system to Windows.
- Save docs/limits.md.
The source map and documented limits should remain unchanged. Only the reviewed date and operating system lines gain measured values.
- In the Explorer sidebar, create .github/pull_request_template.md inside first-service by pasting the supplied template.
- Save .github/pull_request_template.md.
You should now see README.md plus .github/pull_request_template.md in the Explorer. The template should end with Closes #1.
Before you run the final local gate, do you expect every test and lint check to stay green after the evidence edits? Keep that prediction in mind.
- Run the final local gate by entering:
task test
task lint
git status
What should the final gate show?
The tests and linter should exit successfully. Git should list README.md, docs/limits.md, and .github/pull_request_template.md as the evidence changes.
The ignored big.json file stays out of the status output.
- Create the sixth ordered commit by running:
git add README.md docs/limits.md .github/pull_request_template.md
git commit -m "docs: publish measured evidence so reviewers can verify the claims"
What does commit six prove?
This commit records the exact evidence plus its review context. The earlier code and test commits remain separate in the history.
The next command pushes your branch to GitHub. It then opens the pull request workflow in your browser.
- Push contract-first and start the pull request by running:
git push -u origin contract-first
gh pr create --web
What happens next?
The push sets the remote tracking branch. The pull request command opens the browser form with the supplied template.
- Complete every pull request template section with the evidence from README.md.
- Keep Closes #1 in the pull request body.
- Create the pull request from contract-first into main.
- Move issue #1's linked Project card to Review.
Before the final history check, predict how many ordered commits you should see. Keep that number in mind.
- Check the ordered commit history by running:
git log --oneline
Check the commit story
You should see six ordered commits. Their sequence should move from the contract to design records, service skeleton, secure handler, contract tests, and measured evidence.
- Confirm the local repository is clean by running:
git status
Check the final local state
Git should report a clean branch. The local big.json fixture remains available while its ignore rule keeps it untracked.
- Return to the pull request page in your browser.
- Confirm that the pull request is open from contract-first.
- Confirm that the body still contains Closes #1.
- Return to the linked public Project.
- Confirm that issue #1 is in Review.
That is a strong evidence trail. Your branch now shows the promised routes, deliberate failures, safe refusals, measured outputs, and six ordered commits.
Secret mission
Complete the Review and Publish v0.1.0
Turn your green branch into a reviewed release that anyone can inspect. Defend the contract-first choices in a two-minute recording. Finish with a public release linked from Project 1.
Clean Up Your Resources
Clean Up Your Resources
Your project remains at $0 because no cloud compute is running. Choose whether to keep the public evidence, pause local work, or delete every resource.
Resources you used:
- Your local first-service folder, including the Go service and the ignored big.json test fixture.
- Your public GitHub repository, including issue #1, the merged pull request, the attributed review, and the commit history.
- Your linked public GitHub Project with its issue card in Done.
- Your v0.1.0 release with the two-minute recording attached.
- Your committed Project 1 portfolio-index line linking to the repository and release.
Keep everything running
Your review history and release remain available without ongoing compute costs. This option keeps the finished work ready to revisit.
- Keep the public first-service repository with its issue, merged pull request, review, and commits.
- Keep the linked public GitHub Project with its card in Done.
- Keep the v0.1.0 release with the recording attached.
- Keep the Project 1 portfolio-index line linking to the finished work.
- Retain the local first-service folder if you want to run or extend the service later.
Pause - I'll come back to this later
Pausing stops the local service process. Your files and public evidence remain available.
- Press Ctrl+C in any terminal where the local service is still running.
- Confirm that the terminal returns to its command prompt.
- Leave the local first-service folder beside Project 1.
- Leave the GitHub repository, linked Project, release, recording, and Project 1 portfolio-index line unchanged.
Delete - I don't want to use this again
Deletion removes the public evidence that took several steps to build. Copies saved outside these resources stay untouched.
Remove the Portfolio Link
- Return to Project 1's portfolio index in your editor.
- Delete the one-line link to the public first-service repository and its v0.1.0 release.
- Commit the portfolio-index removal in Project 1.
- Push the Project 1 commit to its existing remote.
- Confirm that the public Project 1 portfolio index no longer shows the link.
Remove the Release and Recording
- Open the first-service repository's Releases page on GitHub.
- Open the v0.1.0 release.
- Use the release deletion control to remove the release and its attached recording.
- Confirm that v0.1.0 no longer appears on the repository's Releases page.
Delete the Linked Project
- Open Projects from your GitHub profile.
- Select the linked public Project that contains the Done card for issue #1.
- Open the Project's Settings page.
- Use the permanent deletion control at the bottom of the settings page.
- Confirm that the Project no longer appears on your profile's Projects page.
Delete the Public Repository
- Return to the public first-service repository on GitHub.
- Open the repository's Settings page.
- Scroll to the permanent repository deletion controls at the bottom of the page.
- Use the deletion control for the first-service repository.
- Confirm that your GitHub profile no longer lists the repository.
Delete the Local Folder
- Press Ctrl+C in any terminal where the local service is still running.
- Use Windows search to return to File Explorer.
- Navigate to the folder that contains Project 1.
- Select the first-service folder beside Project 1.
- Press the Delete key to remove the folder.
- Confirm that first-service no longer appears beside Project 1.
Nice Work!
Nice Work!
Mission complete! You shipped a reviewed Go service in a public repository.
The public first-service repository now shows that its API document led the implementation. Its tests expose contract drift in both directions.
You've learned how to:
- Published an OpenAPI 3.1.2 contract before service code. You recorded six acceptance criteria before implementation. You also documented four engineering trade-offs as decision records.
- Built a secure Go 1.27 service with net/http. Its 4096-byte limit bounds every request body. Strict JSON decoding rejects undocumented fields. Fixed RFC 9457 problem details keep parser internals out of client responses.
- Produced two-way route-drift evidence. Two deliberate failures made task test name each missing route. You captured the 413 oversized-body refusal. You also captured both 400 refusal paths. The /healthz route still answered after every refusal.
- Secret Mission: Completed an attributed strict review. You answered every finding with a reason. The merge preserved the ordered commit history. You published a GitHub release tagged v0.1.0 with the two-minute recording. Your talk track defended generation plus CI as the real-team answer. Project 1 now links to the finished portfolio artifact.
Ready to quiz yourself?