Harden a Node.js Checkout Module

Harden a Node.js checkout module with a reviewed AI workflow.

Introduction

30 Second Summary

A checkout total can look convincing for one ordinary basket while quietly mishandling rounding or invalid orders. Those hidden edges are where a quick code change can become a production bug.

In this project, you will harden a Node.js checkout module until it returns validated integer-cent totals. You will use failing boundary tests to guide a permission-gated Google Antigravity workflow through planning, implementation, verification, and review.

What You'll Build

You will run one test command and see all nine checkout cases pass before reviewing a focused source diff that changed only the approved calculator.

By the end of this project, you'll have:

  • An integer-safe checkout calculator that totals multiple order lines in cents while rejecting malformed orders.
  • A boundary test suite that shows nine passing cases for valid totals, discount rounding, input validation, and mutation safety.
  • A review-first AI workflow captured in SPEC.md, AGENTS.md, and AI_WORKFLOW.md for planning bounded changes and reviewing the resulting diff.
  • Secret Mission: Add a SAVE20 promotion with a minimum-spend rule while keeping the approved patch inside the calculator contract and tests.

Are there any prerequisites?

You need intermediate JavaScript experience on a Mac running macOS 12 or later plus a personal Google Account. The project includes all Node.js and Google Antigravity setup.

Before We Start

Before any hands-on work, commit to hardening one Node.js checkout module. A bounded patch keeps the tradeoffs visible so you can see where AI speed helps and where engineering control matters.

Set Up the Controlled AI Workspace

A reviewed patch is only trustworthy when everyone runs it against the same runtime. The agent also needs a permission gate before it can touch your files.

You will confirm the Node.js runtime inside Visual Studio Code. You will verify Git as the baseline tool.

You will install Google Antigravity with its approval gate intact. One trusted folder keeps the agent scoped to this project.

In this step, get ready to:
  • Verify the local development toolchain.
  • Install the permission-gated AI agent.
  • Create one trusted project workspace.
Verify Node.js and Git

Node.js v26.13.0 LTS runs the test suite later in this project. Git preserves the baseline that you will compare against the AI-generated patch.

  • Press Cmd+Space on macOS to open Spotlight.
  • Type Visual Studio Code into Spotlight.
  • Press Enter to start Visual Studio Code.
  • Click View in the top menu bar.
  • Click Terminal to open the integrated terminal.
  • Check the runtime and version-control tool by running these commands:
node --version
git --version

What Are These Checks Proving?

  • The first line confirms which Node.js runtime executes your tests.
  • The second line confirms Git is available for tracking the approved baseline.

✔️ I see v26.13.0

Your runtime is pinned to v26.13.0 LTS. That gives every later test the expected Node.js environment.

ⓧ I see another version

A different Node.js release can produce different runtime behavior. Install the pinned LTS release before continuing.

  • Open the official Node.js download page.
  • Download Node.js v26.13.0 LTS for macOS.
  • Run the downloaded installer.
  • Return to the Visual Studio Code terminal from earlier.
  • Repeat the Node.js check by running:
node --version

Why Check Again?

The second check confirms your terminal now resolves to the pinned Node.js installation. You should see v26.13.0.

ⓧ Command not found

Your terminal cannot find a Node.js installation. The official installer adds the runtime that this project requires.

  • Open the official Node.js download page.
  • Download Node.js v26.13.0 LTS for macOS.
  • Run the downloaded installer.
  • Return to the Visual Studio Code terminal from earlier.
  • Repeat the Node.js check by running:
node --version

Why Check Again?

The second check proves the installer added Node.js to your terminal environment. You should see v26.13.0.

The Git line should include an installed version number. That confirms the repository tools are ready for the staged baseline in the next step.

Still Seeing the Wrong Node.js Version?

  • Close the current terminal if it still reports the previous Node.js version.
  • Open a fresh terminal so the shell reloads its command path.

Ask for help if the fresh terminal still cannot find the pinned runtime: help me diagnose why VS Code is not using Node.js v26.13.0 after installation

Install Google Antigravity CLI

Antigravity requires macOS 12 or later. Its default permission mode pauses before actions that can change your workspace.

  • Check whether Antigravity CLI is already available by running:
agy --version

What Does This Check Prove?

The version check confirms whether your shell can find Antigravity CLI. The pinned release for this project is v1.4.10.

✔️ I see v1.4.10

Antigravity CLI is ready. Its pinned version gives you the planning and review controls used throughout this project.

ⓧ I see an older version

  • Install Antigravity CLI v1.4.10 by running:
curl -fsSL https://antigravity.google/cli/install.sh | bash

What Does the Installer Do?

The official installation script downloads Antigravity CLI for macOS. Its default binary location is ~/.local/bin/agy.

  • Confirm the installed version by running:
agy --version

What Should You See?

The terminal should print Antigravity CLI v1.4.10. This confirms the pinned release is active.

ⓧ Command not found

  • Install Antigravity CLI v1.4.10 by running:
curl -fsSL https://antigravity.google/cli/install.sh | bash

What Does the Installer Do?

The official installation script downloads Antigravity CLI for macOS. Its default binary location is ~/.local/bin/agy.

  • Confirm the installed version by running:
agy --version

What Should You See?

The terminal should print Antigravity CLI v1.4.10. This confirms your shell can find the new installation.

Antigravity Command Still Unavailable?

  • Close the terminal that was open during installation.
  • Open a fresh Visual Studio Code terminal so the shell reloads its command path.
  • Check the installation output for the default binary location ~/.local/bin/agy.

Ask for help if a fresh terminal still cannot find the CLI: help me diagnose why agy is unavailable after the macOS installation script completed

Create and trust the workspace

A bounded workspace limits the files available to the agent. Keeping request-review preserves your approval gate for later edits.

  • Click File in the Visual Studio Code menu bar.
  • Click Open Folder.

