Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
85fd498
cmd-line option: don't run sterling if off
tnelson Feb 27, 2025
b84bf65
cleanup: minor cleanup
tnelson Feb 27, 2025
5d6c27d
add: test case for pred->fun/fun->pred recursive
tnelson Feb 27, 2025
688ea1f
[patch] Handle spaces and quotes in filenames w.r.t. run IDs on backe…
tnelson Mar 25, 2025
8a0e270
Feat int minimization (#303)
tnelson Apr 25, 2025
c6688a2
Merge branch 'main' into dev
tnelson Apr 25, 2025
3a304e9
Avoid identifier clash between expander macros and user-defined sigs,…
tnelson May 28, 2025
80a0085
feat: chain of .ts/.js/.cnd files to Sterling
tnelson Jun 14, 2025
8edd9ad
update Sterling
tnelson Jun 14, 2025
fef4882
fix: harden sterling against multiple vis elements
tnelson Jun 15, 2025
2e9d161
feat: module-style imports for helper libraries, temporal helpers
tnelson Jun 20, 2025
39bedb7
breaking: move seq library to actual library. Require open util/seque…
tnelson Jun 21, 2025
c4d6074
fix: possible id-shadowing bug, add option to run without Sterling
tnelson Oct 13, 2025
f99ec90
fix: join parsing, regression test, note on 2nd issue (#311)
tnelson Nov 12, 2025
3ebbf83
Begin integrating typed Racket into core translation layer (#310)
tnelson Nov 12, 2025
f00a33a
Modify the cores pipeline to get cores info to Sterling (#312)
tnelson Nov 13, 2025
4a1ac94
Refactoring options and imports (#316)
tnelson Dec 13, 2025
d00fda7
Refactor AST, additional static types (#318)
tnelson Jan 16, 2026
d993d29
Update to CnD Sterling v. 2.0.4 (#319)
tnelson Jan 16, 2026
c134d0f
types: small type augmentation feat. Claude
tnelson Jan 17, 2026
114dda9
Jan26 fixes (#320)
tnelson Jan 22, 2026
3178a9e
Update documentation links and installation instructions
tnelson Jan 22, 2026
4d2fa2c
Merge main into dev, feat. Claude
tnelson Jan 26, 2026
2a58aaf
update: Sterling version, fix e2e test
tnelson Jan 26, 2026
9d65ddf
update sterling to v2.1.2
tnelson Jan 31, 2026
f6c99aa
Updating Sterling version (#322)
sidprasad Jan 31, 2026
38552c4
Updating sterling version (#323)
sidprasad Feb 1, 2026
3ab323c
fix: test_keep last was misreporting failures
tnelson Feb 3, 2026
5b5af56
fix: bad run-closing on forge_error tests; improve func name
tnelson Feb 4, 2026
d40e99b
fix: detect fun/pred parameter names that shadow sig/fields
tnelson Feb 4, 2026
c5752b4
tests: add more shadowing error tests
tnelson Feb 4, 2026
18cabb7
Merge main into dev, keeping dev's Sterling
tnelson Feb 4, 2026
bde0fc0
Merge branch 'dev' into fixes_feb26
tnelson Feb 4, 2026
1a147ae
tests: further work on e2e suite, check atoms
tnelson Feb 4, 2026
9afcba6
Sterling version update (#326)
sidprasad Feb 5, 2026
33fcb1c
Merge branch 'fixes_feb26' into dev
tnelson Feb 5, 2026
bfbf96e
fix: shadowing error -> warning, avoid BC
tnelson Feb 5, 2026
667382d
Merge branch 'main' into dev
tnelson Feb 5, 2026
2222fb9
Sig.Sig case
tnelson Feb 6, 2026
6bcadcd
Updating Sterling to version with projection fixes (#328)
sidprasad Feb 9, 2026
ad07437
Updating Sterling version (#330)
sidprasad Feb 9, 2026
dbb3dd1
fix: convert bounds errors to raise-forge-error with improved messages
tnelson Feb 21, 2026
c185cd3
fix: hoist declarations in reader so run/check can forward-reference …
tnelson Feb 21, 2026
250a988
fix: clear breaker state on bounds error to prevent leak between runs
tnelson Feb 21, 2026
d2bcca7
fix: box join sig-as-field now gives Froglet-specific error
tnelson Feb 21, 2026
7dadf90
fix: field 'in' bounds now error on atoms not in sig
tnelson Feb 21, 2026
fb8c114
feat: bounds partition optimization with regression tests
tnelson Feb 25, 2026
0d054ac
add NS benchmark and clean up investigatory comments
tnelson Feb 25, 2026
926928f
Merge branch 'dev' into feb26_fixes_b
tnelson Feb 25, 2026
3c91af0
fix: comment
tnelson Feb 26, 2026
a8218e6
fix: adjust comments, one test fix
tnelson Feb 26, 2026
662b8ef
tests: fix comment, add cases
tnelson Feb 27, 2026
60eccdd
Sterling Temporal + Projection support (#332)
sidprasad Mar 5, 2026
1355683
Fix abac regression, native Linux Glucose, test refactor (#333)
tnelson Mar 6, 2026
6345569
Merge branch 'main' into dev
tnelson Mar 6, 2026
e127124
add: more e2e tests
tnelson Mar 6, 2026
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
105 changes: 94 additions & 11 deletions .github/workflows/continuousIntegration.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,112 @@
name: Continuous Integration

on:
pull_request: ~
pull_request: ~
push:
branches:
- main
- dev

# Docker images for various Racket versions available on DockerHub
# as racket/racket:<version>
# (https://github.com/racket/docker)
# We use features that require at least 8.4; try to use latest.

jobs:
forge-tests:
check-test-coverage:
runs-on: ubuntu-latest
name: check-test-coverage
steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Verify all test subdirs are in CI matrix
run: |
# Extract all tests/* paths mentioned in the workflow file itself.
# This avoids maintaining a separate list — the matrix IS the source of truth.
workflow=".github/workflows/continuousIntegration.yml"
covered=$(grep -oE 'tests/[a-zA-Z0-9_-]+(/[a-zA-Z0-9_-]+)?' "$workflow" \
| sort -u)

# Check tests/forge/ subdirectories
actual=$(cd forge && find tests/forge -mindepth 1 -maxdepth 1 -type d | sort)
missing=$(comm -23 <(echo "$actual") <(echo "$covered"))
if [ -n "$missing" ]; then
echo "ERROR: tests/forge/ subdirectories not covered by CI:"
echo "$missing"
echo ""
echo "Add them to a test-dir entry in $workflow"
exit 1
fi

# Check top-level test directories too
actual_top=$(cd forge && find tests -mindepth 1 -maxdepth 1 -type d | sort)
missing_top=$(comm -23 <(echo "$actual_top") <(echo "$covered"))
if [ -n "$missing_top" ]; then
echo "ERROR: top-level test directories not covered by CI:"
echo "$missing_top"
echo ""
echo "Add them to a test-dir entry in $workflow"
exit 1
fi

echo "All test directories are covered by CI."

test-forge:
needs: check-test-coverage
runs-on: ubuntu-latest
container: docker://karimmouline/forge-dev-img:latest
strategy:
fail-fast: false
matrix:
include:
- name: forge-1
test-dir: tests/forge/other
- name: forge-2
test-dir: tests/forge/bounds tests/forge/domains tests/forge/eval-model tests/forge/examples tests/forge/expressions tests/forge/formulas tests/forge/fuzz tests/forge/ints tests/forge/library tests/forge/relations tests/forge/sigs tests/forge/target
- name: forge-core
test-dir: tests/forge-core tests/forge-functional
- name: smt
test-dir: tests/smt
- name: other
test-dir: tests/error tests/froglet tests/srclocs tests/temporal
name: test-${{ matrix.name }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Racket
uses: Bogdanp/setup-racket@v1.14
with:
version: '8.15'

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq libcairo2-dev libpango1.0-dev libgdk-pixbuf-2.0-dev

- name: Install cvc5
if: matrix.name == 'smt'
run: |
wget -q https://github.com/cvc5/cvc5/releases/download/cvc5-1.2.0/cvc5-Linux-x86_64-static.zip
unzip -q cvc5-Linux-x86_64-static.zip
echo "$PWD/cvc5-Linux-x86_64-static/bin" >> $GITHUB_PATH

- name: Cache Racket packages
uses: actions/cache@v4
with:
path: ~/.local/share/racket
key: racket-pkgs-${{ hashFiles('forge/info.rkt') }}
restore-keys: racket-pkgs-

- name: Install Forge
run: |
raco pkg install --auto --no-docs ./forge
raco pkg update --auto --no-docs --batch ./forge 2>/dev/null || raco pkg install --auto --no-docs ./forge

- name: Run tests
run: |
cd forge/
chmod +x run-tests.sh
./run-tests.sh tests/
for dir in ${{ matrix.test-dir }}; do
./run-tests.sh "$dir"
done
10 changes: 9 additions & 1 deletion forge/domains/abac/runner.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,18 @@
(list (= r (atom 'True)))]
[(equal? (relation-name r) "Request")
(list (= r (atom 'Request)))]
; Boolean fields (target sig is True)
[(equal? (relation-name r) "training")
(list (in r (-> (+ atoms) (atom 'True))))]
[(equal? (relation-name r) "audit")
(list (in r (-> (+ atoms) (atom 'True))))]
; owner maps File -> Subject; Subject's upper bound is the universe atoms
[(equal? (relation-name r) "owner")
(list (in r (-> (+ atoms) (+ atoms))))]
; Everything else:
[(equal? 1 (relation-arity r))
(list (in r (+ atoms)))]
[(equal? 2 (relation-arity r))
[(equal? 2 (relation-arity r))
(list (in r (-> (+ atoms) (+ (atom 'True) (+ atoms)))))]
[else
(raise-user-error (format "Error: relation ~a had invalid arity" r))]))
Expand Down
4 changes: 0 additions & 4 deletions forge/domains/abac/tests/info.rkt

This file was deleted.

1 change: 1 addition & 0 deletions forge/domains/crypto/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CI regression tests derived from these examples live in forge/tests/forge/domains/crypto/.
11 changes: 11 additions & 0 deletions forge/e2e/fixtures/projection-model.frg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#lang forge

-- Model with multiple sig types suitable for projection testing in Sterling
sig Epoch {}
sig Process {
action: set Epoch -> Process
}

projectionRun: run {
some action
} for exactly 2 Process, exactly 3 Epoch
13 changes: 13 additions & 0 deletions forge/e2e/fixtures/temporal-model.frg
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#lang forge/temporal

-- Minimal temporal model for e2e testing of Sterling temporal controls
sig Process {}
var sig Active extends Process {}

pred init { some Active }
pred step { Active' != Active }

temporalRun: run {
init
always step
} for exactly 3 Process
161 changes: 161 additions & 0 deletions forge/e2e/tests/sterling-temporal-projection.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { test, expect, Page } from '@playwright/test';
import { startForge, ForgeInstance, selectAndRunCommand } from '../helpers/forge-runner';
import { TIMEOUT_GRAPH_LAYOUT } from '../helpers/constants';

// Sterling sidebar buttons and drawer titles share the same DOM text (e.g., both
// contain "Time"), but the sidebar button appears first in DOM order. The drawer
// title is visually uppercased via CSS text-transform but the underlying text is
// the same. Using .first() reliably targets the sidebar button.
async function openSidebarDrawer(page: Page, label: string) {
await page.getByText(label, { exact: true }).first().click();
await page.waitForTimeout(500);
}

test.describe('Sterling Temporal Controls', () => {
let forge: ForgeInstance;

test.afterEach(async () => {
if (forge) {
forge.cleanup();
}
});

test('temporal controls are accessible outside the layout drawer', async ({ page }) => {
forge = await startForge('e2e/fixtures/temporal-model.frg');
await page.goto(forge.sterlingUrl);
await selectAndRunCommand(page, 'temporalRun');
await page.waitForTimeout(TIMEOUT_GRAPH_LAYOUT);

// "Time" and "Layout" should both be visible as separate sidebar entries
await expect(page.getByText('Time', { exact: true }).first()).toBeVisible({ timeout: 5000 });
await expect(page.getByText('Layout', { exact: true }).first()).toBeVisible({ timeout: 5000 });

// Negative test: open the Layout drawer and verify temporal controls are NOT there
await openSidebarDrawer(page, 'Layout');
await expect(page.locator('#temporal-policy-select')).not.toBeVisible();

// Now open the Time drawer and verify temporal controls ARE there
await openSidebarDrawer(page, 'Time');
await expect(page.locator('#temporal-policy-select')).toBeVisible({ timeout: 5000 });
});

test('time stepper is visible for temporal instances', async ({ page }) => {
forge = await startForge('e2e/fixtures/temporal-model.frg');
await page.goto(forge.sterlingUrl);
await selectAndRunCommand(page, 'temporalRun');
await page.waitForTimeout(TIMEOUT_GRAPH_LAYOUT);

await openSidebarDrawer(page, 'Time');

// The stepper shows a "State N/M" label
await expect(page.getByText(/^State \d+\/\d+$/)).toBeVisible({ timeout: 5000 });

// Navigation buttons should be present
await expect(page.getByRole('button', { name: 'First State' }).first()).toBeVisible();
await expect(page.getByRole('button', { name: 'Previous State' }).first()).toBeVisible();
});

test('time stepping advances the state index', async ({ page }) => {
forge = await startForge('e2e/fixtures/temporal-model.frg');
await page.goto(forge.sterlingUrl);
await selectAndRunCommand(page, 'temporalRun');
await page.waitForTimeout(TIMEOUT_GRAPH_LAYOUT);

await openSidebarDrawer(page, 'Time');

// Should start at State 1/N
await expect(page.getByText(/^State 1\/\d+$/)).toBeVisible({ timeout: 5000 });

// The forward button has a duplicated aria-label ("First State") due to a
// Sterling bug. It's the second button with that label in the stepper group.
const forwardButton = page.getByRole('button', { name: 'First State' }).nth(1);
await forwardButton.click();
await page.waitForTimeout(500);

// Should now show State 2/N
await expect(page.getByText(/^State 2\/\d+$/)).toBeVisible({ timeout: 5000 });
});

test('compare states mode is available', async ({ page }) => {
forge = await startForge('e2e/fixtures/temporal-model.frg');
await page.goto(forge.sterlingUrl);
await selectAndRunCommand(page, 'temporalRun');
await page.waitForTimeout(TIMEOUT_GRAPH_LAYOUT);

await openSidebarDrawer(page, 'Time');

const compareButton = page.getByRole('button', { name: 'Compare States' });
await expect(compareButton).toBeVisible({ timeout: 5000 });

await compareButton.click();
await page.waitForTimeout(500);

// Button text changes and comparison instructions appear
await expect(page.getByText('Compare Mode')).toBeVisible({ timeout: 3000 });
await expect(page.getByText('Click states to compare side-by-side')).toBeVisible({ timeout: 3000 });
});

test('temporal policy can be changed', async ({ page }) => {
forge = await startForge('e2e/fixtures/temporal-model.frg');
await page.goto(forge.sterlingUrl);
await selectAndRunCommand(page, 'temporalRun');
await page.waitForTimeout(TIMEOUT_GRAPH_LAYOUT);

await openSidebarDrawer(page, 'Time');

const policySelect = page.locator('#temporal-policy-select');
await expect(policySelect).toBeVisible({ timeout: 5000 });

// Verify expected policy options exist
const options = await policySelect.locator('option').allTextContents();
expect(options.length).toBeGreaterThan(1);
expect(options).toContain('Ignore History');
expect(options).toContain('Stability');
});
});

test.describe('Sterling Projection Controls', () => {
let forge: ForgeInstance;

test.afterEach(async () => {
if (forge) {
forge.cleanup();
}
});

test('projection controls are accessible outside the layout drawer', async ({ page }) => {
forge = await startForge('e2e/fixtures/projection-model.frg');
await page.goto(forge.sterlingUrl);
await selectAndRunCommand(page, 'projectionRun');
await page.waitForTimeout(TIMEOUT_GRAPH_LAYOUT);

// "Projections" and "Layout" should both be visible as separate sidebar entries
await expect(page.getByText('Projections', { exact: true }).first()).toBeVisible({ timeout: 5000 });
await expect(page.getByText('Layout', { exact: true }).first()).toBeVisible({ timeout: 5000 });

// Negative test: open Layout drawer and verify projection controls are NOT there
await openSidebarDrawer(page, 'Layout');
await expect(page.getByText('Add Projection')).not.toBeVisible();

// Open projections drawer and verify controls ARE there
await openSidebarDrawer(page, 'Projections');
await expect(page.getByText('Add Projection')).toBeVisible({ timeout: 5000 });
});

test('projection drawer lists projectable types', async ({ page }) => {
forge = await startForge('e2e/fixtures/projection-model.frg');
await page.goto(forge.sterlingUrl);
await selectAndRunCommand(page, 'projectionRun');
await page.waitForTimeout(TIMEOUT_GRAPH_LAYOUT);

await openSidebarDrawer(page, 'Projections');

await page.getByText('Add Projection').click();
await page.waitForTimeout(500);

// Projectable types appear as "+ TypeName" buttons in the Add Projection UI
const hasEpoch = await page.getByRole('button', { name: '+ Epoch' }).isVisible();
const hasProcess = await page.getByRole('button', { name: '+ Process' }).isVisible();
expect(hasEpoch || hasProcess).toBe(true);
});
});
2 changes: 1 addition & 1 deletion forge/info.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
; This includes outdated modules but also examples.
(define compile-omit-paths '("example" "examples" "doc" "tests" "check-ex-spec/demo"
"OLD" "pardinus-cli/out" "kodkod-cli/out" "check-ex-spec/examples"
"amalgam/tests" "amalgam" "domains/crypto/examples" "domains/abac/tests"
"amalgam/tests" "amalgam" "domains/crypto/examples"
))


Expand Down
Binary file removed forge/pardinus-cli/jar/libminisatprover.so
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading