Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Merchant Or Buyer Outcome

- Observed failure:
- Demand proof:
- Pricing hypothesis:
- First distribution format:

## State Boundary

- States or transitions changed:
- Authorization/consent boundary:
- Payment proof remains separate from fulfillment proof:
- Explicitly out of scope:

## Verification

- Commands and results:
- Failure fixture or regression:
- Security receipt or `N/A` with reason:

## Delivery

- Expected production health evidence:
- Human approvals still required:
- Public proof draft or `N/A` with reason:
69 changes: 69 additions & 0 deletions .github/workflows/delivery.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Production delivery receipt

on:
workflow_run:
workflows: ["CI"]
types: [completed]
workflow_dispatch:
inputs:
expected_commit:
description: "Full main commit SHA; blank uses current main"
required: false
type: string

permissions:
contents: read

concurrency:
group: sllr-production-delivery
cancel-in-progress: true

jobs:
verify:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main')
runs-on: ubuntu-latest
timeout-minutes: 8
env:
EXPECTED_COMMIT: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || inputs.expected_commit }}

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || 'main' }}

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "20"

- name: Resolve expected commit
id: expected
run: |
expected="$EXPECTED_COMMIT"
if [[ -z "$expected" ]]; then
expected="$(git rev-parse HEAD)"
fi
if [[ ! "$expected" =~ ^[0-9a-f]{40}$ ]]; then
echo "expected_commit must be a full lowercase 40-character SHA" >&2
exit 1
fi
echo "value=$expected" >> "$GITHUB_OUTPUT"

- name: Verify production revision and durable store
env:
EXPECTED_COMMIT_RESOLVED: ${{ steps.expected.outputs.value }}
run: |
node scripts/verify-sllr-delivery.mjs \
--url https://sll-r.vercel.app \
--expected-commit "$EXPECTED_COMMIT_RESOLVED" \
--output "$RUNNER_TEMP/sllr-delivery-receipt.json"

- name: Upload delivery receipt
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: sllr-delivery-${{ steps.expected.outputs.value }}
path: ${{ runner.temp }}/sllr-delivery-receipt.json
if-no-files-found: error
retention-days: 14
91 changes: 64 additions & 27 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,76 @@
# AGENTS.md - sll-r
# SLL-R Agent Contract

Repo rails for coding agents working in this project.
SLL-R is the merchant-side execution rail:

## Before Editing
```text
merchant-backed offer
-> exact quote
-> quote-bound consent
-> idempotent order
-> payment proof
-> fulfillment proof
-> canonical receipt
```

- Confirm the current branch and dirty state with `git status --short --branch`.
- Read repo-local docs first: `README*`, package/config files, app entrypoints, and nearby tests.
- Preserve unrelated user changes. Do not revert, reformat, or clean files outside the task scope.
- Keep changes surgical. Prefer the existing stack and local patterns over new abstractions.
Payment proof is never fulfillment proof. Demo merchants are fixtures, not
partnership claims.

## Verification
## Required Brief

Before editing, record:

- merchant or buyer failure being solved
- acceptance criteria and failure fixture
- feature, repeated loop, or maintenance classification
- demand proof, pricing hypothesis, and first distribution format, or `N/A`
- affected state transitions and authorization boundary
- verification commands
- actions that remain human-controlled

Use the smallest meaningful check for the changed surface:
Ambiguous payment, merchant authority, consent, or receipt semantics block
implementation.

- JavaScript/TypeScript: prefer existing `check`, `test`, `lint`, `typecheck`, or `build` scripts from `package.json`.
- Swift/Xcode: identify project/workspace, scheme, simulator/device, deployment target, then run the narrow `xcodebuild` build/test command.
- Solidity/EVM: run the local Foundry/Hardhat tests relevant to touched contracts or scripts.
- Python: run targeted tests or `python -m py_compile` for touched scripts when no test suite exists.
- Docs/config only: run formatting/link/containment checks if the repo provides them; otherwise state why runtime verification is not applicable.
## Engineering Loop

