Skip to content
Closed
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
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
# linejam-914: the schema/migration sequencing check below needs
# full history to resolve a merge-base against the PR's base ref.
# This repo is small (a few hundred commits); a full fetch is cheap.
fetch-depth: 0
Comment on lines 39 to +45

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Set persist-credentials: false on the checkout step.

This job never pushes; persisting the GITHUB_TOKEN in the local git config is unnecessary residual credential exposure. zizmor flags this as artipacked.

🔒 Proposed fix
       - name: Checkout code
         uses: actions/checkout@v7
         with:
           # linejam-914: the schema/migration sequencing check below needs
           # full history to resolve a merge-base against the PR's base ref.
           # This repo is small (a few hundred commits); a full fetch is cheap.
           fetch-depth: 0
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Checkout code
uses: actions/checkout@v7
with:
# linejam-914: the schema/migration sequencing check below needs
# full history to resolve a merge-base against the PR's base ref.
# This repo is small (a few hundred commits); a full fetch is cheap.
fetch-depth: 0
- name: Checkout code
uses: actions/checkout@v7
with:
# linejam-914: the schema/migration sequencing check below needs
# full history to resolve a merge-base against the PR's base ref.
# This repo is small (a few hundred commits); a full fetch is cheap.
fetch-depth: 0
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 39-45: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 39 - 45, The Checkout code step in the
workflow should disable credential persistence because this job does not push.
Update the actions/checkout usage in the checkout step to set
persist-credentials to false alongside the existing fetch-depth setting, so the
GITHUB_TOKEN is not left in local git config and the zizmor artipacked warning
is resolved.

Source: Linters/SAST tools


- name: Set NEXT_PUBLIC_CONVEX_URL fallback
run: |
Expand Down Expand Up @@ -65,6 +70,17 @@ jobs:
- name: Security audit
run: ./scripts/ci/dagger-call.sh audit

# linejam-914 (2026-07-04 outage): PR #298 removed schema fields and
# added their migration in the same commit, wedging every deploy.
# This is a plain git-diff heuristic, not a Dagger container, because
# it operates on git history (base ref + merge-base) rather than the
# source tree -- see docs/convex-migrations.md.
- name: Check schema/migration sequencing
if: github.event_name == 'pull_request'
run: |
node scripts/ci/check-schema-migration-sequencing.mjs \
"origin/${{ github.event.pull_request.base.ref }}"

test-build:
name: Test & Build
runs-on: ubuntu-latest
Expand Down
57 changes: 57 additions & 0 deletions .github/workflows/prod-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ jobs:
name: Production Smoke
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
actions: read
env:
PLAYWRIGHT_BASE_URL: https://www.linejam.app
PLAYWRIGHT_REQUIRE_AUTH_SMOKE: '1'
Expand Down Expand Up @@ -43,13 +46,19 @@ jobs:
run: pnpm exec playwright install chromium --with-deps

- name: Run production smoke
id: smoke
run: |
mkdir -p "$RUNNER_TEMP/linejam-smoke"
set +e
pnpm canary:smoke > "$RUNNER_TEMP/linejam-smoke/stdout.log" 2> "$RUNNER_TEMP/linejam-smoke/stderr.log"
code=$?
cat "$RUNNER_TEMP/linejam-smoke/stdout.log"
cat "$RUNNER_TEMP/linejam-smoke/stderr.log" >&2
{
echo "detail<<SMOKE_DETAIL_EOF"
tail -c 2000 "$RUNNER_TEMP/linejam-smoke/stderr.log"
echo "SMOKE_DETAIL_EOF"
} >> "$GITHUB_OUTPUT"
Comment on lines +49 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow around the referenced lines
file=".github/workflows/prod-smoke.yml"
wc -l "$file"
sed -n '1,160p' "$file"

echo
echo "---- search for LINEJAM_SMOKE_FAILURE_DETAIL and steps.smoke.outputs.detail ----"
rg -n "LINEJAM_SMOKE_FAILURE_DETAIL|steps\.smoke\.outputs\.detail|SMOKE_DETAIL_EOF|GITHUB_OUTPUT" .github/workflows . -g '*.yml' -g '*.yaml' -g '*.md'

Repository: misty-step/linejam

Length of output: 5521


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect GitHub Actions docs locally? (repo files only)
rg -n "multiline|GITHUB_OUTPUT|delimiter|random" .github README.md docs -g '*.md' -g '*.yml' -g '*.yaml' || true

Repository: misty-step/linejam

Length of output: 837