You should see the macOS file chooser. This is where you create the dedicated workspace.

  • Navigate to your Mac's Desktop.
  • Click New Folder.
  • Enter ai-checkout-hardening as the folder name.
  • Press Return to create the folder.

The new ai-checkout-hardening folder should now be selected in the file chooser.

  • Click Open to use the folder as your workspace.

The Explorer sidebar should show ai-checkout-hardening as the open workspace. The empty folder is now ready for the project files.

The first Antigravity launch opens your default browser when no saved token exists. You will sign in with a personal Google Account before returning to the terminal interface.

  • Click Terminal in the Visual Studio Code menu bar.
  • Click New Terminal to start a terminal inside ai-checkout-hardening.
  • Launch Antigravity in the current workspace by running:
agy

What Does This Command Do?

Antigravity starts its terminal interface in the current directory. It opens your default browser for authentication when no saved profile exists.

  • Complete the browser sign-in with your personal Google Account.
  • Return to the Antigravity terminal interface.
  • Trust only the ai-checkout-hardening project folder.
  • Keep the permission mode set to request-review.

Why Keep Request Review?

The request-review mode prompts before write actions. It also prompts before Bash, web, or MCP actions.

Those pauses make every meaningful agent action visible before it changes your workspace.

  • Click Terminal in the Visual Studio Code menu bar.
  • Click New Terminal to open a separate verification terminal.

Before you run this check, picture the three version lines you expect to see.

  • Verify the complete toolchain by running these commands:
node --version
git --version
agy --version

What Should You See?

  • The Node.js line should read v26.13.0.
  • The Git line should print an installed version.
  • The Antigravity line should report v1.4.10.

You have the controlled workspace in place. Your runtime is pinned to the project version.

The agent is signed in with a review gate. Only ai-checkout-hardening is trusted for the work ahead.

✔️ Awesome, I've got everything!

The local ai-checkout-hardening folder is open in Visual Studio Code. Antigravity trusts only this folder.

The permission mode is request-review. All three terminal tools report the expected state.

ⓧ I'd like to double check the workspace

  • Confirm the Explorer sidebar shows ai-checkout-hardening.
  • Confirm Node.js reports v26.13.0.
  • Confirm Git reports an installed version.
  • Confirm Antigravity CLI reports v1.4.10.
  • Return to the Antigravity terminal interface.
  • Confirm the trusted workspace is limited to ai-checkout-hardening.
  • Confirm the permission mode remains request-review.

Your runtime and approval gate are ready. Next, you will make the naive checkout calculator fail at the boundaries its happy path hides.

Expose the Naive Failures

Your controlled Node.js workspace is ready. You can now build the smallest version of the checkout calculator that appears convincing on a normal order.

A happy-path result can hide fractional money values or missing validation. This step uses boundary testing to expose those gaps before an AI agent proposes any changes.

In this step, get ready to:
  • Create the deliberately brittle checkout calculator.
  • Add nine tests covering valid orders and production boundaries.
  • Stage the baseline before observing five intentional failures.
Create the naive calculator

The first implementation handles a normal order through multiplication and addition. Its discount calculation can produce fractional cents because it multiplies the subtotal by a decimal rate.

  • In the Explorer sidebar in VS Code, use the folder creation control beside ai-checkout-hardening to create src.

You should see the new src folder inside ai-checkout-hardening.

  • Select the src folder in the Explorer sidebar.
  • Use the file creation control to create calculate-order-total.mjs inside src.
  • Add the naive calculator by pasting this code into src/calculate-order-total.mjs:
export function calculateOrderTotal(items, discountCode = null) {
  const subtotalCents = items.reduce(
    (sum, item) => sum + item.unitPriceCents * item.quantity,
    0,
  );
  const discountCents =
    discountCode === 'SAVE10' ? subtotalCents * 0.1 : 0;

  return {
    subtotalCents,
    discountCents,
    totalCents: subtotalCents - discountCents,
  };
}

What does this code do?

  • The calculateOrderTotal() function accepts an array of order items plus an optional discount code.
  • The reduce() calculation multiplies each unit price by its quantity before adding the line to subtotalCents.
  • The SAVE10 branch calculates ten percent through decimal multiplication.
  • The returned object exposes the subtotal plus the discount plus the final total as cents.
  • Save src/calculate-order-total.mjs.

The Explorer sidebar should now show calculate-order-total.mjs under src.

Source File in the Wrong Place?

Confirm that calculate-order-total.mjs is nested under src in the VS Code Explorer. Move the file into that folder if it appears beside src.

Check that the filename ends with .mjs. A different extension changes how Node.js loads the module.

Help me place the checkout source file correctly.

Build the boundary test suite

The test suite starts with ordinary totals before moving into malformed orders and discount boundaries. Each new group makes another assumption observable.

  • In the Explorer sidebar, use the folder creation control beside ai-checkout-hardening to create test.

You should see test beside src inside the workspace.

  • Select the test folder in the Explorer sidebar.
  • Use the file creation control to create calculate-order-total.test.mjs inside test.
  • Add the test imports plus the first two money checks by pasting this code into test/calculate-order-total.test.mjs:
import test from 'node:test';
import assert from 'node:assert/strict';

import { calculateOrderTotal } from '../src/calculate-order-total.mjs';

test('returns integer cents without a discount', () => {
  assert.deepStrictEqual(
    calculateOrderTotal([{ unitPriceCents: 500, quantity: 2 }]),
    { subtotalCents: 1000, discountCents: 0, totalCents: 1000 },
  );
});

test('rounds a SAVE10 discount down to whole cents', () => {
  assert.deepStrictEqual(
    calculateOrderTotal(
      [{ unitPriceCents: 999, quantity: 1 }],
      'SAVE10',
    ),
    { subtotalCents: 999, discountCents: 99, totalCents: 900 },
  );
});