- Trigger: one scoped brief or GitHub issue.
- Durable state: issue/PR, tests, and the production delivery receipt.
- Input boundary: repo files plus redacted deterministic fixtures; no customer
messages, payment credentials, or production payload dumps.
- Maker: engineering agent on an isolated branch.
- Checker: `pnpm check`, dependency audit, review, and post-deploy health check.
- Feedback: a deterministic test, security review, or commit-aware production
check.
- Artifact: one atomic PR and one `sllr-delivery-receipt.json`.
- Convergence: tests pass and production reports the exact merged commit on a
durable store.
- Human approval: merge, Vercel settings/deploy changes, merchant onboarding,
production secrets, payment mode changes, cron activation, live transaction,
refund, and external messaging.
- Stop: unverifiable merchant data, missing consent, non-idempotent mutation,
payment/fulfillment conflation, tenant leak, production `memory` store,
revision mismatch, or three failed repair iterations.

## Verification

Never claim verification passed if the command was not run. Report skipped checks with the exact reason.
Run:

## Boundaries
```bash
pnpm install --frozen-lockfile
pnpm check
pnpm audit --prod --audit-level high
```

- Do not commit secrets, private keys, tokens, credentials, customer data, or local machine dumps.
- Do not deploy, publish, submit forms, send messages, merge PRs, change permissions, create credentials, or make financial/auth/security changes without explicit action-time approval.
- Do not delete meaningful local or cloud data unless explicitly asked for that exact action.
- For auth, payment, wallet, parser/import, dependency, or externally exposed changes, include a security review note or explain why it is not applicable.
For payment, auth, webhook, merchant mutation, or externally exposed changes,
include a focused security receipt. Raw scanner output is not a confirmed bug
until the reachable path is reproduced or regraded.

## Done Criteria
## Delivery Boundary

A handoff is complete only when it includes:
Vercel may deploy merged `main`. GitHub Actions does not initiate that deploy.
After CI succeeds, the delivery workflow polls only `GET /health` until it
observes the exact main commit on `supabase` or `redis_rest`, then uploads a
receipt. The checker must never create sessions, orders, payment attempts,
receipts, cron runs, or merchant mutations.

- changed files
- verification command and result
- skipped checks, if any
- risks or follow-up work
- commit/PR status, if shipping was requested
Never run a production deploy, activate live payments, send outreach, or merge
a PR without explicit user approval at action time.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ Storage backend selection is Supabase → Redis REST/KV → memory:
- **Redis REST/KV** supports Vercel KV and Upstash-compatible credentials.

A durable backend is required for serverless or horizontally scaled deployment.
`GET /health` reports the selected store. Production must also set
`GET /health` reports the selected store and the deployment revision supplied by
Vercel, Railway, or another supported runtime. The post-CI delivery workflow
requires an exact main-commit match and rejects the in-memory store without
performing any commerce mutation. Production must also set
`SLLR_MERCHANT_PAYMENT_VERIFY_SECRET`; see [env.example](./env.example).

## Run Locally
Expand Down
11 changes: 11 additions & 0 deletions docs/go-live-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ Everything below is **configuration**, not code.

---

## Delivery receipt

After main CI passes, `.github/workflows/delivery.yml` waits for
`https://sll-r.vercel.app/health` to report the exact merged commit and a
durable `supabase` or `redis_rest` store. It uploads
`sllr-delivery-receipt.json` and performs no buyer, merchant, cron, payment, or
receipt mutation. A missing revision or `memory` store is a failed delivery,
not a reason to weaken the check.

---

## Phase 1 — SLL-R env (Vercel · TEST mode)

Vercel → Project (sll-r) → Settings → Environment Variables (scope: Production).
Expand Down
99 changes: 99 additions & 0 deletions scripts/verify-sllr-delivery.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env node

import { writeFile } from "node:fs/promises";

function argument(name) {
const index = process.argv.indexOf(name);
return index === -1 ? null : process.argv[index + 1] ?? null;
}

function positiveInteger(value, fallback) {
if (value === null) return fallback;
const parsed = Number.parseInt(value, 10);
if (!Number.isSafeInteger(parsed) || parsed < 1) {
throw new Error(`Expected a positive integer, got ${value}.`);
}
return parsed;
}