Use a unique GITHUB_OUTPUT delimiter here. A fixed SMOKE_DETAIL_EOF marker can appear in the captured stderr and truncate the multiline value, letting extra text be parsed as new outputs. Generate a per-run delimiter instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/prod-smoke.yml around lines 49 - 61, The smoke job output
block uses a fixed multiline delimiter, which can be collided with by captured
stderr and corrupt the GitHub Actions output parsing. Update the `id: smoke`
step in the workflow to generate a unique per-run delimiter before writing to
`GITHUB_OUTPUT`, and use that dynamic marker for the `detail` multiline value
instead of the hardcoded `SMOKE_DETAIL_EOF`.

exit "$code"

- name: Upload production smoke logs
Expand All @@ -70,3 +79,51 @@ jobs:
playwright-report/
if-no-files-found: ignore
retention-days: 14

# linejam-913 (2026-07-04 outage postmortem): Production Smoke was RED
# for ~15 hours before the operator found the outage by hand -- the
# gate worked, nothing wired the red signal to a human or to BB
# triage. These two steps close that wire: count the consecutive
# failure streak (so one blip is an annotation, not a page), then
# report status to the `linejam-production-smoke` Canary monitor,
# whose `error` check-in maps directly to Canary's Down health state
# and opens/holds an incident that BB triage and the bridge feed both
# already consume. A passing run always reports `ok`, which resolves
# the incident.
- name: Determine consecutive-failure streak
id: streak
if: always()
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
count="$(node scripts/ops/count-consecutive-prod-smoke-failures.mjs '${{ steps.smoke.outcome }}')"
echo "count=$count" >> "$GITHUB_OUTPUT"

- name: Report status to Canary
if: always()
env:
NEXT_PUBLIC_CANARY_API_KEY: ${{ secrets.NEXT_PUBLIC_CANARY_API_KEY }}
NEXT_PUBLIC_CANARY_ENDPOINT: ${{ secrets.NEXT_PUBLIC_CANARY_ENDPOINT }}
LINEJAM_SMOKE_OUTCOME: ${{ steps.smoke.outcome }}
LINEJAM_SMOKE_CONSECUTIVE_FAILURES: ${{ steps.streak.outputs.count }}
LINEJAM_SMOKE_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
LINEJAM_SMOKE_FAILURE_DETAIL: ${{ steps.smoke.outputs.detail }}
run: node scripts/ops/report-prod-smoke-status.mjs

- name: Annotate failure in the step summary
if: steps.smoke.outcome == 'failure'
env:
STREAK_COUNT: ${{ steps.streak.outputs.count }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
{
echo "## Production Smoke failed"
echo
echo "- Consecutive failures: ${STREAK_COUNT}"
echo "- Run: ${RUN_URL}"
if [ "${STREAK_COUNT}" -ge 2 ]; then
echo "- Escalated: reported to the \`linejam-production-smoke\` Canary monitor as Down (opens/holds an incident)."
else
echo "- Not yet escalated: below the 2-run threshold. Recorded on the monitor without opening an incident."
fi
} >> "$GITHUB_STEP_SUMMARY"
56 changes: 52 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,61 @@ jobs:
persist-credentials: false

- name: Run Landmark
# Pinned to the v1 tag's commit — this action holds GH_RELEASE_TOKEN
# (repo write: pushes the release commit + tags), so a floating tag is
# not acceptable here. Bump deliberately when upgrading Landmark.
id: landmark
# Pinned to the v1 tag's commit — this action pushes the release
# commit + tags, so a floating tag is not acceptable here. Bump
# deliberately when upgrading Landmark.
uses: misty-step/landmark@b461fe11b6f609b1a09fd44ef947e8c4c1f234db # v1
with:
mode: full
node-version: 22
healthcheck: 'true'
github-token: ${{ secrets.GH_RELEASE_TOKEN }}
# linejam-916: GH_RELEASE_TOKEN was a personal gh-CLI OAuth token
# pasted into a repo secret. It started 403ing ("You do not have
# permission to create labels on this repository") the moment
# linejam went public (2026-07-04, PR #296) -- an ad-hoc personal
# credential, not a scoped service credential, so it silently broke
# when the authorizing user's effective access to this repo
# changed shape. master has no branch protection and no rulesets
# (verified: GET /branches/master/protection -> 404, GET
# /rulesets -> []), so nothing requires bypassing review checks
# here -- the job's own `contents: write` / `issues: write` /
# `pull-requests: write` permissions (declared above) are exactly
# what semantic-release + @semantic-release/github need, and
# GITHUB_TOKEN is ephemeral (minted per run, nothing to leak,
# rotate, or silently expire). No new secret was provisioned.
github-token: ${{ secrets.GITHUB_TOKEN }}
llm-api-key: ${{ secrets.OPENROUTER_API_KEY }}

# linejam-915: the /releases page used to read a static store that
# nothing wrote to after v0.1.0, while Landmark kept the RSS feed
# current -- two stores, one dead. This step makes the release
# workflow the single writer of content/releases/ on every release
# (see docs/releases-static-store.md), so the two can never diverge.
- name: Write static release content
if: steps.landmark.outputs.released == 'true'
env:
RELEASE_TAG: ${{ steps.landmark.outputs.release-tag }}
RELEASE_NOTES: ${{ steps.landmark.outputs.release-notes }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
git fetch --tags --force
prev_tag="$(git describe --tags --abbrev=0 "${RELEASE_TAG}^" 2>/dev/null || true)"
notes_file="$(mktemp)"
printf '%s' "${RELEASE_NOTES}" > "${notes_file}"

node scripts/release/write-release-from-git.mjs \
--tag="${RELEASE_TAG}" \
${prev_tag:+--previous-tag="${prev_tag}"} \
--notes-file="${notes_file}"

if [ -n "$(git status --porcelain content/releases)" ]; then
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add content/releases
git commit -m "chore(releases): update static release store for ${RELEASE_TAG} [skip ci]"
git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" HEAD:master
else
echo "content/releases unchanged; nothing to commit."
fi
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Read these when you need the truth:
- `lefthook.yml` — local hook enforcement.
- `docs/testing.md` — actual test commands and environment contract.
- `docs/ops/canary-responder.md` — Canary responder operating contract.
- `docs/convex-migrations.md` — expand-migrate-contract sequencing; read before touching `convex/schema.ts` or `convex/migrations.ts` together.
- `docs/releases-static-store.md` — how `content/releases/` is written and kept in sync with `package.json`'s version; read before touching `app/releases/`, `lib/releases/`, or `scripts/release/`.

## Architecture

Expand Down Expand Up @@ -316,9 +318,12 @@ pnpm canary:smoke
pnpm canary:webhook:setup
pnpm evidence:guest-flow

# Release
# Release (see docs/releases-static-store.md; release.yml is the only writer
# of content/releases/ -- there is no manual "generate releases" command)
pnpm build
pnpm generate:releases

# Onboarding
pnpm doctor

# Backlog claiming
source scripts/lib/claims.sh
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,18 @@ bash scripts/setup.sh --write-env --skip-install

# Add your Convex, Clerk, guest-token, and Canary values to .env.local

# Verify the workspace is actually configured, not just installed
pnpm doctor

# Run development servers (parallel)
pnpm dev # Next.js :3000 + Convex backend

# Re-run doctor once `pnpm dev` is up to confirm the app itself is live
pnpm doctor
```

`pnpm doctor` is the setup completion check: `bash scripts/setup.sh` installs dependencies and writes placeholder `.env.local` values, but "installed" is not "working." Doctor fails loudly (nonzero exit) on missing or placeholder env, verifies the Clerk publishable key actually decodes to a real host, and probes both Canary and the running app's `/api/health` -- warning (not failing) when the app or Canary aren't reachable yet, since that's expected before `pnpm dev` is running.

Keep `NEXT_PUBLIC_CONVEX_URL` pointed at the same backend you're running. For local development, use `http://localhost:8187`; if you target a remote Convex deployment, local Dagger now syncs the active Convex dev backend before auth-heavy E2E runs so frontend/backend validators stay aligned.

### Backlog Claims
Expand Down
29 changes: 26 additions & 3 deletions content/releases/manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
{
"latest": "0.1.0",
"versions": ["0.1.0"],
"generatedAt": "2026-01-25T00:06:17.994Z"
"latest": "1.15.1",
"versions": [
"1.15.1",
"1.15.0",
"1.14.0",
"1.13.0",
"1.12.0",
"1.11.0",
"1.10.0",
"1.9.1",
"1.9.0",
"1.8.0",
"1.7.0",
"1.6.0",
"1.5.0",
"1.4.0",
"1.3.0",
"1.2.0",
"1.1.3",
"1.1.2",
"1.1.1",
"1.1.0",
"1.0.0",
"0.1.0"
],
"generatedAt": "2026-07-05T00:13:35.955Z"
}
Loading
Loading