fix: harden Claro diagnostics and runtime guards

Preserve source locations, validate malformed JSON, and add opt-in resource limits. Include executable error-guide coverage, learner pilot materials, native Windows acceptance helpers, and updated CI/documentation.
This commit is contained in:
Hermes Agent
2026-10-25 02:42:53 +00:00
parent bf230f354e
commit ed23524817
42 changed files with 1128 additions and 176 deletions
+6
View File
@@ -35,6 +35,12 @@ jobs:
run: python3 tools/validate_c89.py
- name: Validate executable learning path
run: python3 tools/validate_learning_syntax.py
- name: Validate diagnostics, error guide and learner pilot code
run: |
python3 tools/validate_error_locations.py
python3 tools/validate_error_guide.py
python3 tools/validate_learner_pilot.py
python3 tools/validate_resource_guards.py
- name: Validate memory and malformed-input reliability
run: python3 tools/validate_reliability.py
- name: Validate CI workflow coverage
+2
View File
@@ -1,3 +1,5 @@
# Preserve DOS line endings for the Windows 98 COMMAND.COM build inputs.
build98.bat -text whitespace=cr-at-eol
build98.rsp -text whitespace=cr-at-eol
test98.bat -text whitespace=cr-at-eol
testwin.bat -text whitespace=cr-at-eol
+12
View File
@@ -59,6 +59,18 @@ and test formatting, numeric boundaries, and simple functions. Do not introduce
C99 syntax or depend on generated project headers. See [C89 notes](docs/C89.md).
## Learning-first charter
Claro is a learning tool first and a general-purpose language second. Keep the
core small, predictable, and beginner-focused; prioritize executable lessons,
useful diagnostics, and reliability over feature breadth. Teach one recommended
spelling per concept while preserving supported compatibility forms. Do not
claim accessibility validation without feedback from intended learners. The
[learning-first charter](docs/LEARNING_FIRST.md) is the current product guidance.
Older ambitious plans are retained as historical context, not active priorities;
they do not imply that existing features are removed. See the current
[learning-first roadmap](docs/ROADMAP.md).
## Beginner syntax consistency
Follow [the syntax guide](docs/SYNTAX.md) in introductory teaching material.
+2 -1
View File
@@ -8,7 +8,7 @@ Claro helps beginners learn programming through readable commands, simple rules,
and small programs. Accessibility for people with learning disabilities is a
core design goal; it still needs testing with learners.
Development version: **v2026.10.20.223203**
Development version: **v2026.10.25.014405**
This is a development snapshot, not a newly published release. See
[current status](docs/CURRENT_STATUS.md) and the dated
@@ -51,6 +51,7 @@ concept. Older forms remain supported in a separate compatibility reference.
- [Documentation](docs/README.md) — learning guides and reference.
- [Current roadmap](docs/ROADMAP.md) — the small set of active priorities.
- [Learning-first rules](docs/LEARNING_FIRST.md) — how Claro is designed.
- [Active learning-first roadmap](docs/ROADMAP.md) — current priorities; older platform plans are historical context.
- [Contributing](CONTRIBUTING.md) — source layout, builds, and validation.
Existing features and compatibility syntax remain supported; they are not
+3
View File
@@ -5,6 +5,9 @@ Accessibility for learners with learning disabilities is a design goal and is
**not yet validated** by intended learners. Passing tests does not establish it.
Use [current status](CURRENT_STATUS.md) for scope, not older feature roadmaps.
The [ready-to-run pilot kit](LEARNER_PILOT.md) includes short tasks and a
[blank feedback form](LEARNER_FEEDBACK_TEMPLATE.md). No real session is implied.
## Before a session
- Use a build whose platform was actually tested; check `claro --version`.
+2 -2
View File
@@ -1,6 +1,6 @@
# Claro Command Line
Claro v2026.10.20.223203 supports these main commands:
Claro v2026.10.25.014405 supports these main commands:
```bash
claro file.claro
@@ -58,7 +58,7 @@ claro package doctor
claro package lock
```
The v2026.10.20.223203 package command is a safer local project-file helper. It creates `claro.project`, `claro.lock`, and local folders under `packages/`. Package names may appear only once in `claro.project`; `claro package doctor` reports duplicate entries so the lockfile stays unambiguous. It also requires the project manifest to have exactly one non-empty `name:` field. It is not yet an online package registry.
The v2026.10.25.014405 package command is a safer local project-file helper. It creates `claro.project`, `claro.lock`, and local folders under `packages/`. Package names may appear only once in `claro.project`; `claro package doctor` reports duplicate entries so the lockfile stays unambiguous. It also requires the project manifest to have exactly one non-empty `name:` field. It is not yet an online package registry.
## IDE metadata
+1 -1
View File
@@ -1,6 +1,6 @@
# Claro Complete Platform Roadmap
Current release: **Claro v2026.10.20.223203**
Current release: **Claro v2026.10.25.014405**
This roadmap tracks the larger goal: Claro as a complete beginner-friendly programming platform, not only a toy scripting interpreter.
+10 -6
View File
@@ -1,6 +1,6 @@
# Claro current status
Development version: **v2026.10.20.223203**
Development version: **v2026.10.25.014405**
Claro is a **learning tool first**. The development timestamp identifies source;
it does not mean that a release was published or every platform was tested.
@@ -28,8 +28,9 @@ call depth and the learning path under leak-enabled sanitizers. See
Earlier failure logs are history, not current unresolved findings.
Claro runs trusted scripts with the user's permissions. It is **not a sandbox**.
Intentional loops can run indefinitely, and global file/JSON/CPU budgets are not
provided. See [actual runtime guards](RUNTIME_LIMITS.md).
Loops can run indefinitely by default. Opt-in step and READ FILE budgets are
available; parsed JSON nesting is capped. Global file/output/memory and CPU-time
budgets are not provided. See [actual runtime guards](RUNTIME_LIMITS.md).
## Platforms — do not confuse compilation with OS acceptance
@@ -55,9 +56,12 @@ They are **not active expansion priorities** without a separate request.
## What is next
1. Observe actual beginners and improve instructions/error recovery.
2. Verify the Windows builds on their real target operating systems.
3. Review the accumulated local changes and publish only when authorized.
1. Use the [learner pilot kit](LEARNER_PILOT.md) with a willing person; code checks
are not human feedback. Source-located expression diagnostics now have regressions.
2. Use the [native Windows kit](NATIVE_WINDOWS_TESTING.md) on an authorized target.
Wine is explicitly rejected by its OS evidence helper.
3. Hosted CI/release delivery needs authenticated Gitea access and enabled Actions.
See the [six-track status](SIX_TRACK_STATUS.md) for current blockers.
Follow the [small active roadmap](ROADMAP.md). The
[previous detailed status](history/CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md) and
+127 -12
View File
@@ -1,29 +1,144 @@
# Claro Errors
# Errors and small repairs
Claro tries to report mistakes in beginner-friendly language.
An error starts with `filename:line:`. Open that file and find that line.
The line belongs to the function or imported file where the error happened,
not automatically to the caller. Caught errors expose the same origin through
`LASTERRORFILE` and `LASTERRORLINE`.
Examples:
Use `claro check my_program.claro` for command/block mistakes; running the
program also detects runtime expression/value errors. A checker is not a sandbox.
The examples below are executed by `tools/validate_error_guide.py`.
## Quote
Save as `quote.claro`. Run `claro quote.claro`.
```claro
SAY "Start"
SAY "unfinished
```
Message:
```text
I do not know the command "PRNT". Did you mean "SAY"?
quote.claro:2: Text is missing a closing quote. Add a double quote at the end.
```
One repair:
```claro
SAY "Start"
SAY "finished"
```
## Operand
Save as `operand.claro`. Run `claro operand.claro`.
```claro
SET score 1
SAY score +
```
Message:
```text
SET needs TO.
Try:
SET name TO "Alex"
operand.claro:2: This expression needs a value after the operator.
```
One repair:
```claro
SET score 1
SAY score + 1
```
## Command
Save as `command.claro`. Run `claro check command.claro`.
```claro
PRNT "Hello"
```
Message:
```text
IF block opened here is missing ENDIF
command.claro:1: I do not know the command "PRNT". Did you mean "SAY"?
```
One repair:
```claro
SAY "Hello"
```
## Assignment
Save as `assignment.claro`. Run `claro check assignment.claro`.
```claro
SET name
```
Message:
```text
I do not know the variable "socre" yet. Did you mean "score"?
assignment.claro:1: SET needs a name and a value. Try: SET name "Alex"
```
For safer learning, run this before running a new program:
One repair:
```bash
./claro check my_program.claro
```claro
SET name "Alex"
SAY name
```
## Choice
Save as `choice.claro`. Run `claro check choice.claro`.
```claro
IF YES
SAY "Yes"
```
Message:
```text
choice.claro:1: IF block opened here needs END
```
One repair:
```claro
IF YES
SAY "Yes"
END
```
## Variable
Save as `variable.claro`. Run `claro variable.claro`.
```claro
SET score 10
SAY socre
```
Message:
```text
variable.claro:2: I do not know the variable "socre" yet.
```
One repair:
```claro
SET score 10
SAY score
```
These teach short `SET` and `END`. Older forms remain supported in
[compatibility syntax](COMPATIBILITY.md), not required as repairs.
For a caught error, read its metadata before another error changes it.
+1 -1
View File
@@ -1,6 +1,6 @@
# Claro Graphics Status
Current release: **Claro v2026.10.20.223203**
Current release: **Claro v2026.10.25.014405**
Graphics support is intentionally separated from the stable beginner console workflow.
+32
View File
@@ -0,0 +1,32 @@
# Learner feedback — blank observation form
Status: NOT RUN. Leave observations blank until a consenting person participates.
Do not fill this form with predictions, agent simulations or invented participants.
- Anonymous session ID:
- Consent to retain anonymous notes: yes / no
- Build version and artifact checksum:
- OS/device and assistive tools (only if voluntarily relevant):
- Tasks attempted / skipped:
- Breaks, pacing and requested adjustments:
For each attempted task record:
- What the learner tried or said (anonymous, no health information):
- What worked independently:
- What help or clarification was requested:
- Exact confusing instruction/error and its filename/line:
- Whether the suggested repair helped:
- Learner's suggested change:
Use plain observations, not ability rankings. Do not infer a medical diagnosis.
Debrief:
- Clearest part:
- Most confusing part:
- One small change to try:
- Follow-up session agreed, if any:
- Notes retention/deletion agreement:
Automated example execution verifies code/output only, not these observations.
+92
View File
@@ -0,0 +1,92 @@
# Beginner pilot — observer kit
**Status: prepared, not learner-validated.** No participant feedback has been
collected by these automated checks. Do not infer accessibility from test passes.
Ask for willing participation and permission to keep anonymous notes. Explain
that we are testing the instructions, not the person. Collect no diagnosis,
medical history, real names or sensitive information. Let the learner decline,
ask for help, take breaks or stop. Use any assistive technology they prefer.
Use a platform-tested build, a fresh writable practice folder and reviewed scripts.
Show how to stop/close the interpreter before starting. Do not run downloaded
untrusted scripts. Explain that optional limits are not a sandbox.
Try the first three tasks at the learner's pace. Offer the rest only if wanted.
Let the learner predict, run and change the program. Observe before giving a
hint; never leave someone distressed just to measure independence. Record help
without judging it. Answers/output for observers are in `tests/learner_pilot.json`.
## Say hello
Run this once. Change the message to something you choose and run it again.
```claro
SAY "Hello!"
```
## Ask a name
Run it and type a pretend name. Explain where that name is remembered.
```claro
ASK "Name? " name
SAY "Hello " + name
```
## Change a number
Predict the result, run it, then change 2 to another small number.
```claro
SET score 2
SAY score + 1
```
## Make a choice
Optional: change score to 0. Predict which message appears.
```claro
SET score 2
IF score > 1
SAY "More than one"
ELSE
SAY "One or less"
END
```
## Repeat
Optional: change how many times the message appears. Ask how to stop a stuck run.
```claro
DO 3 TIMES
SAY "Again"
END
```
## Save a file
Optional: use a new writable practice folder. Find the file after running it.
```claro
WRITE FILE "practice.txt" WITH "My first file"
READ FILE "practice.txt" AS text
SAY text
```
## Gentle mistake and recovery
In the hello task, replace SAY with PRNT, run `claro check practice.claro`, and
ask what the message suggests. Restore SAY and rerun. This mistake is optional;
skip it if it adds stress. Do not teach alternative compatibility spellings here.
## Debrief and next change
Ask: What was clear? What was confusing? What would you change? Observe whether
the person can start/run/save, predict a result and use a repair hint. Do not
calculate a disability score or call one session proof of universal accessibility.
Use [the blank feedback form](LEARNER_FEEDBACK_TEMPLATE.md); keep only consented,
anonymous notes, record the exact build/platform, and choose one small improvement.
The [classroom checklist](CLASSROOM_READINESS.md) is a pilot guide, not certification.
+53
View File
@@ -0,0 +1,53 @@
# Native Windows acceptance kit
**Prepared, not executed on a native target.** Wine results do not satisfy this
checklist. A Windows login/authorized VM was not supplied for this run.
Use a **fresh extracted test folder**, not personal documents. Scripts write
`oscheck.txt`, `nver.txt`, `ntests.txt`, `ncheck.txt` and replace their own `native.ok`
marker. Preserve failed logs. A marker alone is not proof: review the OS evidence,
version, all discovered fixtures and every test result together.
## OS evidence helper
`tools/native_platform_probe.c` builds a separate `osprobe.exe`; it is not a
second interpreter source file. With VC6 configured:
```bat
cl /nologo /TC /ML /O1 tools\native_platform_probe.c /Feosprobe.exe /link /SUBSYSTEM:CONSOLE,4.10
```
The probe records numeric OS API fields, detects Wine's exported implementation
entry point, and refuses Wine with exit 2. `--require-win98se` additionally needs
Win32-Windows platform ID and version 4.10 build 2222. A real Windows VM is an
acceptable target; a compatibility layer is not. Old API version reporting on
modern Windows can be affected by application compatibility, so also record the
actual OS/version from its own system information screen. The probe is evidence,
not cryptographic attestation or a substitute for inspecting a real target.
## Automated checks
Put the compiled probe beside the matching interpreter and the complete package.
For Windows 98 SE run [test98.bat](../test98.bat). For modern x64 Windows run
[testwin.bat](../testwin.bat) with the MinGW `claro.exe` build.
These batch scripts avoid modern CMD-only extensions and reject a wrong target
before writing a PASS marker. Review `ntests.txt`: every packaged golden fixture
must appear, with zero failures. `ncheck.txt` must show built-in validation passed.
## Manual target observations — leave blank until actually tested
- Artifact version/SHA-256 and OS/VM identity:
- `oscheck.txt` reviewed, Wine false, target version independently confirmed:
- All packaged fixtures discovered/passed:
- Keyboard input, editing and non-ASCII display observations:
- Save/read a text file in a writable folder:
- File paths containing spaces:
- Start/stop a loop and recover to the console:
- First-hour examples and quiz interaction:
- Missing input or failed command recovery:
- Tester/date, consent to retain anonymous notes, unresolved failures:
Transfer only consented technical logs, not passwords, personal files or medical
information. Do not change an unknown remote machine or install services just to
make a test pass. The native target remains **unverified** until these observations
and actual target logs exist. See [Windows 98 compilation](WINDOWS98.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# Claro Performance and Optimization Notes
Current release: **Claro v2026.10.20.223203**
Current release: **Claro v2026.10.25.014405**
Performance work should come after language behavior is stable and measured. Do not optimize blindly.
+4
View File
@@ -15,6 +15,10 @@
- [Reliability](RELIABILITY.md) and [runtime guards](RUNTIME_LIMITS.md) — tested boundaries, not sandbox guarantees.
- [Portability](PORTABILITY.md) and [Windows 98 / VC6](WINDOWS98.md) — compilation versus target execution.
- [Learner pilot kit](LEARNER_PILOT.md) and [blank feedback](LEARNER_FEEDBACK_TEMPLATE.md)
- [Native Windows acceptance kit](NATIVE_WINDOWS_TESTING.md)
- [Six-track progress and blockers](SIX_TRACK_STATUS.md)
## Look something up
- [Beginner syntax](SYNTAX.md) and [older compatibility forms](COMPATIBILITY.md)
+9
View File
@@ -19,6 +19,15 @@ A development timestamp is not a stable-release designation. Use
[current status](CURRENT_STATUS.md) for the learning scope and
[the release checklist](FINAL_RELEASE_CHECKLIST.md) before publishing.
## Candidate continuation — 2026-10-21
The last pushed source checkpoint is `d55507140660186e594303306647e0f0e9facf76`.
The continuation candidate is a separate development run; see
[six-track status](SIX_TRACK_STATUS.md) for scope and blocked acceptance.
Gitea currently reports Actions disabled, and authenticated runs/release access
was not available for this continuation. No hosted CI success or hosted release
is claimed. Earlier artifact records below retain their original hashes/versions.
## Immutable artifact checkpoints
These are verified build artifacts previously delivered for testing, **not a
+17
View File
@@ -41,6 +41,23 @@ fictional printed list representation. Five isolated ownership tests separate
metadata, field storage and object construction so one leak cannot hide another.
Malformed-expression and nesting tests check failure diagnostics and valid inputs.
## Diagnostics, JSON and opt-in budgets
Expression errors keep source origins across calls, imports and TRY/CATCH.
The error guide is executable-verified. Malformed JSON now fails instead of
hanging, nesting is bounded, control-character round trips are checked, and
Unicode escapes/surrogate pairs are covered. JSON temporaries, serialization
copies and popped import-stack entries have focused leak-enabled regressions.
The optional step budget counts empty loop entries; READ FILE can enforce a
per-read byte cap. These are not process isolation or wall-clock bounds.
`validate_error_locations.py`, `validate_error_guide.py`, `validate_learner_pilot.py`
and `validate_resource_guards.py` run in the full gate and inside the existing
learning-ownership sanitizer check. The reliability runner still counts 35
maintained top-level checks; subcase coverage has expanded, not been relabeled
as 35 new independent platform tests. Human feedback and native Windows tests
remain separate, blocked acceptance tracks.
## Scope
A passing run establishes the exercised boundaries, not universal leak freedom,
+50 -24
View File
@@ -1,32 +1,58 @@
# Current runtime guards
These are implemented guardrails, not a sandbox or a promise that arbitrary
scripts have bounded resource use.
Claro runs trusted scripts with the user's permissions. These guards are **not
a sandbox**, a wall-clock timeout, or proof that arbitrary scripts use bounded resources.
- **Function/method calls:** at most 256 active calls. Excessive recursion raises
a Claro error. Other limits can stop a program earlier.
- **Expressions:** at most 128 nested parser levels, covering parentheses and
unary operations. Excessive nesting produces a diagnostic instead of
continuing recursive parsing.
- **Control blocks:** at most 128 additional nested control-block frames. The
runtime and syntax checker reject deeper blocks. Function-call frames have
their own guard.
- **HTTP response bodies:** limited to 1,048,576 bytes by the existing HTTP path.
## Always-on nesting/response guards
Malformed expressions such as a lone `!`, an unclosed quote/parenthesis,
a missing operand, or trailing expression tokens are rejected by the runtime.
Use `NOT` for logical negation; `!=` remains a supported comparison.
- Function/method calls: at most 256 active calls.
- Expressions and additional nested control blocks: at most 128 levels.
- Parsed JSON: at most 128 nested arrays/objects. A scalar inside 128 containers
is allowed; a 129th container is rejected. Malformed syntax fails with a Claro
diagnostic rather than looping without consuming input. JSON Unicode escapes
and surrogate pairs are decoded; embedded zero characters are rejected because
Claro text uses zero-terminated strings. This is not a general encoding validator.
- HTTP response bodies: the existing 1,048,576-byte cap remains.
Errors raised during expressions keep the executing source file and line,
including imported functions and caught `LASTERRORFILE`/`LASTERRORLINE` metadata.
REPEAT without UNTIL is rejected before accessing a missing terminator.
## Optional guards — disabled by default
`CLARO_STEP_LIMIT` sets a non-negative interpreter-work budget. Zero/unset means
unlimited. It counts source-line dispatches and block/range entries, including
empty loop iterations; it is **not** an exact statement count or elapsed time.
Once exhausted it reports a source-located error suggesting the loop be checked.
The consumed budget is not reset by TRY/CATCH or by calling a function.
`CLARO_READ_FILE_LIMIT` sets the maximum bytes read by each `READ FILE` command.
Zero/unset means unlimited. The exact boundary is accepted; larger input errors
without storing a partial result. It does not cap source loading, package metadata,
all file operations, aggregate memory or output. Invalid/overflowing limit values
fail configuration; supported numeric range depends on the compiler's unsigned long.
For example, in a Windows command prompt before running a reviewed script:
```bat
set CLARO_STEP_LIMIT=10000
set CLARO_READ_FILE_LIMIT=1048576
claro98.exe practice.claro
```
On Linux/macOS shells the equivalent is an environment assignment before `./claro`.
These optional budgets do not interrupt a blocked ASK, a shell command, curl,
or an individual long-running builtin. They are not untrusted-code protection.
## Not provided
The former beta document described `CLARO_STEP_LIMIT`, `CLARO_MAX_FILE_BYTES`,
and `CLARO_MAX_JSON_BYTES`. Those environment controls are not implemented in
the current `src/claro.c`; do not depend on them. The original document is
preserved under [history](history/RUNTIME_LIMITS_BETA25.md).
There is no general CPU/time budget, process isolation, total-allocation limit,
global file/output limit or JSON byte-size control. The old beta names
`CLARO_MAX_FILE_BYTES` and `CLARO_MAX_JSON_BYTES` are still not implemented.
The newer READ FILE guard is deliberately narrower. The former unimplemented
step-limit claim is preserved as history; the current step guard is the explicit
implementation described above, not validation of those older promises.
Ordinary intentional loops can still run indefinitely. This checkpoint does
not establish a general JSON recursion limit, global file/output-size limit,
CPU budget, or untrusted-script protection. Scripts and `RUN COMMAND` execute
with the user's permissions.
See [reliability verification](RELIABILITY.md) for the tested scope.
Without the optional step budget ordinary intentional loops can run indefinitely.
Run only trusted scripts. See [reliability](RELIABILITY.md),
[errors and repairs](ERRORS.md), and the [historical limits](history/RUNTIME_LIMITS_BETA25.md).
+1 -1
View File
@@ -2,7 +2,7 @@
SDL 1.2 support is **not included in the current stable executable**.
The `examples/experimental_sdl/` files are design experiments / future examples only. In Claro v2026.10.20.223203, running them with the included `claro.exe` will fail with:
The `examples/experimental_sdl/` files are design experiments / future examples only. In Claro v2026.10.25.014405, running them with the included `claro.exe` will fail with:
```text
Unknown command: SDL
+61
View File
@@ -0,0 +1,61 @@
# Six-track progress — development candidate v2026.10.21.010934
This is a work/acceptance map, not a stable release or a claim all six external
milestones are complete. Real learner sessions and native OS access were not
supplied in the clarification request. No simulated observations are recorded.
## 1. Error reporting — implemented, regression-tested
Expressions retain file/line origins through functions, imports, UNTIL and
TRY/CATCH metadata. The first failing location is not overwritten by its caller.
Focused tests cover direct, nested and caught paths, including leak-enabled runs.
## 2. Error guide — aligned and executable-verified
The [guide](ERRORS.md) now shows current short SET/END forms and six actual
failure/repair pairs. `tests/error_examples.json` is checked against every guide
code block and against the interpreter's diagnostic and repaired output.
## 3. Beginner testing — kit ready; actual learner acceptance BLOCKED
The [pilot kit](LEARNER_PILOT.md) contains six small starter tasks (three primary,
three optional), observer instructions and a [blank feedback form](LEARNER_FEEDBACK_TEMPLATE.md).
Automated code/output checks are not a person testing the language. A willing,
consenting learner and anonymous real observations are still required.
## 4. Native Windows — test kit prepared; target acceptance BLOCKED
[Native checks](NATIVE_WINDOWS_TESTING.md) require an authorized Windows login or
real Windows 98 SE VM. No such access was supplied. The OS helper explicitly
rejects Wine and can require Win98 SE 4.10.2222 before a PASS marker is written.
Compiler/Wine checks are separate evidence and never counted as target acceptance.
## 5. Robustness — focused fixes and guards, not a sandbox
Malformed JSON such as `[?]` previously hung; it now produces a located diagnostic.
Containers are bounded at 128, separators/numbers/escapes are checked, Unicode
escapes/surrogate pairs and control-character serialization are covered. JSON
parse/serialize temporaries and popped import entries have cleanup regressions.
Missing UNTIL is rejected. Opt-in step and per-READ FILE byte budgets are described
in [runtime guards](RUNTIME_LIMITS.md). Empty loops consume work budget; TRY/CATCH
does not reset it. These do not bound all memory, time, output or external commands.
## 6. Release delivery — candidate validation; hosted gate BLOCKED
Local source/Linux/Windows candidate archives must pass extracted-payload checks
before delivery. Actual checksums/reports belong to those frozen artifacts, not
just the source checkout. The VC6 ZIP uses a separate manifest/report path.
On this run the Gitea repository metadata reported `has_actions: false`; the
Actions runs API returned HTTP 401, the Actions web page was unavailable, and
there was no authenticated browser/API credential. Secure vault entry was not
available in this session. Hosted CI cannot be verified or enabled from Git SSH
access alone. No hosted release or CI success is claimed and no release assets
have been uploaded. Authenticated administration and enabled/configured Actions
are needed before completing the requested hosted CI/release gate.
The repository API also reported `private: false`. No visibility change was made;
do not put credentials or sensitive learner/native-host information into this repo.
See [release status](RELEASE_STATUS.md) for dated earlier checkpoints. Candidate
build evidence does not retroactively validate native Windows or accessibility.
+1 -1
View File
@@ -1,6 +1,6 @@
# Claro Language Specification
Current package: **Claro v2026.10.20.223203**
Current package: **Claro v2026.10.25.014405**
Status: **Living v1 specification**
This document defines the intended behavior of the Claro language. It is written to reduce breaking changes and keep the v1 line teachable and predictable.
+16
View File
@@ -59,6 +59,22 @@ Wine results must be labeled Wine results. Claim native Windows/Win98 support
only after running the target tests there. [Release status](RELEASE_STATUS.md)
records the dated scope; [the checklist](FINAL_RELEASE_CHECKLIST.md) governs publication.
## New focused checks
```bash
python3 tools/validate_error_locations.py
python3 tools/validate_error_guide.py
python3 tools/validate_learner_pilot.py
python3 tools/validate_resource_guards.py
```
They are also part of the full gate. `validate_learning_ownership.py` runs them
with leak detection enabled, including negative paths. Pilot code checks are
not learner observations. [Native acceptance](NATIVE_WINDOWS_TESTING.md) requires
real target logs; the Wine-rejecting probe prevents silently substituting a
compatibility-layer result. Current [six-track status](SIX_TRACK_STATUS.md)
separates implemented work from blocked human/hosted acceptance.
## Historical tools
Older `validate_rc*.py` and versioned validators remain for history and focused
+5 -5
View File
@@ -1,6 +1,6 @@
# Claro v2026.10.20.223203 - Packages and Projects
# Claro v2026.10.25.014405 - Packages and Projects
Claro v2026.10.20.223203 makes projects and packages safer and more useful while keeping the beginner syntax simple.
Claro v2026.10.25.014405 makes projects and packages safer and more useful while keeping the beginner syntax simple.
## Create a project
@@ -28,7 +28,7 @@ README.md
manifest-version: 1
name: MyProject
main: main.claro
version: v2026.10.20.223203
version: v2026.10.25.014405
packages:
package: text
package: net_tools
@@ -77,10 +77,10 @@ If `package doctor` reports `BAD lock checksum: math-tools`, check the `math-too
BAD lock version: expected exactly one lock-version: 1 line
```
The lockfile must also contain exactly one current release line, `version: v2026.10.20.223203`. A missing, stale, blank, or duplicate release line fails with:
The lockfile must also contain exactly one current release line, `version: v2026.10.25.014405`. A missing, stale, blank, or duplicate release line fails with:
```text
BAD lock release version: expected exactly one version: v2026.10.20.223203 line
BAD lock release version: expected exactly one version: v2026.10.25.014405 line
```
The doctor leaves your files unchanged. After reviewing your project and package manifests, run `claro package lock` to regenerate the lockfile, then run `claro package doctor` again. Spaces around values and capitalization of the lock-format field name are accepted, as with other lockfile fields. Valid generated lockfiles continue to work unchanged; this check does not add registry downloads or verify package content hashes.
+1 -1
View File
@@ -1,6 +1,6 @@
# Claro Web Server Plan
Current release: **Claro v2026.10.20.223203**
Current release: **Claro v2026.10.25.014405**
Claro currently has beginner HTTP client commands and offline `claro://` test URLs. A web server API is not complete yet.
+249 -101
View File
@@ -1,5 +1,5 @@
/*
Claro v2026.10.20.223203 - Networking Reliability Release
Claro v2026.10.25.014405 - Networking Reliability Release
A compact, buildable Claro interpreter focused on the beginner-friendly
teaching core: readable commands, simple expressions, functions, collections,
@@ -45,7 +45,7 @@ int pclose(FILE *stream);
#define F_OK 0
#endif
#define CLARO_VERSION "Claro v2026.10.20.223203"
#define CLARO_VERSION "Claro v2026.10.25.014405"
#define CLARO_MAX_CALL_DEPTH 256
#define CLARO_MAX_EXPR_DEPTH 128
#define CLARO_MAX_BLOCK_DEPTH 128
@@ -574,6 +574,9 @@ typedef struct Runtime {
char *err_msg;
char *err_file;
int err_line;
const char *active_file; /* borrowed while a source line executes */
int active_line;
unsigned long steps, step_limit, read_file_limit;
int returning;
Value ret;
int call_depth;
@@ -585,10 +588,42 @@ typedef struct Runtime {
int script_argc;
char **script_argv;
} Runtime;
static void rt_error(Runtime *rt, const char *file, int line, const char *fmt, ...);
static unsigned long rt_env_limit(Runtime *rt, const char *name) {
const char *text = getenv(name);
unsigned long value = 0;
if (!text || !*text) return 0;
while (*text) {
unsigned long digit;
if (*text < '0' || *text > '9') {
rt_error(rt, "<configuration>", 1, "%s needs a non-negative whole number.", name);
return 0;
}
digit = (unsigned long)(*text++ - '0');
if (value > (ULONG_MAX - digit) / 10) {
rt_error(rt, "<configuration>", 1, "%s is too large for this build.", name);
return 0;
}
value = value * 10 + digit;
}
return value;
}
static int rt_tick(Runtime *rt) {
if (rt->error) return 0;
if (!rt->step_limit) return 1;
if (rt->steps >= rt->step_limit) {
rt_error(rt, "", 0, "Execution reached its step limit. Check the loop or raise CLARO_STEP_LIMIT.");
return 0;
}
rt->steps++;
return 1;
}
static void rt_init(Runtime *rt) {
memset(rt, 0, sizeof(*rt));
str_init(&rt->captured);
rt->ret = v_none();
rt->step_limit = rt_env_limit(rt, "CLARO_STEP_LIMIT");
rt->read_file_limit = rt_env_limit(rt, "CLARO_READ_FILE_LIMIT");
srand(1);
}
static Var *env_find(Var *v, const char *name) {
@@ -825,6 +860,10 @@ static void rt_error(Runtime *rt, const char *file, int line, const char *fmt, .
va_start(ap, fmt);
claro_vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (!file || !*file)
file = rt->active_file;
if (line <= 0)
line = rt->active_line;
nfile = normalize_path_copy(file ? file : "");
rt->error = 1;
rt->err_msg = xstrdup(buf);
@@ -1877,134 +1916,195 @@ static int find_done(Program *p, int start) {
/* ---------- JSON ---------- */
typedef struct {
const char *s;
int depth;
const char *error;
} JsonP;
#define CLARO_MAX_JSON_DEPTH 128
static void jp_ws(JsonP *j) {
while (*j->s && isspace((unsigned char)*j->s))
j->s++;
}
static unsigned long jp_hex4(JsonP *j) {
unsigned long value = 0;
int i, digit;
for (i = 0; i < 4; i++) {
char c = *j->s;
if (c >= '0' && c <= '9') digit = c - '0';
else if (c >= 'a' && c <= 'f') digit = c - 'a' + 10;
else if (c >= 'A' && c <= 'F') digit = c - 'A' + 10;
else { j->error = "JSON Unicode escapes need four hexadecimal digits."; return 0; }
value = value * 16 + (unsigned long)digit;
j->s++;
}
return value;
}
static void jp_utf8(JsonP *j, Str *b, unsigned long code) {
unsigned long low;
if (code >= 0xd800UL && code <= 0xdbffUL) {
if (j->s[0] != '\\' || j->s[1] != 'u') {
j->error = "JSON Unicode needs a matching low surrogate.";
return;
}
j->s += 2;
low = jp_hex4(j);
if (j->error) return;
if (low < 0xdc00UL || low > 0xdfffUL) {
j->error = "JSON Unicode needs a matching low surrogate.";
return;
}
code = 0x10000UL + (code - 0xd800UL) * 1024 + low - 0xdc00UL;
} else if (code >= 0xdc00UL && code <= 0xdfffUL) {
j->error = "JSON Unicode has an unmatched low surrogate.";
return;
}
if (!code) { j->error = "JSON text cannot contain a zero character."; return; }
if (code < 0x80UL) str_ch(b, (char)code);
else if (code < 0x800UL) {
str_ch(b, (char)(0xc0UL | (code >> 6)));
str_ch(b, (char)(0x80UL | (code & 63)));
} else if (code < 0x10000UL) {
str_ch(b, (char)(0xe0UL | (code >> 12)));
str_ch(b, (char)(0x80UL | ((code >> 6) & 63)));
str_ch(b, (char)(0x80UL | (code & 63)));
} else {
str_ch(b, (char)(0xf0UL | (code >> 18)));
str_ch(b, (char)(0x80UL | ((code >> 12) & 63)));
str_ch(b, (char)(0x80UL | ((code >> 6) & 63)));
str_ch(b, (char)(0x80UL | (code & 63)));
}
}
static char *jp_string(JsonP *j) {
Str b;
char escaped;
str_init(&b);
jp_ws(j);
if (*j->s != '"')
return xstrdup("");
if (*j->s != '"') { j->error = "JSON object keys need double quotes."; return xstrdup(""); }
j->s++;
while (*j->s && *j->s != '"') {
while (*j->s && *j->s != '"' && !j->error) {
if (*j->s == '\\') {
j->s++;
if (*j->s == 'n')
str_ch(&b, '\n');
else if (*j->s == 't')
str_ch(&b, '\t');
else if (*j->s == 'r')
str_ch(&b, '\r');
else if (*j->s)
str_ch(&b, *j->s);
if (*j->s)
j->s++;
} else
str_ch(&b, *j->s++);
if (!*j->s) { j->error = "JSON text is missing a closing quote."; break; }
escaped = *j->s++;
if (escaped == 'n') str_ch(&b, '\n');
else if (escaped == 't') str_ch(&b, '\t');
else if (escaped == 'r') str_ch(&b, '\r');
else if (escaped == 'b') str_ch(&b, '\b');
else if (escaped == 'f') str_ch(&b, '\f');
else if (escaped == 'u') {
unsigned long code = jp_hex4(j);
if (!j->error) jp_utf8(j, &b, code);
} else if (escaped == '"' || escaped == '\\' || escaped == '/') str_ch(&b, escaped);
else j->error = "JSON has an invalid text escape.";
} else if ((unsigned char)*j->s < 32) j->error = "JSON text contains an unescaped control character.";
else str_ch(&b, *j->s++);
}
if (*j->s == '"')
j->s++;
if (*j->s == '"') j->s++;
else if (!j->error) j->error = "JSON text is missing a closing quote.";
return str_take(&b);
}
static Value jp_value(JsonP *j);
static Value jp_array(JsonP *j) {
Value arr = v_list();
j->s++;
jp_ws(j);
if (*j->s == '[')
j->s++;
jp_ws(j);
if (*j->s == ']') {
j->s++;
return arr;
}
while (*j->s) {
if (*j->s == ']') { j->s++; return arr; }
while (!j->error) {
Value v = jp_value(j);
list_add(arr.list, v);
if (!j->error) list_add(arr.list, v);
value_free(v);
if (j->error) break;
jp_ws(j);
if (*j->s == ',') {
j->s++;
continue;
}
if (*j->s == ']') {
j->s++;
break;
}
if (*j->s == ']') { j->s++; break; }
if (*j->s != ',') { j->error = "JSON list needs a comma or closing bracket."; break; }
j->s++;
}
return arr;
}
static Value jp_object(JsonP *j) {
Value obj = v_map();
j->s++;
jp_ws(j);
if (*j->s == '{')
j->s++;
jp_ws(j);
if (*j->s == '}') {
j->s++;
return obj;
}
while (*j->s) {
char *k = jp_string(j);
Value v;
if (*j->s == '}') { j->s++; return obj; }
while (!j->error) {
char *key = jp_string(j);
Value v = v_none();
jp_ws(j);
if (*j->s == ':')
j->s++;
v = jp_value(j);
map_put(obj.map, k, v);
free(k);
if (!j->error && *j->s != ':') j->error = "JSON object needs a colon after its key.";
if (!j->error) { j->s++; v = jp_value(j); }
if (!j->error) map_put(obj.map, key, v);
value_free(v);
free(key);
if (j->error) break;
jp_ws(j);
if (*j->s == ',') {
j->s++;
continue;
}
if (*j->s == '}') {
j->s++;
break;
}
if (*j->s == '}') { j->s++; break; }
if (*j->s != ',') { j->error = "JSON object needs a comma or closing brace."; break; }
j->s++;
}
return obj;
}
static Value jp_value(JsonP *j) {
const char *scan;
char *end;
double number;
Value result;
jp_ws(j);
if (*j->s == '"') {
char *s = jp_string(j);
Value v = v_str(s);
free(s);
return v;
char *text = jp_string(j);
result = v_str(text);
free(text);
return result;
}
if (*j->s == '{')
return jp_object(j);
if (*j->s == '[')
return jp_array(j);
if (!strncmp(j->s, "true", 4)) {
j->s += 4;
return v_bool(1);
if (*j->s == '[' || *j->s == '{') {
if (j->depth >= CLARO_MAX_JSON_DEPTH) {
j->error = "JSON nesting exceeds the safe limit of 128. Simplify the data.";
return v_none();
}
j->depth++;
result = *j->s == '[' ? jp_array(j) : jp_object(j);
j->depth--;
return result;
}
if (!strncmp(j->s, "false", 5)) {
j->s += 5;
return v_bool(0);
if (!strncmp(j->s, "true", 4)) { j->s += 4; return v_bool(1); }
if (!strncmp(j->s, "false", 5)) { j->s += 5; return v_bool(0); }
if (!strncmp(j->s, "null", 4)) { j->s += 4; return v_none(); }
scan = j->s;
if (*scan == '-') scan++;
if (*scan == '0') scan++;
else if (*scan >= '1' && *scan <= '9') while (isdigit((unsigned char)*scan)) scan++;
else { j->error = "JSON needs a value here."; return v_none(); }
if (*scan == '.') {
scan++;
if (!isdigit((unsigned char)*scan)) { j->error = "JSON number needs digits after its decimal point."; return v_none(); }
while (isdigit((unsigned char)*scan)) scan++;
}
if (!strncmp(j->s, "null", 4)) {
j->s += 4;
if (*scan == 'e' || *scan == 'E') {
scan++;
if (*scan == '+' || *scan == '-') scan++;
if (!isdigit((unsigned char)*scan)) { j->error = "JSON number needs exponent digits."; return v_none(); }
while (isdigit((unsigned char)*scan)) scan++;
}
number = strtod(j->s, &end);
if (end != scan || number > DBL_MAX || number < -DBL_MAX) {
j->error = "JSON number is outside the supported range.";
return v_none();
}
end = NULL;
{
double d = strtod(j->s, &end);
if (end && end != j->s) {
j->s = end;
return v_num(d);
}
}
return v_none();
j->s = scan;
return v_num(number);
}
static Value parse_json_text(const char *s) {
static Value parse_json_text(Runtime *rt, const char *text) {
JsonP j;
j.s = s;
return jp_value(&j);
Value result;
memset(&j, 0, sizeof(j));
j.s = text;
result = jp_value(&j);
jp_ws(&j);
if (!j.error && *j.s) j.error = "JSON has unexpected text after its value.";
if (j.error) {
value_free(result);
rt_error(rt, "", 0, "%s", j.error);
return v_none();
}
return result;
}
static void json_escape(Str *b, const char *s) {
str_ch(b, '"');
@@ -2015,6 +2115,12 @@ static void json_escape(Str *b, const char *s) {
str_add(b, "\\\\");
} else if (*s == '\n') {
str_add(b, "\\n");
} else if ((unsigned char)*s < 32) {
static const char hex[] = "0123456789abcdef";
unsigned char c = (unsigned char)*s;
str_add(b, "\\u00");
str_ch(b, hex[c >> 4]);
str_ch(b, hex[c & 15]);
} else
str_ch(b, *s);
s++;
@@ -2861,6 +2967,9 @@ static void do_import(Runtime *rt, const char *path, const char *ns, const char
exec_range(rt, p, 0, p->count);
}
rt->import_depth--;
free(rt->import_stack[rt->import_depth]);
rt->import_stack[rt->import_depth] = NULL;
split_args_free(exports, ec);
free(prefix);
}
static void create_object_value(Runtime *rt, const char *file, int line, const char *cls,
@@ -2889,7 +2998,7 @@ static void create_object_value(Runtime *rt, const char *file, int line, const c
}
}
}
static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
static void exec_line_body(Runtime *rt, Program *p, int *pcp, const char *raw) {
char buf[4096], up[64], w[128];
char *t;
int pc = *pcp;
@@ -3461,12 +3570,25 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
}
if (strcmp(up, "REPEAT") == 0) {
int end = match_block(p, pc, "REPEAT", "UNTIL", NULL, NULL);
char *cond = p->lines[end] + 5;
char *cond;
int again = 0;
if (end < 0) {
rt_error(rt, p->path, pc + 1, "REPEAT needs UNTIL and a stopping condition.");
return;
}
cond = p->lines[end] + 5;
do {
exec_range(rt, p, pc + 1, end);
if (rt->error || rt->returning)
break;
} while (!v_truth(eval_expr(rt, cond)));
rt->active_file = p->path;
rt->active_line = end + 1;
{
Value condition = eval_expr(rt, cond);
again = !v_truth(condition);
value_free(condition);
}
} while (again && !rt->error);
*pcp = end < 0 ? pc : end;
return;
}
@@ -3717,12 +3839,21 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
Str b;
int c;
Value contents;
unsigned long bytes = 0;
str_init(&b);
while ((c = fgetc(f)) != EOF)
while ((c = fgetc(f)) != EOF) {
if (rt->read_file_limit && bytes >= rt->read_file_limit) {
rt_error(rt, p->path, pc + 1,
"READ FILE exceeded the configured file limit. Raise CLARO_READ_FILE_LIMIT if needed.");
break;
}
str_ch(&b, (char)c);
if (rt->read_file_limit) bytes++;
}
if (ferror(f)) rt_error(rt, p->path, pc + 1, "Could not finish reading the file.");
fclose(f);
contents = v_str(b.s ? b.s : "");
rt_set(rt, var, contents);
if (!rt->error) rt_set(rt, var, contents);
value_free(contents);
free(b.s);
}
@@ -3844,7 +3975,10 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
char *var = xstrdup(trim_inplace((char *)as + 2));
Value s = eval_expr(rt, ex);
char *txt = v_to_string(s);
rt_set(rt, var, parse_json_text(txt));
Value parsed = parse_json_text(rt, txt);
if (!rt->error) rt_set(rt, var, parsed);
value_free(parsed);
value_free(s);
free(txt);
free(ex);
free(var);
@@ -3860,7 +3994,10 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
char *var = xstrdup(trim_inplace((char *)as + 2));
Value v = eval_expr(rt, ex);
char *js = make_json(v, pretty);
rt_set(rt, var, v_str(js));
Value encoded = v_str(js);
rt_set(rt, var, encoded);
value_free(encoded);
value_free(v);
free(js);
free(ex);
free(var);
@@ -4136,6 +4273,16 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
}
rt_error(rt, p->path, pc + 1, "Unknown command: %s", w);
}
static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
const char *saved_file = rt->active_file;
int saved_line = rt->active_line;
rt->active_file = p->path;
rt->active_line = *pcp + 1;
if (rt_tick(rt))
exec_line_body(rt, p, pcp, raw);
rt->active_file = saved_file;
rt->active_line = saved_line;
}
static void exec_range(Runtime *rt, Program *p, int start, int end) {
int pc;
/* Function calls have their own guard; count additional control-block frames. */
@@ -4144,6 +4291,7 @@ static void exec_range(Runtime *rt, Program *p, int start, int end) {
"Block nesting exceeds the safe limit of 128. Simplify the nested blocks.");
return;
}
if (!rt_tick(rt)) return;
rt->range_depth++;
if (end < 0 || end > p->count)
end = p->count;
@@ -6630,12 +6778,12 @@ static int create_new_project(const char *name) {
claro_snprintf(path, sizeof(path), "%s/claro.project", name);
claro_snprintf(
text, sizeof(text),
"manifest-version: 1\nname: %s\nmain: main.claro\nversion: v2026.10.20.223203\npackages:\n",
"manifest-version: 1\nname: %s\nmain: main.claro\nversion: v2026.10.25.014405\npackages:\n",
name);
write_text_file_simple(path, text);
claro_snprintf(path, sizeof(path), "%s/claro.lock", name);
write_text_file_simple(path,
"# Claro package lock\nlock-version: 1\nversion: v2026.10.20.223203\n");
"# Claro package lock\nlock-version: 1\nversion: v2026.10.25.014405\n");
claro_snprintf(path, sizeof(path), "%s/README.md", name);
write_text_file_simple(
path, "# My Claro Project\n\nBuild Claro, then run this project with:\n\n```bash\nclaro "
@@ -6732,7 +6880,7 @@ static void ensure_project_file(void) {
if (!file_exists_simple("claro.project"))
write_text_file_simple("claro.project",
"manifest-version: 1\nname: ClaroProject\nmain: "
"main.claro\nversion: v2026.10.20.223203\npackages:\n");
"main.claro\nversion: v2026.10.25.014405\npackages:\n");
}
static char *project_value(const char *key) {
char *txt = read_file_text("claro.project");
@@ -6858,7 +7006,7 @@ static int write_package_lock(void) {
free(txt);
return 0;
}
fputs("# Claro package lock\nlock-version: 1\nversion: v2026.10.20.223203\n", f);
fputs("# Claro package lock\nlock-version: 1\nversion: v2026.10.25.014405\n", f);
p = txt;
while (*p) {
char *line = p;
@@ -7341,7 +7489,7 @@ static int package_doctor(void) {
}
if (txt && !package_lock_release_version_matches(txt)) {
printf(
" BAD lock release version: expected exactly one version: v2026.10.20.223203 line\n");
" BAD lock release version: expected exactly one version: v2026.10.25.014405 line\n");
ok = 0;
}
free(txt);
@@ -7505,7 +7653,7 @@ static int run_package_cmd(int argc, char **argv, int arg) {
static int print_ide_info(void) {
printf("{\n");
printf(" \"language\": \"Claro\",\n");
printf(" \"version\": \"v2026.10.20.223203\",\n");
printf(" \"version\": \"v2026.10.25.014405\",\n");
printf(" \"file_extensions\": [\".claro\"],\n");
printf(" \"keywords\": "
"[\"SAY\",\"ASK\",\"SET\",\"IF\",\"ELSE\",\"ENDIF\",\"END\",\"REPEAT\",\"UNTIL\","
@@ -7855,7 +8003,7 @@ static int run_validate(void) {
printf(
"%s\n",
fails ? "Validation found problems."
: "Validation passed. Claro v2026.10.20.223203 foundation checks are ready for use.");
: "Validation passed. Claro v2026.10.25.014405 foundation checks are ready for use.");
return fails ? 1 : 0;
}
+21
View File
@@ -0,0 +1,21 @@
@echo off
rem Run only in a fresh extracted Claro test folder.
if exist native.ok del native.ok
if not exist osprobe.exe goto wrongos
osprobe.exe --require-win98se >oscheck.txt
if errorlevel 1 goto wrongos
claro98.exe --version >nver.txt
if errorlevel 1 goto failed
claro98.exe test >ntests.txt
if errorlevel 1 goto failed
claro98.exe validate >ncheck.txt
if errorlevel 1 goto failed
echo Automated native checks passed. Manual console and file checks are still required.
echo PASS>native.ok
goto end
:wrongos
echo Native target not verified. Check oscheck.txt and the probe message.
goto end
:failed
echo Claro checks failed. Keep the logs; do not report acceptance.
:end
+56
View File
@@ -0,0 +1,56 @@
[
{
"name": "quote",
"filename": "quote.claro",
"mode": "run",
"code": "SAY \"Start\"\nSAY \"unfinished\n",
"diagnostic": "quote.claro:2: Text is missing a closing quote. Add a double quote at the end.",
"repair": "SAY \"Start\"\nSAY \"finished\"\n",
"output": "Start\nfinished\n"
},
{
"name": "operand",
"filename": "operand.claro",
"mode": "run",
"code": "SET score 1\nSAY score +\n",
"diagnostic": "operand.claro:2: This expression needs a value after the operator.",
"repair": "SET score 1\nSAY score + 1\n",
"output": "2\n"
},
{
"name": "command",
"filename": "command.claro",
"mode": "check",
"code": "PRNT \"Hello\"\n",
"diagnostic": "command.claro:1: I do not know the command \"PRNT\". Did you mean \"SAY\"?",
"repair": "SAY \"Hello\"\n",
"output": "Hello\n"
},
{
"name": "assignment",
"filename": "assignment.claro",
"mode": "check",
"code": "SET name\n",
"diagnostic": "assignment.claro:1: SET needs a name and a value. Try: SET name \"Alex\"",
"repair": "SET name \"Alex\"\nSAY name\n",
"output": "Alex\n"
},
{
"name": "choice",
"filename": "choice.claro",
"mode": "check",
"code": "IF YES\n SAY \"Yes\"\n",
"diagnostic": "choice.claro:1: IF block opened here needs END",
"repair": "IF YES\n SAY \"Yes\"\nEND\n",
"output": "Yes\n"
},
{
"name": "variable",
"filename": "variable.claro",
"mode": "run",
"code": "SET score 10\nSAY socre\n",
"diagnostic": "variable.claro:2: I do not know the variable \"socre\" yet.",
"repair": "SET score 10\nSAY score\n",
"output": "10\n"
}
]
+44
View File
@@ -0,0 +1,44 @@
[
{
"name": "Say hello",
"code": "SAY \"Hello!\"\n",
"input": "",
"output": "Hello!\n",
"task": "Run this once. Change the message to something you choose and run it again."
},
{
"name": "Ask a name",
"code": "ASK \"Name? \" name\nSAY \"Hello \" + name\n",
"input": "Sam\n",
"output": "Name? \nHello Sam\n",
"task": "Run it and type a pretend name. Explain where that name is remembered."
},
{
"name": "Change a number",
"code": "SET score 2\nSAY score + 1\n",
"input": "",
"output": "3\n",
"task": "Predict the result, run it, then change 2 to another small number."
},
{
"name": "Make a choice",
"code": "SET score 2\nIF score > 1\n SAY \"More than one\"\nELSE\n SAY \"One or less\"\nEND\n",
"input": "",
"output": "More than one\n",
"task": "Optional: change score to 0. Predict which message appears."
},
{
"name": "Repeat",
"code": "DO 3 TIMES\n SAY \"Again\"\nEND\n",
"input": "",
"output": "Again\nAgain\nAgain\n",
"task": "Optional: change how many times the message appears. Ask how to stop a stuck run."
},
{
"name": "Save a file",
"code": "WRITE FILE \"practice.txt\" WITH \"My first file\"\nREAD FILE \"practice.txt\" AS text\nSAY text\n",
"input": "",
"output": "My first file\n",
"task": "Optional: use a new writable practice folder. Find the file after running it."
}
]
+21
View File
@@ -0,0 +1,21 @@
@echo off
rem Run only in a fresh extracted Claro test folder.
if exist native.ok del native.ok
if not exist osprobe.exe goto wrongos
osprobe.exe --require-native >oscheck.txt
if errorlevel 1 goto wrongos
claro.exe --version >nver.txt
if errorlevel 1 goto failed
claro.exe test >ntests.txt
if errorlevel 1 goto failed
claro.exe validate >ncheck.txt
if errorlevel 1 goto failed
echo Automated native checks passed. Manual console and file checks are still required.
echo PASS>native.ok
goto end
:wrongos
echo Native target not verified. Check oscheck.txt and the probe message.
goto end
:failed
echo Claro checks failed. Keep the logs; do not report acceptance.
:end
+1 -1
View File
@@ -14,7 +14,7 @@ import zipfile
ROOT = Path(__file__).resolve().parents[1]
DIRECTORIES = ['.forgejo', 'src', 'docs', 'lessons', 'examples', 'lib', 'tests', 'tools', 'assets', 'support']
FILES = ['README.md', 'LICENSE', 'CHANGELOG.md', 'CONTRIBUTING.md', 'Makefile', 'build.bat', 'build.ps1', 'build98.bat', 'build98.rsp', 'run_tests.bat']
FILES = ['README.md', 'LICENSE', 'CHANGELOG.md', 'CONTRIBUTING.md', 'Makefile', 'build.bat', 'build.ps1', 'build98.bat', 'build98.rsp', 'test98.bat', 'testwin.bat', 'run_tests.bat']
FLAGS = ['-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', '-O2', '-static']
+35
View File
@@ -0,0 +1,35 @@
/* Native-OS evidence helper, not part of the Claro interpreter. */
#include <windows.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
OSVERSIONINFOA info;
HMODULE module;
int wine, want98, match98;
memset(&info, 0, sizeof(info));
info.dwOSVersionInfoSize = sizeof(info);
if (!GetVersionExA(&info)) {
fprintf(stderr, "Could not identify the reported Windows API version.\n");
return 3;
}
module = GetModuleHandleA("ntdll.dll");
wine = module && GetProcAddress(module, "wine_get_version") != NULL;
want98 = argc > 1 && strcmp(argv[1], "--require-win98se") == 0;
match98 = info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
info.dwMajorVersion == 4 && info.dwMinorVersion == 10 &&
(info.dwBuildNumber & 0xffffUL) == 2222;
printf("{\"wine\":%s,\"reported_api_major\":%lu,\"reported_api_minor\":%lu,\"reported_build\":%lu,\"platform_id\":%lu,\"matches_win98se\":%s}\n",
wine ? "true" : "false", (unsigned long)info.dwMajorVersion,
(unsigned long)info.dwMinorVersion,
(unsigned long)(info.dwBuildNumber & 0xffffUL),
(unsigned long)info.dwPlatformId, match98 ? "true" : "false");
if (wine) {
fprintf(stderr, "Wine detected: this is NOT native Windows acceptance.\n");
return 2;
}
if (want98 && !match98) {
fprintf(stderr, "This is not the required Windows 98 SE 4.10.2222 target.\n");
return 2;
}
return 0;
}
+4
View File
@@ -17,6 +17,10 @@ REQUIRED_COMMANDS = [
"python3 tools/validate_c89.py",
"python3 tools/validate_msvc6_source.py",
"python3 tools/validate_documentation_status.py",
"python3 tools/validate_error_locations.py",
"python3 tools/validate_error_guide.py",
"python3 tools/validate_learner_pilot.py",
"python3 tools/validate_resource_guards.py",
"python3 tools/validate_ci_workflow.py",
]
+3
View File
@@ -11,6 +11,9 @@ ACTIVE = (
'docs/RELEASE_STATUS.md', 'docs/FINAL_RELEASE_CHECKLIST.md',
'docs/CLASSROOM_READINESS.md', 'docs/TESTING.md', 'docs/HARDENING.md',
'docs/PORTABILITY.md', 'docs/RELIABILITY.md',
'docs/ERRORS.md', 'docs/RUNTIME_LIMITS.md', 'docs/LEARNER_PILOT.md',
'docs/LEARNER_FEEDBACK_TEMPLATE.md', 'docs/NATIVE_WINDOWS_TESTING.md',
'docs/SIX_TRACK_STATUS.md',
'docs/history/CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md',
'docs/history/HARDENING_BEFORE_STATUS_CLEANUP.md',
)
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""Execute every error-guide failure and repair against the actual interpreter."""
from pathlib import Path
import json, os, re, shlex, subprocess, tempfile
ROOT = Path(__file__).resolve().parents[1]
def main():
cases = json.loads((ROOT/'tests/error_examples.json').read_text())
guide = (ROOT/'docs/ERRORS.md').read_text()
blocks = re.findall(r'```claro\n(.*?)```',guide,re.S)
expected = [c[k] for c in cases for k in ['code','repair']]
assert blocks == expected, 'Every error and repair block must match executable coverage'
with tempfile.TemporaryDirectory(prefix='claro-error-guide-',dir=os.environ.get('TMPDIR')) as tmp:
work=Path(tmp); override=os.environ.get('CLARO_EXE'); exe=Path(override).resolve() if override else work/'claro'
if not override:
subprocess.run(shlex.split(os.environ.get('CLARO_CC','gcc'))+['-std=c89','-pedantic-errors',str(ROOT/'src/claro.c'),'-o',str(exe),'-lm'],check=True)
for case in cases:
path=work/case['filename'];path.write_text(case['code'])
cmd=[str(exe)]+(['check'] if case['mode']=='check' else [])+[path.name]
r=subprocess.run(cmd,cwd=work,capture_output=True,text=True,timeout=10)
assert r.returncode==1 and case['diagnostic'] in r.stdout+r.stderr,(case['name'],r.stdout,r.stderr)
assert case['diagnostic'] in guide
path.write_text(case['repair']);r=subprocess.run([str(exe),path.name],cwd=work,capture_output=True,text=True,timeout=10)
assert r.returncode==0 and r.stdout==case['output'],(case['name'],r.stdout,r.stderr)
print('PASS error guide:',case['name'])
if __name__=='__main__':main()
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""Verify real runtime diagnostic origins, including functions/imports/TRY."""
from pathlib import Path
import os
import shlex
import subprocess
import tempfile
ROOT = Path(__file__).resolve().parents[1]
def main():
with tempfile.TemporaryDirectory(prefix='claro-error-locations-', dir=os.environ.get('TMPDIR')) as temp:
work = Path(temp)
override = os.environ.get('CLARO_EXE')
exe = Path(override).resolve() if override else work / 'claro'
if not override:
compiler = shlex.split(os.environ.get('CLARO_CC', 'gcc'))
subprocess.run(compiler + ['-std=c89', '-pedantic-errors', str(ROOT / 'src/claro.c'), '-o', str(exe), '-lm'], check=True)
cases = [
('quote.claro', 'SAY "Start"\nSAY "unfinished\n', 2, 'Text is missing a closing quote.'),
('operand.claro', 'SET score 1\nSAY score +\n', 2, 'needs a value after the operator'),
('parenthesis.claro', 'SAY "Start"\nSAY (1 + 2\n', 2, 'needs a closing parenthesis'),
('function.claro', 'TEACH broken\n SAY 1 +\nEND\nDO broken\n', 2, 'needs a value after the operator'),
('repeat.claro', 'REPEAT\n SAY "Once"\nUNTIL (\n', 3, 'needs a value after the operator'),
]
for name, text, line, message in cases:
(work / name).write_text(text)
result = subprocess.run([str(exe), name], cwd=work, capture_output=True, text=True, timeout=10)
output = result.stdout + result.stderr
assert not any(t in output for t in ["AddressSanitizer", "LeakSanitizer", "runtime error:"]), output
assert result.returncode == 1, (name, result.returncode, output)
assert f'{name}:{line}:' in output and ':0:' not in output and message in output, (name, output)
print('PASS origin:', name, line)
(work / 'broken-module.claro').write_text('TEACH broken\n SAY 1 +\nEND\n')
(work / 'caller.claro').write_text('IMPORT "broken-module.claro" AS problem\nDO problem.broken\n')
result = subprocess.run([str(exe), 'caller.claro'], cwd=work, capture_output=True, text=True, timeout=10)
assert result.returncode == 1 and 'broken-module.claro:2:' in result.stderr, result.stderr
print('PASS origin: imported function')
(work / 'caught.claro').write_text('TRY\n DO problem.broken\nCATCH\n SAY LASTERRORFILE\n SAY LASTERRORLINE\nENDTRY\n')
(work / 'caller.claro').write_text('IMPORT "broken-module.claro" AS problem\nTRY\n DO problem.broken\nCATCH\n SAY LASTERRORFILE\n SAY LASTERRORLINE\nENDTRY\n')
result = subprocess.run([str(exe), 'caller.claro'], cwd=work, capture_output=True, text=True, timeout=10)
assert result.returncode == 0 and result.stdout.replace('\\', '/').endswith('broken-module.claro\n2\n'), (result.stdout, result.stderr)
print('PASS origin: caught error metadata')
if __name__ == '__main__':
main()
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env python3
"""Execute the pilot's six starter tasks, not simulated human observations."""
from pathlib import Path
import json,os,re,shlex,subprocess,tempfile
ROOT=Path(__file__).resolve().parents[1]
def main():
tasks=json.loads((ROOT/'tests/learner_pilot.json').read_text())
blocks=re.findall(r'```claro\n(.*?)```',(ROOT/'docs/LEARNER_PILOT.md').read_text(),re.S)
assert blocks==[t['code'] for t in tasks]
with tempfile.TemporaryDirectory(prefix='claro-pilot-',dir=os.environ.get('TMPDIR')) as tmp:
work=Path(tmp);override=os.environ.get('CLARO_EXE');exe=Path(override).resolve() if override else work/'claro'
if not override:subprocess.run(shlex.split(os.environ.get('CLARO_CC','gcc'))+['-std=c89','-pedantic-errors',str(ROOT/'src/claro.c'),'-o',str(exe),'-lm'],check=True)
for i,t in enumerate(tasks):
p=work/f'task{i}.claro';p.write_text(t['code']);r=subprocess.run([str(exe),p.name],cwd=work,input=t['input'],capture_output=True,text=True,timeout=10)
assert r.returncode==0 and r.stdout==t['output'],(t['name'],r.stdout,r.stderr)
print('PASS pilot code:',t['name'])
assert (work/'practice.txt').read_text()=='My first file'
print('Human usability/accessibility: NOT TESTED')
if __name__=='__main__':main()
+1 -1
View File
@@ -17,7 +17,7 @@ def main():
binary = Path(tmp) / 'claro-asan'
subprocess.run(['gcc', '-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', '-O0', '-g', '-fsanitize=address,undefined', str(ROOT / 'src/claro.c'), '-o', str(binary), '-lm'], check=True)
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=1:halt_on_error=1', UBSAN_OPTIONS='halt_on_error=1', CLARO_EXE=str(binary))
for checker in ['validate_learning_syntax.py', 'validate_first_hour.py']:
for checker in ['validate_learning_syntax.py', 'validate_first_hour.py', 'validate_error_locations.py', 'validate_error_guide.py', 'validate_learner_pilot.py', 'validate_resource_guards.py']:
result = subprocess.run([sys.executable, str(ROOT / 'tools' / checker)], cwd=ROOT, env=env, capture_output=True, text=True, timeout=90)
if result.returncode:
print(result.stdout + result.stderr)
+9 -9
View File
@@ -6,7 +6,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
EXE = ROOT / ("claro.exe" if os.name == "nt" else "claro")
EXPECTED_VERSION = "Claro v2026.10.20.223203"
EXPECTED_VERSION = "Claro v2026.10.25.014405"
def fail(message):
@@ -57,11 +57,11 @@ def main():
fail(out)
starter = work / "StarterApp"
starter_project = read(starter / "claro.project")
for phrase in ["manifest-version: 1", "name: StarterApp", "main: main.claro", "version: v2026.10.20.223203", "packages:"]:
for phrase in ["manifest-version: 1", "name: StarterApp", "main: main.claro", "version: v2026.10.25.014405", "packages:"]:
if phrase not in starter_project:
fail(f"claro new project file missing {phrase!r}:\n{starter_project}")
starter_lock = read(starter / "claro.lock")
for phrase in ["lock-version: 1", "version: v2026.10.20.223203"]:
for phrase in ["lock-version: 1", "version: v2026.10.25.014405"]:
if phrase not in starter_lock:
fail(f"claro new lockfile missing {phrase!r}:\n{starter_lock}")
@@ -71,7 +71,7 @@ def main():
if rc != 0:
fail(out)
project = read(work / "claro.project")
for phrase in ["manifest-version: 1", "name: ClaroProject", "main: main.claro", "version: v2026.10.20.223203", "packages:"]:
for phrase in ["manifest-version: 1", "name: ClaroProject", "main: main.claro", "version: v2026.10.25.014405", "packages:"]:
if phrase not in project:
fail(f"claro.project missing {phrase!r}:\n{project}")
@@ -100,7 +100,7 @@ def main():
fail(f"Package checksum should be a stable non-placeholder value, got {checksum!r}")
lock = read(work / "claro.lock")
for phrase in ["lock-version: 1", "version: v2026.10.20.223203", "package: math-tools", "checksum:"]:
for phrase in ["lock-version: 1", "version: v2026.10.25.014405", "package: math-tools", "checksum:"]:
if phrase not in lock:
fail(f"claro.lock missing {phrase!r}:\n{lock}")
@@ -127,12 +127,12 @@ def main():
fail(f"Lock format-version diagnostic was unclear:\n{out}")
if read(work / "claro.lock") != invalid_lock:
fail("package doctor must not rewrite an invalid lock format version")
invalid_release_lock = lock.replace("version: v2026.10.20.223203", "version: v1.18.25", 1)
invalid_release_lock = lock.replace("version: v2026.10.25.014405", "version: v1.18.25", 1)
(work / "claro.lock").write_text(invalid_release_lock, encoding="utf-8")
rc, out = run([str(EXE), "package", "doctor"], work)
if rc == 0:
fail("package doctor must reject a lockfile for a different Claro release")
if "BAD lock release version: expected exactly one version: v2026.10.20.223203 line" not in out:
if "BAD lock release version: expected exactly one version: v2026.10.25.014405 line" not in out:
fail(f"Lock release-version diagnostic was unclear:\n{out}")
if read(work / "claro.lock") != invalid_release_lock:
fail("package doctor must not rewrite an invalid lock release version")
@@ -343,7 +343,7 @@ def main():
fail("Long package name should not create a package folder")
(work / "claro.project").write_text(
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.20.223203\npackages:\npackage: ../bad\n",
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.25.014405\npackages:\npackage: ../bad\n",
encoding="utf-8",
)
rc, out = run([str(EXE), "package", "doctor"], work)
@@ -382,7 +382,7 @@ def main():
fail("package add should not create a package folder while unsafe names remain in claro.project")
(work / "claro.project").write_text(
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.20.223203\npackages:\npackage: math-tools\npackage: ../bad\n",
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.25.014405\npackages:\npackage: math-tools\npackage: ../bad\n",
encoding="utf-8",
)
rc, out = run([str(EXE), "package", "remove", "math-tools"], work)
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Exercise bounded malformed JSON, opt-in resource guards and empty loops."""
from pathlib import Path
import json
import os
import shlex
import subprocess
import tempfile
ROOT = Path(__file__).resolve().parents[1]
def main():
with tempfile.TemporaryDirectory(prefix='claro-resource-guards-', dir=os.environ.get('TMPDIR')) as tmp:
work = Path(tmp)
override = os.environ.get('CLARO_EXE')
exe = Path(override).resolve() if override else work / 'claro'
if not override:
subprocess.run(shlex.split(os.environ.get('CLARO_CC', 'gcc')) + ['-std=c89', '-pedantic-errors', str(ROOT/'src/claro.c'), '-o', str(exe), '-lm'], check=True)
def run(name, text, error=None, limits=None):
(work/name).write_text(text)
env = dict(os.environ)
for key in ['CLARO_STEP_LIMIT', 'CLARO_READ_FILE_LIMIT']:
env.pop(key, None)
env.update(limits or {})
try:
r = subprocess.run([str(exe), name], cwd=work, env=env, capture_output=True, text=True, timeout=8)
except subprocess.TimeoutExpired:
raise AssertionError(f'{name}: interpreter failed to terminate') from None
output = r.stdout+r.stderr
assert not any(t in output for t in ["AddressSanitizer", "LeakSanitizer", "runtime error:"]), output
assert r.returncode == (1 if error else 0), (name, r.returncode, output)
if error:
assert error in output and ':0:' not in output, (name, output)
print('PASS:', name)
return r
(work/'invalid.json').write_text('[?]')
run('bad-json.claro', 'READ FILE "invalid.json" AS text\nPARSE JSON text AS data\n', 'JSON')
for number, text in enumerate(['[1,]', '{"x" 1}', '[1 2]', '"unterminated', '1 extra', '01', '[', '{', '{"x":}', '1e+', '"\\uD800"', '"\\uDC00"', '"\\u12zz"', '"\\u0000"', '"\\q"']):
(work/'invalid.json').write_text(text)
run(f'malformed-{number}.claro', 'READ FILE "invalid.json" AS text\nPARSE JSON text AS data\n', 'JSON')
(work/'nested.json').write_text('['*129+'0'+']'*129)
run('deep-json.claro','READ FILE "nested.json" AS text\nPARSE JSON text AS data\n','nesting')
(work/'valid.json').write_text('['*128+'0'+']'*128)
run('valid-depth.claro','READ FILE "valid.json" AS text\nPARSE JSON text AS data\n')
(work/'valid.json').write_text('{"text":"\\u0041\\uD83D\\uDE00", "n": -1.25e2, "ok": true}')
r=run('valid-json.claro','READ FILE "valid.json" AS text\nPARSE JSON text AS data\nGET data KEY "text" AS result\nSAY result\n')
assert r.stdout=='A😀\n',r.stdout
original = {"text": "tab\tnewline\ncontrol\x01quote\"", "values": [1, True, None, "A😀"]}
(work/'valid.json').write_text(json.dumps(original))
run('roundtrip.claro','READ FILE "valid.json" AS text\nPARSE JSON text AS data\nMAKE JSON data AS encoded\nWRITE FILE "roundtrip.json" WITH encoded\n')
assert json.loads((work/'roundtrip.json').read_text()) == original
for name, text in [('empty-repeat.claro','REPEAT\nUNTIL NO\n'),('empty-count.claro','DO 1000000000 TIMES\nEND\n'),('empty-for.claro','FOR i FROM 1 TO 1000000000\nEND\n')]:
run(name,text,'step limit',{'CLARO_STEP_LIMIT':'20'})
run('finite.claro','DO 3 TIMES\nSAY "Yes"\nEND\n',limits={'CLARO_STEP_LIMIT':'20'})
run('missing-until.claro','REPEAT\nSAY "Oops"\n','UNTIL')
(work/'large.txt').write_bytes(b'x'*1048577)
run('read-limit.claro','READ FILE "large.txt" AS data\n','file limit',{'CLARO_READ_FILE_LIMIT':'1048576'})
(work/'boundary.txt').write_bytes(b'x'*32)
r=run('read-boundary.claro','READ FILE "boundary.txt" AS data\nTEXT LENGTH data AS length\nSAY length\n',limits={'CLARO_READ_FILE_LIMIT':'32'})
assert r.stdout=='32\n',r.stdout
run('unlimited-read.claro','READ FILE "large.txt" AS data\n')
run('caught-budget.claro','TRY\nREPEAT\nUNTIL NO\nCATCH\nSAY "must not escape"\nENDTRY\n','step limit',{'CLARO_STEP_LIMIT':'20'})
r=run('long-finite.claro','SET i 0\nREPEAT\nSET i i + 1\nUNTIL i = 20000\nSAY i\n',limits={'CLARO_STEP_LIMIT':'200000'})
assert r.stdout=='20000\n',r.stdout
run('overflow-limit.claro','SAY "must not execute"\n','CLARO_STEP_LIMIT',{'CLARO_STEP_LIMIT':'999999999999999999999999999999'})
run('invalid-limit.claro','SAY "must not execute"\n','CLARO_STEP_LIMIT',{'CLARO_STEP_LIMIT':'-1'})
if __name__=='__main__':
main()
+1 -1
View File
@@ -43,4 +43,4 @@ with tempfile.TemporaryDirectory() as td:
run([str(local), "package", "remove", "text"], cwd=proj)
run([str(local), "run"], cwd=proj)
print("Validation passed. Claro v2026.10.20.223203 networking reliability is ready for use.")
print("Validation passed. Claro v2026.10.25.014405 networking reliability is ready for use.")
+6 -2
View File
@@ -40,12 +40,16 @@ def main():
run([sys.executable, "tools/validate_c89.py"])
run([sys.executable, "tools/validate_msvc6_source.py"])
out = run([str(EXE), "--version"])
if "Claro v2026.10.20.223203" not in out:
if "Claro v2026.10.25.014405" not in out:
raise SystemExit("Wrong version string")
run([str(EXE), "test"])
run([str(EXE), "validate"])
run([sys.executable, "tools/validate_first_hour.py"])
run([sys.executable, "tools/validate_learning_syntax.py"])
run([sys.executable, "tools/validate_error_locations.py"])
run([sys.executable, "tools/validate_error_guide.py"])
run([sys.executable, "tools/validate_learner_pilot.py"])
run([sys.executable, "tools/validate_resource_guards.py"])
run([sys.executable, "tools/validate_ide_metadata.py"])
run([sys.executable, "tools/validate_lsp_helper.py"])
run([sys.executable, "tools/validate_concurrency.py"])
@@ -55,7 +59,7 @@ def main():
run([sys.executable, "tools/validate_version_convention.py"])
run([sys.executable, "tools/validate_documentation_status.py"])
run([str(EXE), "examples/networking.claro"])
print("v2026.10.20.223203 validation complete")
print("v2026.10.25.014405 validation complete")
if __name__ == "__main__":
main()
+5 -5
View File
@@ -7,7 +7,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
EXE = ROOT / "claro.exe"
EXPECTED = "Claro v2026.10.20.223203"
EXPECTED = "Claro v2026.10.25.014405"
def fail(message):
@@ -31,12 +31,12 @@ def main():
if ide.returncode != 0:
fail(ide.stdout)
metadata = json.loads(ide.stdout)
if metadata.get("version") != "v2026.10.20.223203":
fail(f"Expected IDE metadata version 'v2026.10.20.223203', got {metadata.get('version')!r}")
if metadata.get("version") != "v2026.10.25.014405":
fail(f"Expected IDE metadata version 'v2026.10.25.014405', got {metadata.get('version')!r}")
validator = ROOT / "tools" / "validate_v1_17.py"
text = validator.read_text(encoding="utf-8")
if "Claro v2026.10.20.223203" not in text:
fail("tools/validate_v1_17.py must validate Claro v2026.10.20.223203")
if "Claro v2026.10.25.014405" not in text:
fail("tools/validate_v1_17.py must validate Claro v2026.10.25.014405")
print("Version convention validation OK")