const url = argument("--url");
const expectedCommit = argument("--expected-commit");
const output = argument("--output");
const attempts = positiveInteger(argument("--attempts"), 20);
const intervalMs = positiveInteger(argument("--interval-ms"), 15_000);

if (!url || !expectedCommit || !output) {
throw new Error("--url, --expected-commit, and --output are required.");
}
if (!/^https:\/\/[a-z0-9.-]+(?::\d+)?(?:\/.*)?$/i.test(url)
&& !/^http:\/\/127\.0\.0\.1:\d+(?:\/.*)?$/.test(url)) {
throw new Error("Delivery verification requires HTTPS or loopback HTTP.");
}
if (!/^[0-9a-f]{40}$/.test(expectedCommit)) {
throw new Error("Expected commit must be a full lowercase 40-character SHA.");
}

const healthUrl = new URL("/health", url).toString();
const durableStores = new Set(["supabase", "redis_rest"]);
let lastObservation = null;

for (let attempt = 1; attempt <= attempts; attempt += 1) {
try {
const response = await fetch(healthUrl, {
headers: { accept: "application/json" },
signal: AbortSignal.timeout(10_000),
});
const payload = await response.json();
lastObservation = {
status: response.status,
ok: payload?.ok === true,
product: payload?.product ?? null,
store: payload?.store ?? null,
revision: payload?.revision ?? null,
};

if (
response.ok
&& lastObservation.ok
&& lastObservation.product === "SLL-R"
&& durableStores.has(lastObservation.store)
&& lastObservation.revision === expectedCommit
) {
const receipt = {
contractVersion: "sllr-delivery-receipt/v1",
product: "SLL-R",
expectedCommit,
observedCommit: lastObservation.revision,
productionUrl: url,
store: lastObservation.store,
attempts: attempt,
checks: {
serviceIdentity: "passed",
exactRevision: "passed",
durableStore: "passed",
paymentMutation: "not-performed",
merchantMutation: "not-performed",
},
status: "verified",
};
await writeFile(output, `${JSON.stringify(receipt, null, 2)}\n`, "utf8");
console.log(`SLL-R delivery verified at ${expectedCommit}.`);
process.exit(0);
}
} catch (error) {
lastObservation = {
error: error instanceof Error ? error.message : String(error),
};
}

console.log(
`Attempt ${attempt}/${attempts}: production has not converged to ${expectedCommit}.`,
);
if (attempt < attempts) {
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
}

throw new Error(
`SLL-R production delivery did not converge: ${JSON.stringify(lastObservation)}`,
);
23 changes: 23 additions & 0 deletions src/scripts/smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2943,6 +2943,9 @@ async function smokePersonalShop(origin: string) {

async function main() {
smokeMoneyBoundaries();
const previousRevision = process.env.VERCEL_GIT_COMMIT_SHA;
const smokeRevision = "0123456789abcdef0123456789abcdef01234567";
process.env.VERCEL_GIT_COMMIT_SHA = smokeRevision;
const server = createSllrServer();
server.listen(0);
await once(server, "listening");
Expand All @@ -2951,6 +2954,21 @@ async function main() {
const origin = `http://127.0.0.1:${address.port}`;

try {
const health = await getJson(origin, "/health") as {
ok?: boolean;
product?: string;
store?: string;
revision?: string | null;
};
if (
health.ok !== true
|| health.product !== "SLL-R"
|| health.store !== "memory"
|| health.revision !== smokeRevision
) {
throw new Error(`Health did not expose delivery identity: ${JSON.stringify(health)}`);
}

const world = await getText(origin, "/world");
if (
!world.response.headers.get("content-type")?.includes("text/html")
Expand Down Expand Up @@ -3922,6 +3940,11 @@ async function main() {

console.log("SLL-R smoke passed");
} finally {
if (previousRevision === undefined) {
delete process.env.VERCEL_GIT_COMMIT_SHA;
} else {
process.env.VERCEL_GIT_COMMIT_SHA = previousRevision;
}
server.close();
}
}
Expand Down
Loading