What do these tests prove?

The first test proves that an ordinary order returns integer-cent totals. The second test sets up a subtotal whose ten-percent discount contains a fractional cent.

The expected discount rounds down to 99 cents. This boundary reveals whether decimal multiplication leaks fractions into the result.

  • Save test/calculate-order-total.test.mjs.

Before you run the tests, do you think both money checks will pass?

  • Run the first two tests from the VS Code terminal with this command:
node --test

What should you see?

The report shows two tests in total. One passes while the rounding test fails because the implementation returns fractional discount money.

This failure is intentional. You have exposed the first production boundary without changing the implementation.

Test File Not Discovered?

Confirm that the file is named calculate-order-total.test.mjs inside the test folder. The test runner discovers files that use the test filename pattern.

Check that the import path begins with ../src/ because the test file sits one folder below ai-checkout-hardening.

Help me diagnose why Node.js cannot discover the checkout tests.

The next group checks multiple lines plus the shape of the order itself. These cases separate valid aggregation from invalid container input.

  • Add the next three tests below the rounding test in test/calculate-order-total.test.mjs by pasting this code:
test('totals multiple order lines', () => {
  assert.deepStrictEqual(
    calculateOrderTotal([
      { unitPriceCents: 500, quantity: 2 },
      { unitPriceCents: 250, quantity: 3 },
    ]),
    { subtotalCents: 1750, discountCents: 0, totalCents: 1750 },
  );
});

test('rejects an empty order', () => {
  assert.throws(() => calculateOrderTotal([]), RangeError);
});

test('rejects a non-array order', () => {
  assert.throws(() => calculateOrderTotal('not-an-array'), TypeError);
});

What do these tests prove?

  • The multiple-line test proves that the calculator adds separate item totals correctly.
  • The empty-order test requires a RangeError because a checkout needs at least one item.
  • The non-array test requires a TypeError when the order has the wrong container type.
  • Save test/calculate-order-total.test.mjs.
  • Run the expanded suite from the VS Code terminal with this command:
node --test

What changed in the report?

The report now covers five tests. Three pass while the rounding test plus the empty-order test fail.

The non-array case passes because calling reduce() on a string already causes a type error. The implementation still lacks an explicit order-type check.

Seeing a Syntax Failure?

Compare the three new test blocks with the snippet above. Check each closing parenthesis plus each closing brace.

Confirm that the new tests begin after the closing }); from the rounding test. A test nested inside another test causes the file to parse incorrectly.

Help me find the syntax problem in the expanded checkout test file.

Valid container input can still contain invalid values. The next cases require explicit range checks plus predictable discount handling.

  • Add the next three boundary tests below the non-array test in test/calculate-order-total.test.mjs by pasting this code:
test('rejects a negative unit price', () => {
  assert.throws(
    () => calculateOrderTotal([{ unitPriceCents: -1, quantity: 1 }]),
    RangeError,
  );
});

test('rejects a zero quantity', () => {
  assert.throws(
    () => calculateOrderTotal([{ unitPriceCents: 500, quantity: 0 }]),
    RangeError,
  );
});

test('rejects an unknown discount code', () => {
  assert.throws(
    () =>
      calculateOrderTotal(
        [{ unitPriceCents: 500, quantity: 1 }],
        'MYSTERY',
      ),
    RangeError,
  );
});

What do these tests prove?

  • The negative-price test requires the calculator to reject an invalid monetary range.
  • The zero-quantity test requires every item to contribute at least one unit.
  • The unknown-code test prevents an unrecognized promotion from being treated as no discount.
  • Save test/calculate-order-total.test.mjs.
  • Run the eight-test suite from the VS Code terminal with this command:
node --test

What changed in the report?

The report now covers eight tests. Three pass while five fail.

The new failures show that negative prices plus zero quantities plus unknown discounts pass through without an exception. The naive implementation has no explicit validation for those boundaries.

Seeing More Than Five Failures?

Check that each assert.throws() call includes the expected error class after the function argument. Missing commas can make the entire test file fail to parse.

Confirm that the unknown-code test passes MYSTERY as the second argument to calculateOrderTotal().

Help me compare my eight-test report with the expected five intentional failures.

The final test protects caller-owned data. Frozen input makes an accidental mutation visible immediately.

  • Add the immutability test below the unknown-code test in test/calculate-order-total.test.mjs by pasting this code:
test('does not mutate the input order', () => {
  const item = Object.freeze({ unitPriceCents: 500, quantity: 2 });
  const items = Object.freeze([item]);

  calculateOrderTotal(items, 'SAVE10');

  assert.deepStrictEqual(items, [item]);
});

What does this test prove?

The test freezes both the item and its containing array. It calls calculateOrderTotal() before confirming that the original array still contains the same item.

A passing result proves that the current calculation reads caller-owned data without changing it.

  • Save test/calculate-order-total.test.mjs.

✔️ Awesome, I've got everything!

Both files are now saved. The source contains the naive calculator while the test file contains all nine contract checks.

ⓧ I'd like to double check the full code

Compare these complete files with the versions in your VS Code Explorer.

export function calculateOrderTotal(items, discountCode = null) {
  const subtotalCents = items.reduce(
    (sum, item) => sum + item.unitPriceCents * item.quantity,
    0,
  );
  const discountCents =
    discountCode === 'SAVE10' ? subtotalCents * 0.1 : 0;

  return {
    subtotalCents,
    discountCents,
    totalCents: subtotalCents - discountCents,
  };
}

What should match?

The source file should contain one exported calculateOrderTotal() function. Its discount branch should still multiply subtotalCents by 0.1.

import test from 'node:test';
import assert from 'node:assert/strict';

import { calculateOrderTotal } from '../src/calculate-order-total.mjs';

