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
58 changes: 58 additions & 0 deletions .github/workflows/copy-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Copy gate

on:
pull_request:
paths:
- 'frontend/**'
- 'scripts/copy-lint/**'
- '.github/workflows/copy-gate.yml'
- 'scripts/copy-lint/baseline.json'
push:
branches: [main]
paths:
- 'frontend/**'
- 'scripts/copy-lint/**'
- '.github/workflows/copy-gate.yml'
- 'scripts/copy-lint/baseline.json'

permissions:
contents: read

jobs:
gate:
name: Copy gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
with:
node-version: '20'
- run: node --test scripts/copy-lint/check-copy.test.mjs
- name: Read base copy baseline
env:
BASE_REF: ${{ github.base_ref }}
BEFORE_SHA: ${{ github.event.before }}
run: |
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
base_ref="origin/${BASE_REF}"
else
if [[ "$BEFORE_SHA" =~ ^0{40}$ ]]; then
exit 0
fi
base_ref="$BEFORE_SHA"
fi
base_commit=$(git rev-parse --verify "${base_ref}^{commit}") || exit 1
baseline_path="scripts/copy-lint/baseline.json"
if git cat-file -e "${base_commit}:${baseline_path}" 2>/dev/null; then
git show "${base_commit}:${baseline_path}" > "$RUNNER_TEMP/base-baseline.json" || exit 1
else
baseline_entry=$(git ls-tree -r --name-only "$base_commit" -- "$baseline_path") || exit 1
if [ -n "$baseline_entry" ]; then
echo "Unable to read base copy baseline" >&2
exit 1
fi
fi
- run: node scripts/copy-lint/check-copy.mjs . --gate --github --base-baseline "$RUNNER_TEMP/base-baseline.json"
51 changes: 46 additions & 5 deletions .github/workflows/engine-review-pass.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ on:
types: [opened, synchronize, reopened]
pull_request_review:
types: [submitted, dismissed]
issue_comment:
types: [created, edited]

# Review bursts must not cancel in-progress runs.
concurrency:
group: engine-review-pass-${{ github.event.pull_request.number }}
group: engine-review-pass-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: false

permissions:
statuses: write
pull-requests: read
issues: read
contents: read

jobs:
Expand All @@ -30,7 +33,20 @@ jobs:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pr = context.payload.pull_request;
let pr = context.payload.pull_request;

if (!pr) {
if (!context.payload.issue?.pull_request) {
core.info('Issue comment is not on a pull request. Skipping.');
return;
}
const prResponse = await github.rest.pulls.get({
owner,
repo,
pull_number: context.payload.issue.number,
});
pr = prResponse.data;
}

if (!pr) {
core.setFailed('Could not determine pull request from event payload.');
Expand All @@ -50,6 +66,9 @@ jobs:
'grok',
'muse',
'kimi',
'pi',
'deepseek',
'hermes',
'visual-designer',
'product-designer',
]);
Expand Down Expand Up @@ -107,7 +126,29 @@ jobs:

core.info(`Total reviews retrieved: ${reviews.length}`);

const eligibleReviews = reviews.filter((r) => {
core.info(`Fetching comments for pull request #${prNumber}...`);

const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: prNumber,
per_page: 100,
});

core.info(`Total issue comments retrieved: ${comments.length}`);

const normalizedComments = comments.map((c) => ({
id: c.id,
body: c.body,
submitted_at: c.created_at,
author_association: c.author_association,
user: c.user,
state: 'COMMENTED',
}));

const allReviews = reviews.concat(normalizedComments);

const eligibleReviews = allReviews.filter((r) => {
if (r.state === 'DISMISSED' || !r.submitted_at || !r.body) {
return false;
}
Expand All @@ -131,7 +172,7 @@ jobs:
});

// The marker must be alone on its own line.
const markerRegex = /^REVIEW: (PASS|BLOCKED|FAIL) ([A-Za-z0-9._-]+) ([0-9a-f]{7,40})\s*$/;
const markerRegex = /^REVIEW: (PASS|BLOCKED|FAIL) ([A-Za-z0-9._-]+) ([0-9a-f]{40})\s*$/;
const headShaLower = headSha.toLowerCase();
const authorEngineLower = authorEngine.toLowerCase();

Expand All @@ -154,7 +195,7 @@ jobs:
continue;
}

