Skip to content

Commit 28a59ee

Browse files
committed
3.6.2
1 parent ec38210 commit 28a59ee

67 files changed

Lines changed: 1578 additions & 333 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,123 @@
22

33
All notable changes to this repository are documented in this file.
44

5+
## [3.6.2] — 2026-05-07
6+
7+
A stability and parity release. Closes 27 categories of cross-engine
8+
divergence between `synx-core` (Rust) and `@aperturesyndicate/synx-format`
9+
(TypeScript), plus three new utility markers. **No surface syntax change**
10+
to existing markers — same `.synx` file produces the same JSON in every
11+
binding now.
12+
13+
### Added — three new markers (active mode)
14+
15+
- **`:replace:FROM:TO`** — literal substring replacement on a string value.
16+
`TO` defaults to `""` (deletion). `FROM`/`TO` cannot themselves contain
17+
`:` (chain delimiter); use `{interpolation}` for those cases.
18+
```synx
19+
!active
20+
greeting:replace:l:L Hello there # → "HeLLo there"
21+
```
22+
- **`:sort`** / **`:sort:desc`** — sort an array. Numeric items compare
23+
numerically, otherwise lexicographic. Default direction is ascending.
24+
```synx
25+
!active
26+
ranked:sort:desc
27+
- 5
28+
- 1
29+
- 3
30+
# → [5, 3, 1]
31+
```
32+
- **`:sum`** — sum the numeric items of an array (non-numeric items are
33+
ignored). Returns an integer when all summands are integers, otherwise
34+
a float.
35+
```synx
36+
!active
37+
total:sum
38+
- 19.99
39+
- 29.99
40+
- 5.50
41+
# → 55.48
42+
```
43+
44+
### Fixed — cross-engine parity (CRITICAL)
45+
46+
- **`Synx.parse()` no longer silently switches engines at 5 KB.** The pure-TS
47+
parser is now used unconditionally. The native binding remains available
48+
via `bindings/node` for callers that explicitly opt in. Previously, the
49+
same file produced *different trees* depending on its size.
50+
- **`Synx.parseTool()` rewritten to spec §9.** Sorts top-level keys
51+
lexicographically, takes the first as the tool name, returns
52+
`{ tool, params }` (call mode) or `{ tools: [...] }` (schema mode).
53+
Previously returned empty `{ tool: "", params: {} }`.
54+
- **`Value::Secret` is now properly redacted in JSON output.** `synx-core`'s
55+
`write_json` emits `"[SECRET]"` for the `Value::Secret` variant; previously
56+
the raw secret string was leaked. **Security-relevant**.
57+
- **`.synxb` binary format is now cross-language compatible.** JS
58+
`Synx.compile`/`decompile` now use the same wire format as
59+
`synx-core::binary` (header + raw-deflate payload via Node `zlib`).
60+
- **List items with sub-keys parse identically in both engines.** Rust now
61+
supports the README pattern `- name Sword\n damage 50` as an object,
62+
matching the long-standing JS behaviour. Previously Rust silently
63+
hoisted `damage` into the parent map.
64+
- **`!tool` and `!schema` directives now parsed by JS parser.** Previously
65+
ignored — flags on `SynxParseResult` were never set.
66+
67+
### Fixed — semantic alignment (MAJOR)
68+
69+
- **`pattern` constraint is now enforced in Rust** (`regex` crate added).
70+
- **Constraint validation runs once, not twice.** Eliminates Rust error
71+
messages like `value 52 exceeds max 10` (where 52 was the byte length
72+
of the previous error string).
73+
- **`[constraints]` parser is now bracket-balanced.** Patterns containing
74+
`]` such as `[pattern:^[A-Z]{2}$]` parse correctly in both engines.
75+
- **`:env` casts booleans/`null` from the environment in JS.** `DEBUG=true`
76+
now yields `true` (boolean), not `"true"` (string).
77+
- **`:calc` supports dot-paths in JS** (`base.hp * 5`). Was Rust-only.
78+
- **`:i18n:count_field` plural forms work in JS** (CLDR-inspired:
79+
`one/few/many/other`/`zero`/`two`).
80+
- **`:watch:nested.field` resolves dot-paths** in Rust for both JSON and
81+
SYNX inputs (real `serde_json` parse, real SYNX parse — not substring
82+
search).
83+
- **`:import` marker** is now handled in JS (was Rust-only).
84+
- **`:include` / `:import` markers register an alias** in the includes map
85+
so `{leaf:db}` interpolation works for the README pattern.
86+
- **`:inherit base` with children** — both engines now treat the value
87+
after `:inherit` as the parent name and open a group for the child
88+
block. Previously the README-documented pattern silently broke.
89+
- **`:format:%e`** is implemented in Rust.
90+
- **`:prompt` emits keys in sorted order** in both engines (deterministic
91+
for LLM caching).
92+
- **`Synx.toJSON()` sorts keys lexicographically** (canonical JSON per
93+
spec §10).
94+
- **`__proto__` / `constructor` / `prototype` keys are blocked** in both
95+
engines for safe consumption from JavaScript downstream code.
96+
- **`synx parse` auto-enables `--active`** when the file declares
97+
`!active` or `#!mode:active`. Removes a long-standing UX papercut.
98+
99+
### Fixed — security (MAJOR)
100+
101+
- **Path-jail blocks Linux-style `/foo` and Windows-rooted `\foo`.**
102+
Previously, on Windows, `/etc/passwd` was not classified as absolute
103+
by `Path::is_absolute()` and could escape the base directory in some
104+
edge cases.
105+
- **Error messages are OS-portable.** ENOENT / "system cannot find" both
106+
emit `file not found: <path>`; absolute/rooted blocks emit identical
107+
text on Linux and Windows.
108+
- **JS parser enforces the §3 limits** (16 MiB input, 128 nesting depth,
109+
1 MiB multiline block, 1 M list items, 4 K includes, 4 K enum parts,
110+
512 marker chain segments). Previously only the Rust core had them.
111+
112+
### Fixed — cosmetic
113+
114+
- **Cycle-alias error messages are now stable.** Both keys in an
115+
`a → b → a` cycle produce the same message regardless of HashMap
116+
iteration order.
117+
- **`(unknown_hint)value`** in JS no longer wraps the value back with
118+
the unknown hint; falls through to plain cast (matches Rust
119+
`cast_typed` fallback). A leading `(`-prefixed value with non-identifier
120+
contents (e.g. `(10 + 20) / 2`) is preserved as a literal string.
121+
5122
## [3.6.1] — 2026-04-09
6123