test('returns integer cents without a discount', () => {
  assert.deepStrictEqual(
    calculateOrderTotal([{ unitPriceCents: 500, quantity: 2 }]),
    { subtotalCents: 1000, discountCents: 0, totalCents: 1000 },
  );
});

test('rounds a SAVE10 discount down to whole cents', () => {
  assert.deepStrictEqual(
    calculateOrderTotal(
      [{ unitPriceCents: 999, quantity: 1 }],
      'SAVE10',
    ),
    { subtotalCents: 999, discountCents: 99, totalCents: 900 },
  );
});

test('totals multiple order lines', () => {
  assert.deepStrictEqual(
    calculateOrderTotal([
      { unitPriceCents: 500, quantity: 2 },
      { unitPriceCents: 250, quantity: 3 },
    ]),
    { subtotalCents: 1750, discountCents: 0, totalCents: 1750 },
  );
});

test('rejects an empty order', () => {
  assert.throws(() => calculateOrderTotal([]), RangeError);
});

test('rejects a non-array order', () => {
  assert.throws(() => calculateOrderTotal('not-an-array'), TypeError);
});

test('rejects a negative unit price', () => {
  assert.throws(
    () => calculateOrderTotal([{ unitPriceCents: -1, quantity: 1 }]),
    RangeError,
  );
});

test('rejects a zero quantity', () => {
  assert.throws(
    () => calculateOrderTotal([{ unitPriceCents: 500, quantity: 0 }]),
    RangeError,
  );
});

test('rejects an unknown discount code', () => {
  assert.throws(
    () =>
      calculateOrderTotal(
        [{ unitPriceCents: 500, quantity: 1 }],
        'MYSTERY',
      ),
    RangeError,
  );
});

test('does not mutate the input order', () => {
  const item = Object.freeze({ unitPriceCents: 500, quantity: 2 });
  const items = Object.freeze([item]);

  calculateOrderTotal(items, 'SAVE10');

  assert.deepStrictEqual(items, [item]);
});

What should match?

The test file should contain nine named tests. The tests cover ordinary totals plus rounding plus invalid orders plus immutability.

A staged Git baseline preserves the naive source plus its tests before any implementation patch. Future diffs can then show exactly what the AI changes.

  • Initialize the repository and stage both files by running these commands in the VS Code terminal:
git init
git add .

What do these commands do?

The first command initializes Git inside ai-checkout-hardening. The second command adds the current source plus test contents to Git's staged baseline.

Later edits remain separate from this baseline until you stage them. That separation makes scope growth visible during review.

  • Confirm that no unstaged file changes remain by running this command:
git diff

What should you see?

The command should return no diff because the current files match Git's staged versions. Your naive calculator plus all nine tests now form the review baseline.

Still Seeing a Diff?

Save both src/calculate-order-total.mjs and test/calculate-order-total.test.mjs in VS Code. Stage the saved contents again with the staging command from above.

Help me understand why Git still shows unstaged checkout file changes.

Run the complete failure report

The complete suite now tests both the behavior that already works and the boundaries the implementation ignores. The failing report is evidence that the tests can detect the missing production contract.

Before you run the complete suite, do you expect the final immutability check to change the number of failures?

  • Run all nine tests from the VS Code terminal with this command:
node --test

What should you see?

The report shows nine tests in total. Four pass while five fail.

  • The whole-cent SAVE10 rounding test fails.
  • The empty-order rejection test fails.
  • The negative-price rejection test fails.
  • The zero-quantity rejection test fails.
  • The unknown-discount rejection test fails.

That failure report is the win for this step. You now have visible proof that a convincing happy path leaves five production boundaries unprotected.

Different Test Counts?

Confirm that the report lists nine named tests. Compare your test file with the full-code tab if a test is missing or duplicated.

Leave the naive source unchanged even if a failure looks easy to fix. This step preserves the designed obstacle for the contract-first workflow.

Help me compare my nine-test report with the expected four passing and five failing tests.

Your staged baseline now makes every missing boundary visible. Next, you will give Google Antigravity a compact contract that limits its plan before it touches the calculator.

Define the Repository Contract

Your Node.js test run exposed five failures in the naive checkout calculator.

Failing tests reveal symptoms. Contract-first development turns those symptoms into behavior that every future patch must preserve.

You will record the calculator behavior in SPEC.md. You will give Google Antigravity persistent boundaries in AGENTS.md.

A reviewable plan then connects those rules to one bounded implementation change.

In this step, get ready to:
  • Define calculator behavior in SPEC.md.
  • Set agent scope in AGENTS.md.
  • Approve one bounded plan after inspecting the active context.
Write the repository rules

The SPEC.md file defines the calculator's public behavior. It turns the failing test cases into a contract that future edits must satisfy.

  • Use the new-file control in the workspace file tree to create SPEC.md inside the ai-checkout-hardening folder.

You should see a blank SPEC.md beside the existing src folder and test folder.

  • Add the public API and input rules by pasting this first section into SPEC.md:
# Checkout Calculator Contract

## Public API

`calculateOrderTotal(items, discountCode = null)` returns:

- `subtotalCents`
- `discountCents`
- `totalCents`

Every returned money value is a safe integer number of cents.

## Input rules

- `items` is a non-empty array.
- Every item is an object.
- `unitPriceCents` is a non-negative safe integer.
- `quantity` is a positive safe integer.
- The function does not mutate the input array or its items.

What does this contract define?

  • The calculateOrderTotal(items, discountCode = null) signature preserves the module's existing public entry point.
  • The return-field list preserves subtotalCents, discountCents, and totalCents.
  • The input rules reject malformed data before it can produce a convincing total.
  • Safe integer cents keep every returned money value inside JavaScript's reliable integer range.
  • Save SPEC.md.

The saved file should show the Public API section above the Input rules section.