if (!headShaLower.startsWith(markerSha.toLowerCase())) {
if (markerSha.toLowerCase() !== headShaLower) {
core.info(`Ignoring marker for non-matching SHA ${markerSha} on head ${headSha}`);
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Agreed sending time per supplier facility.

Mirrors supabase/migrations/20261024080000_supplier_agreed_sending_hours.sql
exactly: adds public.supplier_facilities.agreed_sending_hours, integer NULL,
CHECK (agreed_sending_hours > 0). No backfill. The supplier data quality view
counts a CTE record as late when it is received more than this many hours
after its event time.

Additive and idempotent (ADD COLUMN IF NOT EXISTS with an inline CHECK that is
only created together with the column), so the plain SQL file can run ahead of
it. Downgrade drops only what this revision created.

Revision ID: b005ddbf1dba
Revises: 41f96588d87c
Create Date: 2026-10-24
"""

from typing import Sequence, Union

from alembic import op

revision: str = "b005ddbf1dba" # pragma: allowlist secret
down_revision: Union[str, Sequence[str], None] = "41f96588d87c" # pragma: allowlist secret
branch_labels = None
depends_on = None


def upgrade() -> None:
op.execute(
"""
alter table public.supplier_facilities
add column if not exists agreed_sending_hours integer null
constraint supplier_facilities_agreed_sending_hours_check
check (agreed_sending_hours > 0);
"""
)


def downgrade() -> None:
op.execute(
"ALTER TABLE public.supplier_facilities DROP CONSTRAINT IF EXISTS supplier_facilities_agreed_sending_hours_check"
)
op.execute("ALTER TABLE public.supplier_facilities DROP COLUMN IF EXISTS agreed_sending_hours")
53 changes: 53 additions & 0 deletions docs/schema/expected_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5525,6 +5525,55 @@
}
]
},
"public.data_gap_events": {
"columns": {
"closed_at": {
"nullable": true,
"type": "timestamp"
},
"closing_record_hash": {
"nullable": true,
"type": "text"
},
"closing_record_id": {
"nullable": true,
"type": "uuid"
},
"cte_type": {
"nullable": false,
"type": "text"
},
"gap_key": {
"nullable": false,
"type": "text"
},
"gap_kind": {
"nullable": false,
"type": "text"
},
"id": {
"nullable": false,
"type": "uuid"
},
"lot_code": {
"nullable": false,
"type": "text"
},
"opened_at": {
"nullable": false,
"type": "timestamp"
},
"supplier_key": {
"nullable": false,
"type": "text"
},
"tenant_id": {
"nullable": false,
"type": "uuid"
}
},
"foreign_keys": []
},
"public.food_traceability_list": {
"columns": {
"applicable_ctes": {
Expand Down Expand Up @@ -6758,6 +6807,10 @@
},
"public.supplier_facilities": {
"columns": {
"agreed_sending_hours": {
"nullable": true,
"type": "integer"
},
"city": {
"nullable": false,
"type": "text"
Expand Down
17 changes: 15 additions & 2 deletions docs/security/route-security-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2956,6 +2956,19 @@
"storage_boundary": "admin-postgres:jwt-tenant-or-session-membership-tenant",
"test_adapter": "tenant_dual_session_csrf"
},
{
"operation": "admin:PUT:/v1/supplier/facilities/{facility_id}/agreed-sending-hours",
"owner": "admin",
"method": "PUT",
"path": "/v1/supplier/facilities/{facility_id}/agreed-sending-hours",
"classification": "tenant-scoped",
"authentication": "bearer-token-or-opaque-session-cookie",
"required_permissions": [
"authenticated"
],
"storage_boundary": "admin-postgres:jwt-tenant-or-session-membership-tenant",
"test_adapter": "tenant_dual_session_csrf"
},
{
"operation": "admin:POST:/v1/supplier/facilities/{facility_id}/cte-events",
"owner": "admin",
Expand Down Expand Up @@ -4944,13 +4957,13 @@
}
],
"summary": {
"enabled_operation_count": 257,
"enabled_operation_count": 258,
"disabled_operation_count": 180,
"disabled_unique_path_count": 163,
"enabled_by_classification": {
"global-sysadmin": 16,
"public": 30,
"tenant-scoped": 204,
"tenant-scoped": 205,
"workload-only": 7
}
}
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"src/lib/capabilities.ts"
]
},
"//knip": "src/lib/capabilities.ts is read by scripts/copy-lint/check-copy.mjs (the SEL-452 copy gate registry), not imported by frontend code.",
"//": "Pinning policy (#569): next, eslint-config-next, @sentry/nextjs, and @supabase/supabase-js are pinned to exact versions (no ^ or ~ prefix) because breaking changes in these packages have caused production incidents. Bump them deliberately after testing. All other deps use ^ for minor/patch auto-updates.",
"dependencies": {
"@radix-ui/react-checkbox": "^1.4.1",
Expand Down
11 changes: 0 additions & 11 deletions frontend/public/samples/sample_recall_drill.csv

This file was deleted.

8 changes: 4 additions & 4 deletions frontend/src/__tests__/components/demo-banner.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import { DemoBanner } from '@/components/control-plane/demo-banner';
describe('DemoBanner Component', () => {
it('renders when visible=true', () => {
render(<DemoBanner visible={true} />);
expect(screen.getByText('Sample Data')).toBeInTheDocument();
expect(screen.getByText('No data yet')).toBeInTheDocument();
});

it('shows "Sample Data" text and description', () => {
it('shows the empty-state text and a next-step link', () => {
render(<DemoBanner visible={true} />);
expect(screen.getByText('Sample Data')).toBeInTheDocument();
expect(screen.getByText(/backend unavailable/i)).toBeInTheDocument();
expect(screen.getByText('No data yet')).toBeInTheDocument();
expect(screen.getByRole('link', { name: /connect real data/i })).toHaveAttribute('href', '/ingest');
});

it('does not render when visible=false', () => {
Expand Down
Loading
Loading