7124
### Added

Cargo.lock

Lines changed: 11 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ exclude = [
1818
]
1919

2020
[workspace.package]
21-
version = "3.6.1"
21+
version = "3.6.2"
2222
edition = "2021"
2323
license = "MIT"
2424

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
> Main SYNX site: https://synx.aperturesyndicate.com/
2+
13
<p align="center">
24
<img src="https://media.aperturesyndicate.com/asother/as/branding/png/aperturesyndicate.png" alt="APERTURESyndicate" width="360" />
35
</p>
@@ -17,6 +19,8 @@
1719

1820
As of **April 2026**, **SYNX 3.6** is **frozen**: the normative definition is [`docs/spec/SYNX-3.6-NORMATIVE.md`](docs/spec/SYNX-3.6-NORMATIVE.md), and the reference implementation is **`synx-core` 3.6.x** checked by [`tests/conformance/`](tests/conformance/). **PATCH** releases may only restore that contract (bugs, spec alignment); new surface syntax stays **additive** until a new normative version (for example 3.7). Full policy: [`docs/spec/CORE-FREEZE.md`](docs/spec/CORE-FREEZE.md).
1921

22+
> **3.6.2 (2026-05-07)** — stability + parity release: closes 27 categories of cross-engine divergence between `synx-core` and `synx-js`, masks `:secret` in CLI JSON output, makes `.synxb` cross-language compatible, balances `[constraints]` brackets so `[pattern:^[A-Z]{2}$]` parses correctly, fixes `Synx.parseTool` reshape, adds `:replace:from:to`, `:sort` / `:sort:desc`, `:sum` markers. See [CHANGELOG.md](CHANGELOG.md).
23+
2024
---
2125