Missing part of the contract?

Check that SPEC.md is inside ai-checkout-hardening. It should sit beside the src folder and test folder.

Compare the function name and return fields character for character against the block above.

help me compare the first section of SPEC.md with the checkout calculator contract.

The remaining rules define discount behavior and error categories. They also block dependency growth during the fix.

  • Place your cursor on the blank line after the final input rule.
  • Add the remaining contract rules by pasting this section:
## Discount rules

- `null` means no discount.
- `SAVE10` subtracts 10 percent of the subtotal.
- Fractional discount cents are rounded down.
- Any other discount code throws `RangeError`.

## Error rules

- Wrong input types throw `TypeError`.
- Invalid ranges, empty orders, unknown discounts, and unsafe integer results throw `RangeError`.

## Constraints

- Keep the existing public function name and return fields.
- Use only JavaScript and Node.js built-ins.
- Add no dependencies.

Why separate errors and constraints?

The error rules map wrong types to TypeError. Invalid values and unsafe results map to RangeError.

The constraints preserve the public API and keep the patch focused on built-in JavaScript behavior.

  • Save SPEC.md.

Your contract should now end with the Constraints section and the rule Add no dependencies..

Contract sections out of order?

Check that Discount rules follows Input rules. The final two sections should be Error rules and Constraints.

help me find a missing or misplaced section in SPEC.md.

✔️ Awesome, I've got everything!

Great. Double check that you saved SPEC.md inside ai-checkout-hardening.

ⓧ I'd like to double check the full code

Compare your complete SPEC.md file against this reference.

# Checkout Calculator Contract

## Public API

`calculateOrderTotal(items, discountCode = null)` returns:

- `subtotalCents`
- `discountCents`
- `totalCents`

Every returned money value is a safe integer number of cents.

## Input rules

- `items` is a non-empty array.
- Every item is an object.
- `unitPriceCents` is a non-negative safe integer.
- `quantity` is a positive safe integer.
- The function does not mutate the input array or its items.

## Discount rules

- `null` means no discount.
- `SAVE10` subtracts 10 percent of the subtotal.
- Fractional discount cents are rounded down.
- Any other discount code throws `RangeError`.

## Error rules

- Wrong input types throw `TypeError`.
- Invalid ranges, empty orders, unknown discounts, and unsafe integer results throw `RangeError`.

## Constraints

- Keep the existing public function name and return fields.
- Use only JavaScript and Node.js built-ins.
- Add no dependencies.

What should match?

The headings and bullets should match this reference exactly. This file is the approved behavior contract for the calculator.

The behavior contract describes what the calculator must do. The AGENTS.md file controls how the agent may work on that contract.

  • Use the new-file control in the workspace file tree to create AGENTS.md inside the ai-checkout-hardening folder.

You should see a blank AGENTS.md beside SPEC.md in the workspace file tree.

  • Add the persistent agent rules by pasting this file into AGENTS.md:
# Repository Rules

## Scope

- Read `SPEC.md` before proposing or editing code.
- Change only files named in the approved plan.
- Preserve the `calculateOrderTotal` public function and its return fields.
- Add no dependencies.

## Implementation

- Represent money as integer cents.
- Reject invalid input instead of coercing it.
- Prefer small functions and explicit branches over speculative abstractions.
- Do not mutate caller-owned data.

## Verification

- Run `node --test` after an implementation change.
- Do not weaken or delete a failing test to make the suite pass.
- Stop and ask if the specification and tests conflict.

## Response format

- Summarize changed files.
- Report the test result.
- List remaining risks in no more than three bullets.

What do these rules control?

  • The scope rules limit edits to files named in an approved plan.
  • The implementation rules favor explicit validation and integer money.
  • The verification rules protect the failing tests from being weakened or deleted.
  • The response format makes changed files and remaining risks visible during review.
  • Save AGENTS.md.

The saved file should contain four sections named Scope, Implementation, Verification, and Response format.

Agent rules incomplete?

Check that AGENTS.md is saved at the top level of ai-checkout-hardening. Antigravity reads the workspace-root rule file when it starts.

help me compare AGENTS.md with the required repository rules.

✔️ Awesome, I've got everything!

Great. Double check that you saved AGENTS.md beside SPEC.md.

ⓧ I'd like to double check the full code

Compare your complete AGENTS.md file against this reference.

# Repository Rules

## Scope

- Read `SPEC.md` before proposing or editing code.
- Change only files named in the approved plan.
- Preserve the `calculateOrderTotal` public function and its return fields.
- Add no dependencies.

## Implementation

- Represent money as integer cents.
- Reject invalid input instead of coercing it.
- Prefer small functions and explicit branches over speculative abstractions.
- Do not mutate caller-owned data.

## Verification

- Run `node --test` after an implementation change.
- Do not weaken or delete a failing test to make the suite pass.
- Stop and ask if the specification and tests conflict.

## Response format

- Summarize changed files.
- Report the test result.
- List remaining risks in no more than three bullets.

What should match?

Every rule should match this reference exactly. These instructions keep future Antigravity sessions scoped to the same engineering boundaries.

Staging records the approved contracts beside the naive baseline. Git can then distinguish these accepted files from the implementation patch that comes next.

  • Stage the repository contracts by running this command in the VS Code terminal from earlier:
git add .

What does this command do?

The command adds the current contents of SPEC.md and AGENTS.md to Git's index. The staged baseline now includes the naive implementation and its approved repository contracts.

The terminal should return to its prompt without reporting an error.

Git command failed?

Check that the terminal is still inside the ai-checkout-hardening folder. The repository was initialized in that folder during the previous step.

help me stage SPEC.md and AGENTS.md in the existing Git repository.

Inspect the active context

