mirror of
https://github.com/gorules/zen.git
synced 2026-10-04 00:02:18 +00:00
* feat: implement slots instead of natural language Replace the natural-language projection (`nl`) with engine-owned slot completion: for a cursor position the engine classifies the slot being edited (operand, operator, literal, member access, ...) and returns typed options, literal facts and the expected type, for policies and graphs. - zen-expression: new `slot` module (classification, literals, operators) and a lenient lexer mode for incomplete input; `nl` removed - Intellisense: typed diagnostic codes with arguments, richer inspect for hover, dedicated parser error variants (messages unchanged) - Engine: `Workspace::cursor_scope`, `slot`, `facts`; `rename_from` and `references_from`; editor spans in UTF-16 units; renames reach imported policies, `$nodes` reads, child graphs and output column heads; no `$root` completion in policies - Node bindings: `slot`, `facts`, `cursorScope`, `slotBatch` replace `nl`, `nlTokenize`, `nlEncodeString`, `nlTokenizeBatch` No change to expression evaluation: the VM, compiler, functions and the default lexer path are untouched, and parser error messages keep their text. * fix: harden slot classification, scopes and graph expectations - restore master execution order; slot scopes hide writes of the edited block and its dependents instead of relying on block ranking - token-start editing gated by slot state; operator, list and operand spans no longer splice into neighbouring tokens - interval-aware bracket pairing for half-open and reversed intervals - lenient lexer recovers from unknown characters (slots only) - graph expected type follows switch and pass-through nodes and merges compatible output schemas - sibling inference caps distinct values and analyses lazily - deterministic overload return type display * fix: order policy blocks by entity reads through closures and relationships - record reads for `#.field` in closures so entity fields read through lists become dependencies - link reads through derived lists (filter/map results) to the entity fields they carry and order the list after those fields - evaluate entity blocks on single relationships, and demand entity paths for plain reads through relationships - slot scopes keep the top-level variable when hiding written fields
126 lines
4.6 KiB
TOML
126 lines
4.6 KiB
TOML
# Per-property dependency-lineage tests (`PolicyWorkspace::dependencies`).
|
|
#
|
|
# policies / content / policy: load policies into the workspace (same as
|
|
# diagnostics.toml — fixtures by filename, or inline `content`).
|
|
# target: the property whose dependency tree is built. May be a sub-path of a
|
|
# computed object/array-of-object (per-field lineage).
|
|
#
|
|
# Assertions check classification of any node in the tree by its dotted path:
|
|
# computed = paths that must resolve to a writer block (lineage continues)
|
|
# inputs = paths that must be plain inputs (no writer, resolvable)
|
|
# unresolved = paths the analyzer could not pin down (flagged, not silently
|
|
# treated as inputs)
|
|
|
|
[[test]]
|
|
name = "loan summary traces through per-account computed interest"
|
|
policies = ["loan_summary.json"]
|
|
target = "accountSummaries"
|
|
computed = ["account.interestDue"]
|
|
inputs = ["accounts.id", "account.balance", "account.apr"]
|
|
|
|
[[test]]
|
|
name = "per-field: projected computed field traces to its specific source"
|
|
policies = ["loan_summary.json"]
|
|
target = "accountSummaries.interest"
|
|
computed = ["account.interestDue"]
|
|
|
|
[[test]]
|
|
name = "per-field: projected input field traces to the input, not the computed sibling"
|
|
policies = ["loan_summary.json"]
|
|
target = "accountSummaries.id"
|
|
inputs = ["accounts.id"]
|
|
|
|
[[test]]
|
|
name = "per-field: ternary-of-objects unions both branches"
|
|
target = "summary.total"
|
|
computed = ["base", "bonus"]
|
|
content = '''
|
|
{
|
|
"blocks": [
|
|
{ "id": "dm", "type": "dataModel", "props": { "data": {
|
|
"name": "globals", "scope": "global",
|
|
"properties": [
|
|
{ "id": "g1", "name": "amount", "type": "number", "array": false, "optional": false },
|
|
{ "id": "g2", "name": "flag", "type": "boolean", "array": false, "optional": false }
|
|
]
|
|
}}},
|
|
{ "id": "s1", "type": "expression", "props": { "data": { "key": "base", "value": "amount * 2" } } },
|
|
{ "id": "s2", "type": "expression", "props": { "data": { "key": "bonus", "value": "amount * 3" } } },
|
|
{ "id": "s3", "type": "expression", "props": { "data": { "key": "summary", "value": "flag ? { total: base } : { total: bonus }" } } }
|
|
]
|
|
}
|
|
'''
|
|
|
|
[[test]]
|
|
name = "per-field: non-decomposable computed object field is flagged unresolved, not silent"
|
|
target = "wrapper.a"
|
|
unresolved = ["wrapper.a"]
|
|
content = '''
|
|
{
|
|
"blocks": [
|
|
{ "id": "dm", "type": "dataModel", "props": { "data": {
|
|
"name": "globals", "scope": "global",
|
|
"properties": [
|
|
{ "id": "g1", "name": "amount", "type": "number", "array": false, "optional": false }
|
|
]
|
|
}}},
|
|
{ "id": "s1", "type": "expression", "props": { "data": { "key": "base", "value": "{ a: amount }" } } },
|
|
{ "id": "s2", "type": "expression", "props": { "data": { "key": "wrapper", "value": "base" } } }
|
|
]
|
|
}
|
|
'''
|
|
|
|
[[test]]
|
|
name = "untrackable closure collection is flagged, resolvable operands stay inputs"
|
|
target = "ledgerTotals"
|
|
unresolved = ["t.amount"]
|
|
inputs = ["deposits", "withdrawals"]
|
|
content = '''
|
|
{
|
|
"blocks": [
|
|
{ "id": "dm-ledger", "type": "dataModel", "props": { "data": {
|
|
"name": "ledger", "scope": "global",
|
|
"properties": [
|
|
{ "id": "g1", "name": "deposits", "type": "number", "array": true, "optional": false },
|
|
{ "id": "g2", "name": "withdrawals", "type": "number", "array": true, "optional": false }
|
|
]
|
|
}}},
|
|
{ "id": "s1", "type": "expression", "props": { "data": { "key": "ledgerTotals", "value": "map(deposits or withdrawals as t, t.amount)" } } }
|
|
]
|
|
}
|
|
'''
|
|
|
|
[[test]]
|
|
name = "nested write parent unions its leaf writers' dependencies"
|
|
target = "loanSummary"
|
|
inputs = ["principal"]
|
|
content = '''
|
|
{
|
|
"blocks": [
|
|
{ "id": "dm", "type": "dataModel", "props": { "data": {
|
|
"name": "globals", "scope": "global",
|
|
"properties": [
|
|
{ "id": "g1", "name": "principal", "type": "number", "array": false, "optional": false }
|
|
]
|
|
}}},
|
|
{ "id": "s1", "type": "expression", "props": { "data": { "key": "loanSummary.byBucket.CURRENT.interestDue", "value": "principal * 0.04" } } },
|
|
{ "id": "s2", "type": "expression", "props": { "data": { "key": "loanSummary.grandTotal", "value": "principal * 1.04" } } }
|
|
]
|
|
}
|
|
'''
|
|
|
|
[[test]]
|
|
name = "dependencies follow the open policy when siblings write the same field (A)"
|
|
policies = ["merge_policy_a.json", "merge_policy_b.json", "merge_policy_c.json"]
|
|
policy = "merge_policy_a.json"
|
|
target = "customer.greeting"
|
|
inputs = ["customer.name"]
|
|
|
|
[[test]]
|
|
name = "dependencies follow the open policy when siblings write the same field (C)"
|
|
policies = ["merge_policy_a.json", "merge_policy_b.json", "merge_policy_c.json"]
|
|
policy = "merge_policy_c.json"
|
|
target = "customer.greeting"
|
|
inputs = ["customer.country"]
|
|
|