2226
## Documentation and repo map
@@ -499,6 +503,15 @@ recording:audio ./welcome.mp3
499503
db:import ./config/db.synx
500504
production:inherit base
501505
host prod.example.com
506+
shouted:replace:l:L Hello there
507+
ranked:sort:desc
508+
- 5
509+
- 1
510+
- 3
511+
total:sum
512+
- 19.99
513+
- 29.99
514+
- 5.50
502515
```
503516

504517
### Constraints (require `!active`)
@@ -552,11 +565,13 @@ SYNX is designed to be **safe by default** — no code execution, no eval, no ne
552565

553566
| Protection | Description |
554567
|---|---|
555-
| **Path jail** | `:include`, `:import`, `:watch`, `:fallback` paths cannot escape the project's base directory. Absolute paths and `../` traversal are blocked. |
568+
| **Path jail** | `:include`, `:import`, `:watch`, `:fallback` paths cannot escape the project's base directory. Absolute paths, Linux-rooted `/foo`, Windows-rooted `\foo` and `../` traversal are all blocked (3.6.2 closed a Windows-only escape). |
556569
| **Include depth limit** | Nested includes are limited to 16 levels (configurable). Prevents infinite recursion. |
557570
| **File size limit** | Included files > 10 MB are rejected. Prevents memory exhaustion. |
558571
| **Calc expression limit** | Expressions longer than 4096 characters are rejected. |
559572
| **Env isolation** | When `env` option is provided, only that map is used — no fallthrough to `process.env`. |
573+
| **Secret redaction** | Values marked `:secret` are emitted as `"[SECRET]"` in JSON output of every binding (`synx parse`, `Synx.toJSON`, FFI, WASM). Real values are accessible only via the typed `Value::Secret` API. **Fixed in 3.6.2** — earlier versions of the Rust CLI leaked the raw value. |
574+
| **Resource limits everywhere** | The pure-TS engine now enforces the same §3 caps as `synx-core` (16 MiB input, 128 nesting depth, 1 MiB multiline, 1 M list items, …). Browser/Node use is no longer DoS-able by oversized input. |
560575

561576
### Configuration
562577

benchmarks/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
> Main SYNX site: https://synx.aperturesyndicate.com/
2+
13
# SYNX Benchmarks
24

35
Performance comparison: **JSON · YAML · XML · SYNX** across Rust, Node.js, and Python.

benchmarks/llm-tests/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
> Main SYNX site: https://synx.aperturesyndicate.com/
2+
13
# LLM SYNX Format Compatibility Tests
24

35
Comprehensive testing of how well different LLM models understand and work with the SYNX format.

benchmarks/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "synx-benchmarks",
3-
"version": "3.6.1",
3+
"version": "3.6.2",
44
"private": true,
55
"description": "SYNX format benchmarks — JSON vs YAML vs SYNX (JS/Native)",
66
"scripts": {

benchmarks/rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "synx-bench"
3-
version = "3.6.1"
3+
version = "3.6.2"
44
edition = "2021"
55
publish = false
66

bindings/c-header/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "synx-c"
3-
version = "3.6.1"
3+
version = "3.6.2"
44
edition = "2021"
55
authors = ["APERTURESyndicate <support@aperturesyndicate.com>"]
66
description = "SYNX C FFI — shared library for embedding SYNX in any language"

bindings/cpp/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
> Main SYNX site: https://synx.aperturesyndicate.com/
2+
13
# SYNX C++ SDK (`synx-cpp`)
24

35
**Version:** 3.6.1 (format + engine parity with `synx-core`)

0 commit comments

Comments
 (0)