Agent context is the material currently available for reasoning. A narrow context keeps the calculator contract and relevant files at the center of the request.

  • Switch back to the Antigravity workspace from earlier.
  • Open the context usage visualization by entering this command:
/context

What does this command show?

The /context command opens a visualization of the active context. It lets you confirm that the current reasoning boundary belongs to this workspace.

  • Use the context panel to confirm the active workspace is ai-checkout-hardening.
  • Check that the visible context relates to the calculator repository.

The panel should show a focused workspace context for the checkout project.

Context looks unrelated?

Confirm that Antigravity is still attached to the trusted ai-checkout-hardening workspace. Return to that workspace before opening the context panel again.

help me confirm Antigravity is using the ai-checkout-hardening workspace context.

Plan the bounded implementation

An Implementation Plan turns the contract into a reviewable file-level proposal. Planning performs exploration without changing the calculator source.

Before you submit the request, picture the file list you expect a tightly bounded plan to contain.

  • Close the context panel using its on-screen close control.
  • Submit the bounded planning request by entering this exact prompt:
/plan Harden calculateOrderTotal to satisfy SPEC.md and the existing tests. Modify only src/calculate-order-total.mjs. Add no dependencies. Run node --test after the change.

Why use a plan first?

The /plan command explores the request and drafts a reviewable artifact before implementation. The named file and verification command make scope growth easy to spot.

Antigravity should create an Implementation Plan for review. The contents of src/calculate-order-total.mjs should remain unchanged.

  • Press Ctrl+R to open the Artifact Review panel.
  • Inspect the Implementation Plan.
  • Check that the plan modifies only src/calculate-order-total.mjs.
  • Confirm that the plan runs node --test after the change.
  • Press n to reject a plan that adds a dependency or names another file.
  • Resubmit the same bounded request after a rejection.
  • Press y to approve a plan that stays within the contract.
  • Stop after approving the plan.

The approved plan should name only src/calculate-order-total.mjs and node --test. The source file should still contain the naive implementation.

You now have a reviewed implementation boundary without allowing an edit.

Plan tries to widen the patch?

Reject any plan that changes tests or adds dependencies. Submit the same request again so the named implementation file remains the only edit target.

help me review an Antigravity plan for scope growth.

Your repository now has an explicit contract and a reviewed plan. Next up, you will let Antigravity implement that one bounded patch.

Apply and Verify the Bounded Patch

The approved Implementation Plan gives Google Antigravity a narrow route from five visible failures to one source patch. That boundary keeps the agent focused on the calculator contract.

This step turns the reviewed plan into working code. You will inspect the generated diff before independently proving that every existing test passes.

In this step, get ready to:
  • Apply the approved plan to the named source file.
  • Review the generated source diff against the contract.
  • Independently prove that all nine tests pass.
Apply the approved plan

The plan already limits implementation work to src/calculate-order-total.mjs. The permission gate gives you one more chance to enforce that limit before any file changes.

  • Return to the approved Implementation Plan in Antigravity.
  • Select Proceed on the approved plan.
  • Approve the request to modify src/calculate-order-total.mjs.
  • Reject any write request that names another file.

Antigravity produces a candidate implementation for the calculator. The patch still needs your review before it becomes accepted work.

Patch Touching Another File?

  • Reject the highlighted artifact by pressing n.
  • Tell Antigravity to follow the approved file scope exactly.

Ask for help if the generated patch keeps widening its scope: help me enforce the approved one-file Antigravity plan

Review the source diff

A source diff shows the exact lines the agent added or removed. Review those lines against SPEC.md before accepting the implementation.

  • Open the Artifact Review panel by pressing Ctrl+R.
  • Open the diff for src/calculate-order-total.mjs.
  • Compare each input check with the rules in SPEC.md.
  • Compare the changed file list with the scope in AGENTS.md.

✔️ Awesome, I've got everything!

Your diff validates the order boundaries while preserving the existing public function and return fields.

ⓧ I'd like to double check the full code

Compare the generated diff with this complete src/calculate-order-total.mjs reference.

const DISCOUNT_RATE_PERCENT = 10;

function calculateLineTotal(item, index) {
  if (item === null || typeof item !== 'object' || Array.isArray(item)) {
    throw new TypeError(`items[${index}] must be an object`);
  }

  const { unitPriceCents, quantity } = item;

  if (!Number.isSafeInteger(unitPriceCents)) {
    throw new TypeError(`items[${index}].unitPriceCents must be a safe integer`);
  }
  if (unitPriceCents < 0) {
    throw new RangeError(`items[${index}].unitPriceCents must be non-negative`);
  }
  if (!Number.isSafeInteger(quantity)) {
    throw new TypeError(`items[${index}].quantity must be a safe integer`);
  }
  if (quantity <= 0) {
    throw new RangeError(`items[${index}].quantity must be greater than zero`);
  }

  const lineTotalCents = unitPriceCents * quantity;
  if (!Number.isSafeInteger(lineTotalCents)) {
    throw new RangeError(`items[${index}] line total exceeds safe integer range`);
  }

  return lineTotalCents;
}

export function calculateOrderTotal(items, discountCode = null) {
  if (!Array.isArray(items)) {
    throw new TypeError('items must be an array');
  }
  if (items.length === 0) {
    throw new RangeError('items must contain at least one item');
  }

  let subtotalCents = 0;
  for (const [index, item] of items.entries()) {
    subtotalCents += calculateLineTotal(item, index);
    if (!Number.isSafeInteger(subtotalCents)) {
      throw new RangeError('subtotal exceeds safe integer range');
    }
  }

  let discountRatePercent = 0;
  if (discountCode === 'SAVE10') {
    discountRatePercent = DISCOUNT_RATE_PERCENT;
  } else if (discountCode !== null) {
    throw new RangeError('unknown discount code');
  }

  const discountProduct = subtotalCents * discountRatePercent;
  if (!Number.isSafeInteger(discountProduct)) {
    throw new RangeError('discount calculation exceeds safe integer range');
  }

  const discountCents = Math.floor(discountProduct / 100);

  return {
    subtotalCents,
    discountCents,
    totalCents: subtotalCents - discountCents,
  };
}

What Does This Implementation Do?

  • The calculateLineTotal() helper rejects malformed items before calculating each line total.
  • The main function checks the order array before accumulating an integer subtotalCents value.
  • The discount branch accepts SAVE10 while rejecting unknown codes with RangeError.
  • The percentage calculation uses integer multiplication before Math.floor() removes any fractional cent.
  • Confirm that src/calculate-order-total.mjs is the only changed file in the artifact review.
  • Approve the highlighted source artifact by pressing y.

The source diff is now reviewed and approved. Your contract stayed in control of the implementation.

Verify the patch independently

Agent-reported success is one signal. An independent local run proves that the approved code works in your current workspace.

  • Return to the Antigravity verification request.
  • Authorize the requested test command.

Antigravity reports that the existing suite passes. Your own terminal run now checks the same working tree without relying on that report.

Before you run the independent check, do you expect the local result to match the agent's report?

  • Independently verify the approved patch in the VS Code terminal by running:
node --test

What Does This Check Prove?

The Node.js test runner discovers the repository's existing test file. It checks the approved implementation against all nine boundary cases.

You should see all nine named tests pass with zero failures. That closes the verification loop with a result you reproduced yourself.

Still Seeing a Failing Test?

  • Compare src/calculate-order-total.mjs character for character with the full-file reference above.
  • Confirm that test/calculate-order-total.test.mjs still matches the staged baseline.

Use this if the failing test still does not point to the mismatch: help me diagnose the remaining checkout test failure without changing the tests

Your calculator now handles integer money and invalid boundaries through one reviewed patch. Next, you will inspect the working-tree diff like the code owner.

Review the Patch as Code Owner

Your bounded patch now passes all nine tests. That proves the calculator meets every tested behavior in SPEC.md.

Passing tests cover known behavior. A code-owner review checks the patch's scope before you accept it.

In this step, get ready to:
  • Review the source patch against the staged baseline.
  • Request a read-only risk review from Google Antigravity.
  • Save a reusable six-stage review protocol.
Review the staged delta

Git compares your working tree with the staged baseline. This view exposes every implementation change that still needs your approval.

Before you run this check, which file do you expect the diff to contain?

  • Reveal the source patch against the staged baseline by running this command:
git diff

What does this command show?

  • The git diff command compares the working tree with the staged index.
  • The staged index contains your approved baseline.
  • The remaining diff should contain only the bounded calculator patch.

You should see changes only in src/calculate-order-total.mjs. The specification plus the tests should stay outside the diff.

  • Inspect the added validation branches for duplicated checks.
  • Check every input path for silent coercion.
  • Confirm calculateLineTotal has one clear responsibility.
  • Check that the patch avoids unnecessary abstractions.
  • Confirm the public calculateOrderTotal function keeps its existing return fields.
  • Confirm the diff contains no changes outside src/calculate-order-total.mjs.

Strong work. You have now checked the implementation as its owner instead of relying only on the passing test report.

Seeing unexpected scope?

Stop the review if the diff includes an unrelated file. Compare that file with the staged baseline before accepting any extra change.

Use help me identify scope growth in this patch if you want help interpreting the diff.

Request a read-only risk review

A separate risk review can expose assumptions that the current tests do not name. Exact file mentions keep the review inside the approved boundary.

  • Return to the trusted Antigravity workspace from earlier.
  • Submit the exact read-only review request below:
Review only @SPEC.md, @src/calculate-order-total.mjs, and @test/calculate-order-total.test.mjs. Do not edit files. Return at most three contract gaps or production risks, ordered by severity.

How does this prompt stay bounded?

  • The three file mentions define the complete review boundary.
  • The read-only instruction protects the approved implementation from new edits.
  • The response limit forces Antigravity to prioritize the highest-risk findings.

You should receive no more than three findings. The calculator source should remain unchanged.

  • Compare every reported contract gap with SPEC.md.
  • Compare every implementation claim with test/calculate-order-total.test.mjs.
  • Treat only contract-backed findings as actionable.
  • Reject any suggestion that requires an unrelated file change.
  • Reject any suggestion that adds a dependency.

Did the review propose edits?

Do not approve an edit request from this review. Submit the same read-only request again if the response crosses the file boundary.

Use help me keep this review read-only if the agent continues proposing changes.

Inspect context and save the protocol

Agent context determines which repository details influence the review. Inspecting it shows whether your named files kept the session focused.

  • Open the active context visualization by entering this command in Antigravity:
/context

What does context reveal?

The context usage visualization shows which files occupy the active session. A focused review should center on the three files named in your request.

You should see the calculator contract plus its source and test files represented in the active context.

  • Return to the Antigravity prompt.
  • Open the current model quota panel by entering this command:
/usage

What does usage reveal?

The usage panel shows your current model quota. It also shows remaining requests or tokens for supported models.

You have now checked the information boundary behind the review. You have also checked the capacity available for future agent work.

A durable protocol turns these checks into a repeatable engineering habit. The final project file captures the same six stages for your next production change.

  • Return to Visual Studio Code from earlier.
  • Create AI_WORKFLOW.md inside the open ai-checkout-hardening workspace by pasting the content below:
# Six-Stage Production AI Change Protocol

1. Explore: Name the exact files and ask for read-only explanation before edits.
2. Specify: Record behavior, edge cases, constraints, and observable completion criteria.
3. Plan: Use `/plan`, review the proposed files and dependencies, and reject scope growth.
4. Implement: Approve one bounded diff while retaining human control of architecture and permissions.
5. Verify: Run deterministic local tests and reproduce the result independently.
6. Review: Inspect the actual diff, request a risk-only review, check `/context` and `/usage`, and update `AGENTS.md` when a correction should persist.

Reusable prompt:

`/plan Read @SPEC.md, @AGENTS.md, and the named failing test. Change only the named implementation file. Add no dependencies. Do not edit tests. Run node --test and stop when the contract passes.`

What does this protocol capture?

  • Explore names the exact files before any edits begin.
  • Specify records the behavior that defines success.
  • Plan exposes proposed scope before implementation.
  • Implement keeps architecture plus permissions under human control.
  • Verify requires deterministic local evidence.
  • Review checks the actual patch for remaining risk.
  • Save AI_WORKFLOW.md.
  • Confirm AI_WORKFLOW.md appears in the Explorer sidebar inside ai-checkout-hardening.

Cannot find the workflow file?

Check that the Explorer sidebar still shows the ai-checkout-hardening workspace. Recreate the file there if it was saved in another folder.

Use help me locate AI_WORKFLOW.md if the file remains missing.

✔️ Awesome, I've got everything!

Your six-stage protocol is saved in AI_WORKFLOW.md. It is ready to guide the next bounded change.

ⓧ I'd like to double check the full code

Compare your complete AI_WORKFLOW.md file with this reference.

# Six-Stage Production AI Change Protocol

1. Explore: Name the exact files and ask for read-only explanation before edits.
2. Specify: Record behavior, edge cases, constraints, and observable completion criteria.
3. Plan: Use `/plan`, review the proposed files and dependencies, and reject scope growth.
4. Implement: Approve one bounded diff while retaining human control of architecture and permissions.
5. Verify: Run deterministic local tests and reproduce the result independently.
6. Review: Inspect the actual diff, request a risk-only review, check `/context` and `/usage`, and update `AGENTS.md` when a correction should persist.

Reusable prompt:

`/plan Read @SPEC.md, @AGENTS.md, and the named failing test. Change only the named implementation file. Add no dependencies. Do not edit tests. Run node --test and stop when the contract passes.`

Before the final run, do you expect the review work to change the calculator's test result?

  • Return to the VS Code terminal from earlier.
  • Independently verify the reviewed repository by running this command:
node --test

What does this final run prove?

The built-in Node.js test runner executes the unchanged nine-test suite. This independent run confirms that the reviewed source still satisfies the contract.

You should see all nine tests pass with zero failures. That is the full ownership loop complete: you inspected the diff, challenged its risks, checked agent context, recorded the protocol, and reproduced the result yourself.

Seeing a test failure?

Confirm that the read-only review did not modify src/calculate-order-total.mjs. Confirm that the existing test file remains unchanged.

Use help me diagnose the final test failure without widening the patch if the suite no longer passes.

Secret mission

Add a Conditional Promotion Without Expanding Scope

Extend the checkout calculator with a SAVE20 promotion that applies only at a 5000-cent minimum. Use the same plan, approval, test, and review controls to keep the change inside three named files.

Clean Up Your Resources

Clean Up Your Resources

Your checkout project stays on your Mac, so it creates no ongoing cloud costs. Choose whether to keep your workspace ready, pause your local tools, or remove the local resources.

Public Preview Costs

The Google Antigravity individual plan is available at no cost during public preview. Weekly rate limits apply.

The Google Cloud pricing page is the source of truth for ongoing use.

Resources you used:

  • The local ai-checkout-hardening Git repository.
  • The saved Antigravity authentication profile in Apple Keychain.

Keep everything running

No action is needed. Choose this option if you want to extend the checkout calculator or reuse the production AI workflow.

  • Keep the ai-checkout-hardening folder on your Mac.
  • Keep your signed-in Antigravity profile available for your next local project.

Pause - I'll come back to this later

Shut down your local tools while keeping the repository available. No deployed service continues running.

  • Close Antigravity.
  • Close Visual Studio Code.

Your project files remain on your Mac. Your Antigravity profile remains stored in Apple Keychain.

Delete - I don't want to use this again

Deleting completed work can feel drastic. These steps remove the local project folder and saved Antigravity authentication profile.

  • Disconnect the saved Antigravity profile by entering this command in the Antigravity prompt:
/logout

What Does This Command Do?

The /logout command disconnects your profile from Antigravity. It purges saved authentication profiles from Apple Keychain.

  • Confirm Antigravity no longer shows your signed-in profile.

Still Signed In?

  • Return to the Antigravity prompt.
  • Enter /logout once more.
  • Get help disconnecting the saved profile.
  • Close Antigravity.
  • Close Visual Studio Code.
  • Open Finder from your Mac's Dock.
  • Locate the ai-checkout-hardening folder.
  • Drag the ai-checkout-hardening folder to the Trash in the Dock.
  • Check the Trash for any other files you want to keep.
  • Empty the Trash.
  • Confirm the ai-checkout-hardening folder no longer appears in its previous location.

Nice Work!

Nice Work!

That is a production-style checkout hardening workflow completed from end to end. Your JavaScript module now calculates integer-cent totals safely. It also rejects malformed orders through explicit contract rules.

You learned how to:

  • Built an integer-safe checkout calculator that validates order lines. It applies predictable discount rules without mutating caller-owned data.
  • Used boundary testing to expose rounding errors and invalid inputs. Your Node.js test suite now proves eleven checkout behaviors.
  • Practiced a review-first AI workflow with compact contracts and bounded plans. You inspected the actual diff before independently verifying the result. You also saved the six-stage protocol in AI_WORKFLOW.md for future changes.
  • Secret Mission: Added a conditional SAVE20 promotion with a 5000-cent minimum. You kept the approved patch limited to SPEC.md, src/calculate-order-total.mjs, and test/calculate-order-total.test.mjs.

Ready to quiz yourself?