@synapsor/runner 1.1.1 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +123 -1
- package/README.md +52 -31
- package/SECURITY.md +11 -0
- package/THREAT_MODEL.md +7 -4
- package/dist/cli.d.ts +5 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/local-ui.d.ts.map +1 -1
- package/dist/runner.mjs +8103 -2601
- package/docs/README.md +23 -3
- package/docs/benchmarks/bounded-set-local.md +27 -0
- package/docs/bounded-set-writeback.md +298 -0
- package/docs/capability-authoring.md +26 -8
- package/docs/conformance.md +12 -0
- package/docs/current-scope.md +12 -4
- package/docs/doctor.md +21 -7
- package/docs/dsl-reference.md +91 -5
- package/docs/getting-started-own-database.md +26 -3
- package/docs/guarded-crud-writeback.md +218 -0
- package/docs/limitations.md +30 -9
- package/docs/oss-vs-cloud.md +1 -1
- package/docs/production.md +52 -20
- package/docs/release-notes.md +105 -2
- package/docs/release-policy.md +1 -1
- package/docs/result-envelope-v2.md +20 -6
- package/docs/result-envelope-v3.md +43 -0
- package/docs/result-envelope-v4.md +47 -0
- package/docs/reversible-change-sets.md +151 -0
- package/docs/rfcs/004-guarded-crud-receipt-authority.md +165 -0
- package/docs/rfcs/005-bounded-set-writeback.md +164 -0
- package/docs/rfcs/006-reviewed-reversible-change-sets.md +152 -0
- package/docs/runner-config-reference.md +83 -9
- package/docs/running-a-runner-fleet.md +2 -1
- package/docs/security-boundary.md +36 -9
- package/docs/why-synapsor-vs-app-guardrails.md +169 -0
- package/docs/writeback-executors.md +12 -11
- package/examples/runner-fleet/seed/mysql.sql +47 -0
- package/examples/runner-fleet/seed/postgres.sql +48 -0
- package/examples/support-plan-credit/README.md +31 -0
- package/examples/support-plan-credit/contract.synapsor.sql +26 -0
- package/examples/support-plan-credit/expected-output/tools-preview.txt +1 -0
- package/examples/support-plan-credit/seed/001_seed.sql +14 -1
- package/examples/support-plan-credit/synapsor.contract.json +101 -1
- package/examples/support-plan-credit/synapsor.runner.json +7 -1
- package/fixtures/protocol/MANIFEST.json +131 -1
- package/fixtures/protocol/change-set.bounded-update.v3.json +57 -0
- package/fixtures/protocol/change-set.delete.v2.json +31 -0
- package/fixtures/protocol/change-set.insert.v2.json +36 -0
- package/fixtures/protocol/change-set.update.v2.json +32 -0
- package/fixtures/protocol/compensation-change-set.update.v1.json +40 -0
- package/fixtures/protocol/execution-receipt.bounded-update-applied.v3.json +39 -0
- package/fixtures/protocol/execution-receipt.compensation-update-applied.v4.json +37 -0
- package/fixtures/protocol/execution-receipt.delete-applied.v2.json +25 -0
- package/fixtures/protocol/execution-receipt.insert-applied.v2.json +24 -0
- package/fixtures/protocol/execution-receipt.reconciliation-required.v2.json +25 -0
- package/fixtures/protocol/execution-receipt.update-applied.v2.json +25 -0
- package/fixtures/protocol/writeback-job.bounded-update.v3.json +43 -0
- package/fixtures/protocol/writeback-job.compensation-update.v4.json +34 -0
- package/fixtures/protocol/writeback-job.delete.v2.json +22 -0
- package/fixtures/protocol/writeback-job.insert.v2.json +28 -0
- package/fixtures/protocol/writeback-job.update.v2.json +24 -0
- package/package.json +2 -1
- package/schemas/change-set.v2.schema.json +245 -0
- package/schemas/change-set.v3.schema.json +112 -0
- package/schemas/compensation-change-set.v1.schema.json +32 -0
- package/schemas/execution-receipt.v2.schema.json +106 -0
- package/schemas/execution-receipt.v3.schema.json +40 -0
- package/schemas/execution-receipt.v4.schema.json +22 -0
- package/schemas/inverse-descriptor.v1.schema.json +46 -0
- package/schemas/synapsor.runner.schema.json +132 -2
- package/schemas/writeback-job.v2.schema.json +238 -0
- package/schemas/writeback-job.v3.schema.json +53 -0
- package/schemas/writeback-job.v4.schema.json +17 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "synapsor.writeback-job.v4",
|
|
3
|
+
"writeback_job_id": "wbj_revert_1",
|
|
4
|
+
"proposal_id": "wrp_revert_1",
|
|
5
|
+
"proposal_version": 1,
|
|
6
|
+
"proposal_hash": "sha256:revert-proposal",
|
|
7
|
+
"runner_scope": { "project_id": "local", "source_id": "src_1" },
|
|
8
|
+
"engine": "postgres",
|
|
9
|
+
"operation": "restore_update",
|
|
10
|
+
"target": { "schema": "public", "table": "customers", "primary_key": { "column": "id", "value": "CUS-1" } },
|
|
11
|
+
"tenant_guard": { "column": "tenant_id", "value": "acme" },
|
|
12
|
+
"allowed_columns": ["plan_credit_cents"],
|
|
13
|
+
"patch": {},
|
|
14
|
+
"compensation": {
|
|
15
|
+
"schema_version": "synapsor.inverse-descriptor.v1",
|
|
16
|
+
"availability": "available",
|
|
17
|
+
"reason_codes": [],
|
|
18
|
+
"operation": "restore_update",
|
|
19
|
+
"cardinality": "single",
|
|
20
|
+
"forward_proposal_id": "wrp_forward_1",
|
|
21
|
+
"forward_writeback_job_id": "wbj_forward_1",
|
|
22
|
+
"target": { "source_id": "src_1", "schema": "public", "table": "customers", "primary_key_column": "id" },
|
|
23
|
+
"tenant_guard": { "column": "tenant_id", "value": "acme" },
|
|
24
|
+
"allowed_columns": ["plan_credit_cents"],
|
|
25
|
+
"members": [{ "primary_key": { "column": "id", "value": "CUS-1" }, "expected_state": { "plan_credit_cents": 2500, "version": 8 }, "restore_values": { "plan_credit_cents": 0 } }],
|
|
26
|
+
"max_rows": 1,
|
|
27
|
+
"aggregate_bounds": [],
|
|
28
|
+
"version_advance": { "column": "version", "strategy": "integer_increment" },
|
|
29
|
+
"lineage": { "root_proposal_id": "wrp_forward_1", "parent_proposal_id": "wrp_forward_1", "reverts_proposal_id": "wrp_forward_1", "depth": 1 }
|
|
30
|
+
},
|
|
31
|
+
"forward_receipt_hash": "sha256:forward-receipt",
|
|
32
|
+
"idempotency_key": "revert:wrp_forward_1:wrp_revert_1",
|
|
33
|
+
"lease": { "lease_id": "lease_revert_1", "attempt": 1, "expires_at": "2026-07-13T23:59:59Z" }
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "synapsor.writeback-job.v2",
|
|
3
|
+
"writeback_job_id": "wbj_delete_2003",
|
|
4
|
+
"proposal_id": "wrp_delete_2003",
|
|
5
|
+
"proposal_version": 1,
|
|
6
|
+
"proposal_hash": "sha256:delete-proposal",
|
|
7
|
+
"runner_scope": { "project_id": "project_demo", "source_id": "src_support_demo" },
|
|
8
|
+
"engine": "mysql",
|
|
9
|
+
"target": {
|
|
10
|
+
"schema": "support",
|
|
11
|
+
"table": "pending_tasks",
|
|
12
|
+
"primary_key": { "column": "id", "value": "task_2003" }
|
|
13
|
+
},
|
|
14
|
+
"tenant_guard": { "column": "tenant_id", "value": "tenant_demo" },
|
|
15
|
+
"allowed_columns": [],
|
|
16
|
+
"mutation": {
|
|
17
|
+
"kind": "single_row_delete",
|
|
18
|
+
"conflict_guard": { "kind": "column", "column": "version", "expected_value": 4 }
|
|
19
|
+
},
|
|
20
|
+
"idempotency_key": "idem_delete_2003",
|
|
21
|
+
"lease": { "lease_id": "lease_delete_2003", "attempt": 1, "expires_at": "2026-07-13T19:02:00Z" }
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "synapsor.writeback-job.v2",
|
|
3
|
+
"writeback_job_id": "wbj_insert_2002",
|
|
4
|
+
"proposal_id": "wrp_insert_2002",
|
|
5
|
+
"proposal_version": 1,
|
|
6
|
+
"proposal_hash": "sha256:insert-proposal",
|
|
7
|
+
"runner_scope": { "project_id": "project_demo", "source_id": "src_billing_demo" },
|
|
8
|
+
"engine": "postgres",
|
|
9
|
+
"target": {
|
|
10
|
+
"schema": "public",
|
|
11
|
+
"table": "account_credits",
|
|
12
|
+
"primary_key": { "column": "id" }
|
|
13
|
+
},
|
|
14
|
+
"tenant_guard": { "column": "tenant_id", "value": "tenant_demo" },
|
|
15
|
+
"allowed_columns": ["amount_cents", "reason"],
|
|
16
|
+
"mutation": {
|
|
17
|
+
"kind": "single_row_insert",
|
|
18
|
+
"values": { "amount_cents": 2500, "reason": "service interruption" },
|
|
19
|
+
"deduplication": {
|
|
20
|
+
"components": [
|
|
21
|
+
{ "column": "tenant_id", "value": "tenant_demo", "source": "trusted_tenant" },
|
|
22
|
+
{ "column": "request_id", "value": "wrp_insert_2002", "source": "proposal_id" }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"idempotency_key": "idem_insert_2002",
|
|
27
|
+
"lease": { "lease_id": "lease_insert_2002", "attempt": 1, "expires_at": "2026-07-13T19:01:00Z" }
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "synapsor.writeback-job.v2",
|
|
3
|
+
"writeback_job_id": "wbj_update_2001",
|
|
4
|
+
"proposal_id": "wrp_update_2001",
|
|
5
|
+
"proposal_version": 1,
|
|
6
|
+
"proposal_hash": "sha256:update-proposal",
|
|
7
|
+
"runner_scope": { "project_id": "project_demo", "source_id": "src_billing_demo" },
|
|
8
|
+
"engine": "postgres",
|
|
9
|
+
"target": {
|
|
10
|
+
"schema": "public",
|
|
11
|
+
"table": "account_credits",
|
|
12
|
+
"primary_key": { "column": "id", "value": "credit_2001" }
|
|
13
|
+
},
|
|
14
|
+
"tenant_guard": { "column": "tenant_id", "value": "tenant_demo" },
|
|
15
|
+
"allowed_columns": ["amount_cents"],
|
|
16
|
+
"mutation": {
|
|
17
|
+
"kind": "single_row_update",
|
|
18
|
+
"values": { "amount_cents": 2500 },
|
|
19
|
+
"conflict_guard": { "kind": "column", "column": "version", "expected_value": 7 },
|
|
20
|
+
"version_advance": { "column": "version", "strategy": "integer_increment" }
|
|
21
|
+
},
|
|
22
|
+
"idempotency_key": "idem_update_2001",
|
|
23
|
+
"lease": { "lease_id": "lease_update_2001", "attempt": 1, "expires_at": "2026-07-13T19:00:00Z" }
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synapsor/runner",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Stop giving AI agents execute_sql; expose reviewed Postgres/MySQL MCP actions with proposals, approval, writeback, and replay.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"AGENTS.md",
|
|
36
36
|
"CHANGELOG.md",
|
|
37
37
|
"CONTRIBUTING.md",
|
|
38
|
+
"SECURITY.md",
|
|
38
39
|
"LICENSE",
|
|
39
40
|
"NOTICE",
|
|
40
41
|
"THREAT_MODEL.md",
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.synapsor.ai/synapsor.change-set.v2.schema.json",
|
|
4
|
+
"title": "Synapsor Change Set v2",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"proposal_id",
|
|
10
|
+
"proposal_version",
|
|
11
|
+
"action",
|
|
12
|
+
"operation",
|
|
13
|
+
"mode",
|
|
14
|
+
"principal",
|
|
15
|
+
"scope",
|
|
16
|
+
"source",
|
|
17
|
+
"before",
|
|
18
|
+
"patch",
|
|
19
|
+
"after",
|
|
20
|
+
"guards",
|
|
21
|
+
"evidence",
|
|
22
|
+
"approval",
|
|
23
|
+
"writeback",
|
|
24
|
+
"source_database_mutated",
|
|
25
|
+
"integrity",
|
|
26
|
+
"created_at"
|
|
27
|
+
],
|
|
28
|
+
"properties": {
|
|
29
|
+
"schema_version": { "const": "synapsor.change-set.v2" },
|
|
30
|
+
"proposal_id": { "type": "string", "minLength": 1 },
|
|
31
|
+
"proposal_version": { "type": "integer", "minimum": 1 },
|
|
32
|
+
"action": { "type": "string", "minLength": 1 },
|
|
33
|
+
"operation": { "enum": ["single_row_update", "single_row_insert", "single_row_delete"] },
|
|
34
|
+
"mode": { "enum": ["read_only", "shadow", "review_required", "approved_for_writeback"] },
|
|
35
|
+
"principal": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"additionalProperties": false,
|
|
38
|
+
"required": ["id", "source"],
|
|
39
|
+
"properties": {
|
|
40
|
+
"id": { "type": "string", "minLength": 1 },
|
|
41
|
+
"source": { "enum": ["trusted_session", "cloud_session", "environment", "static_dev"] }
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scope": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"required": ["tenant_id", "business_object", "object_id"],
|
|
48
|
+
"properties": {
|
|
49
|
+
"tenant_id": { "type": "string", "minLength": 1 },
|
|
50
|
+
"business_object": { "type": "string", "minLength": 1 },
|
|
51
|
+
"object_id": { "type": "string", "minLength": 1 }
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"source": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"additionalProperties": false,
|
|
57
|
+
"required": ["kind", "source_id", "schema", "table", "primary_key"],
|
|
58
|
+
"properties": {
|
|
59
|
+
"kind": { "enum": ["external_postgres", "external_mysql", "synapsor_table"] },
|
|
60
|
+
"source_id": { "type": "string", "minLength": 1 },
|
|
61
|
+
"schema": { "$ref": "#/$defs/safeIdentifier" },
|
|
62
|
+
"table": { "$ref": "#/$defs/safeIdentifier" },
|
|
63
|
+
"primary_key": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"additionalProperties": false,
|
|
66
|
+
"required": ["column"],
|
|
67
|
+
"properties": {
|
|
68
|
+
"column": { "$ref": "#/$defs/safeIdentifier" },
|
|
69
|
+
"value": { "$ref": "#/$defs/scalar" }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"before": { "$ref": "#/$defs/scalarRecord" },
|
|
75
|
+
"patch": { "$ref": "#/$defs/scalarRecord" },
|
|
76
|
+
"after": { "$ref": "#/$defs/scalarRecord" },
|
|
77
|
+
"guards": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"additionalProperties": false,
|
|
80
|
+
"required": ["tenant", "allowed_columns"],
|
|
81
|
+
"properties": {
|
|
82
|
+
"tenant": { "$ref": "#/$defs/columnValue" },
|
|
83
|
+
"allowed_columns": {
|
|
84
|
+
"type": "array",
|
|
85
|
+
"items": { "$ref": "#/$defs/safeIdentifier" },
|
|
86
|
+
"maxItems": 256,
|
|
87
|
+
"uniqueItems": true
|
|
88
|
+
},
|
|
89
|
+
"expected_version": { "$ref": "#/$defs/columnValue" },
|
|
90
|
+
"version_advance": { "$ref": "#/$defs/versionAdvance" },
|
|
91
|
+
"deduplication": { "$ref": "#/$defs/deduplication" }
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"evidence": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"additionalProperties": true,
|
|
97
|
+
"required": ["bundle_id", "query_fingerprint", "items"],
|
|
98
|
+
"properties": {
|
|
99
|
+
"bundle_id": { "type": "string", "minLength": 1 },
|
|
100
|
+
"query_fingerprint": { "$ref": "#/$defs/sha256" },
|
|
101
|
+
"items": { "type": "array", "maxItems": 1000 }
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"approval": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"additionalProperties": true,
|
|
107
|
+
"required": ["status"],
|
|
108
|
+
"properties": {
|
|
109
|
+
"status": { "enum": ["pending", "approved", "rejected", "canceled"] },
|
|
110
|
+
"required_role": { "type": "string" },
|
|
111
|
+
"required_approvals": { "type": "integer", "minimum": 1, "maximum": 10 }
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"writeback": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"additionalProperties": true,
|
|
117
|
+
"required": ["status", "mode"],
|
|
118
|
+
"properties": {
|
|
119
|
+
"status": { "enum": ["not_applied", "pending_worker", "applied", "conflict", "failed", "canceled", "reconciliation_required"] },
|
|
120
|
+
"mode": { "enum": ["trusted_worker_required", "synapsor_merge", "read_only"] }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"source_database_mutated": { "type": "boolean" },
|
|
124
|
+
"integrity": {
|
|
125
|
+
"type": "object",
|
|
126
|
+
"additionalProperties": false,
|
|
127
|
+
"required": ["proposal_hash"],
|
|
128
|
+
"properties": { "proposal_hash": { "$ref": "#/$defs/sha256" } }
|
|
129
|
+
},
|
|
130
|
+
"created_at": { "type": "string", "format": "date-time" }
|
|
131
|
+
},
|
|
132
|
+
"allOf": [
|
|
133
|
+
{
|
|
134
|
+
"if": { "properties": { "operation": { "const": "single_row_insert" } } },
|
|
135
|
+
"then": {
|
|
136
|
+
"properties": {
|
|
137
|
+
"before": { "maxProperties": 0 },
|
|
138
|
+
"patch": { "minProperties": 1 },
|
|
139
|
+
"after": { "minProperties": 1 },
|
|
140
|
+
"guards": {
|
|
141
|
+
"required": ["deduplication"],
|
|
142
|
+
"not": { "anyOf": [{ "required": ["expected_version"] }, { "required": ["version_advance"] }] },
|
|
143
|
+
"properties": {
|
|
144
|
+
"allowed_columns": { "minItems": 1 },
|
|
145
|
+
"deduplication": {
|
|
146
|
+
"properties": {
|
|
147
|
+
"components": {
|
|
148
|
+
"allOf": [
|
|
149
|
+
{ "contains": { "properties": { "source": { "const": "proposal_id" } }, "required": ["source"] } },
|
|
150
|
+
{ "contains": { "properties": { "source": { "const": "trusted_tenant" } }, "required": ["source"] } }
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"if": { "properties": { "operation": { "const": "single_row_update" } } },
|
|
162
|
+
"then": {
|
|
163
|
+
"properties": {
|
|
164
|
+
"source": { "properties": { "primary_key": { "required": ["value"] } } },
|
|
165
|
+
"before": { "minProperties": 1 },
|
|
166
|
+
"patch": { "minProperties": 1 },
|
|
167
|
+
"after": { "minProperties": 1 },
|
|
168
|
+
"guards": {
|
|
169
|
+
"required": ["expected_version"],
|
|
170
|
+
"not": { "required": ["deduplication"] },
|
|
171
|
+
"properties": { "allowed_columns": { "minItems": 1 } }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"if": { "properties": { "operation": { "const": "single_row_delete" } } },
|
|
178
|
+
"then": {
|
|
179
|
+
"properties": {
|
|
180
|
+
"source": { "properties": { "primary_key": { "required": ["value"] } } },
|
|
181
|
+
"before": { "minProperties": 1 },
|
|
182
|
+
"patch": { "maxProperties": 0 },
|
|
183
|
+
"after": { "maxProperties": 0 },
|
|
184
|
+
"guards": {
|
|
185
|
+
"required": ["expected_version"],
|
|
186
|
+
"not": { "anyOf": [{ "required": ["version_advance"] }, { "required": ["deduplication"] }] },
|
|
187
|
+
"properties": { "allowed_columns": { "maxItems": 0 } }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
],
|
|
193
|
+
"$defs": {
|
|
194
|
+
"safeIdentifier": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
|
|
195
|
+
"sha256": { "type": "string", "pattern": "^sha256:.+" },
|
|
196
|
+
"scalar": { "type": ["string", "number", "boolean", "null"] },
|
|
197
|
+
"scalarRecord": {
|
|
198
|
+
"type": "object",
|
|
199
|
+
"propertyNames": { "$ref": "#/$defs/safeIdentifier" },
|
|
200
|
+
"additionalProperties": { "$ref": "#/$defs/scalar" },
|
|
201
|
+
"maxProperties": 256
|
|
202
|
+
},
|
|
203
|
+
"columnValue": {
|
|
204
|
+
"type": "object",
|
|
205
|
+
"additionalProperties": false,
|
|
206
|
+
"required": ["column", "value"],
|
|
207
|
+
"properties": {
|
|
208
|
+
"column": { "$ref": "#/$defs/safeIdentifier" },
|
|
209
|
+
"value": { "$ref": "#/$defs/scalar" }
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
"versionAdvance": {
|
|
213
|
+
"type": "object",
|
|
214
|
+
"additionalProperties": false,
|
|
215
|
+
"required": ["column", "strategy"],
|
|
216
|
+
"properties": {
|
|
217
|
+
"column": { "$ref": "#/$defs/safeIdentifier" },
|
|
218
|
+
"strategy": { "enum": ["integer_increment", "database_generated"] }
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
"deduplicationComponent": {
|
|
222
|
+
"type": "object",
|
|
223
|
+
"additionalProperties": false,
|
|
224
|
+
"required": ["column", "value", "source"],
|
|
225
|
+
"properties": {
|
|
226
|
+
"column": { "$ref": "#/$defs/safeIdentifier" },
|
|
227
|
+
"value": { "$ref": "#/$defs/scalar" },
|
|
228
|
+
"source": { "enum": ["proposal_id", "trusted_tenant", "fixed"] }
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"deduplication": {
|
|
232
|
+
"type": "object",
|
|
233
|
+
"additionalProperties": false,
|
|
234
|
+
"required": ["components"],
|
|
235
|
+
"properties": {
|
|
236
|
+
"components": {
|
|
237
|
+
"type": "array",
|
|
238
|
+
"items": { "$ref": "#/$defs/deduplicationComponent" },
|
|
239
|
+
"minItems": 1,
|
|
240
|
+
"maxItems": 8
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.synapsor.ai/synapsor.change-set.v3.schema.json",
|
|
4
|
+
"title": "Synapsor Bounded Change Set v3",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "proposal_id", "proposal_version", "action", "operation", "mode", "principal", "scope", "source", "before", "patch", "after", "guards", "frozen_set", "evidence", "approval", "writeback", "source_database_mutated", "integrity", "created_at"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": "synapsor.change-set.v3" },
|
|
10
|
+
"proposal_id": { "type": "string", "minLength": 1 },
|
|
11
|
+
"proposal_version": { "type": "integer", "minimum": 1 },
|
|
12
|
+
"action": { "type": "string", "minLength": 1 },
|
|
13
|
+
"operation": { "$ref": "#/$defs/setOperation" },
|
|
14
|
+
"mode": { "enum": ["read_only", "shadow", "review_required", "approved_for_writeback"] },
|
|
15
|
+
"principal": {
|
|
16
|
+
"type": "object", "additionalProperties": false, "required": ["id", "source"],
|
|
17
|
+
"properties": { "id": { "type": "string", "minLength": 1 }, "source": { "enum": ["trusted_session", "cloud_session", "environment", "static_dev"] } }
|
|
18
|
+
},
|
|
19
|
+
"scope": {
|
|
20
|
+
"type": "object", "additionalProperties": false, "required": ["tenant_id", "business_object", "object_id"],
|
|
21
|
+
"properties": { "tenant_id": { "type": "string", "minLength": 1 }, "business_object": { "type": "string", "minLength": 1 }, "object_id": { "type": "string", "minLength": 1 } }
|
|
22
|
+
},
|
|
23
|
+
"source": {
|
|
24
|
+
"type": "object", "additionalProperties": false, "required": ["kind", "source_id", "schema", "table", "primary_key"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"kind": { "enum": ["external_postgres", "external_mysql", "synapsor_table"] },
|
|
27
|
+
"source_id": { "type": "string", "minLength": 1 },
|
|
28
|
+
"schema": { "$ref": "#/$defs/safeIdentifier" },
|
|
29
|
+
"table": { "$ref": "#/$defs/safeIdentifier" },
|
|
30
|
+
"primary_key": { "type": "object", "additionalProperties": false, "required": ["column"], "properties": { "column": { "$ref": "#/$defs/safeIdentifier" } } }
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"before": { "$ref": "#/$defs/scalarRecord" },
|
|
34
|
+
"patch": { "$ref": "#/$defs/scalarRecord" },
|
|
35
|
+
"after": { "$ref": "#/$defs/scalarRecord" },
|
|
36
|
+
"guards": {
|
|
37
|
+
"type": "object", "additionalProperties": false, "required": ["tenant", "allowed_columns"],
|
|
38
|
+
"properties": {
|
|
39
|
+
"tenant": { "$ref": "#/$defs/columnValue" },
|
|
40
|
+
"allowed_columns": { "type": "array", "items": { "$ref": "#/$defs/safeIdentifier" }, "maxItems": 256, "uniqueItems": true },
|
|
41
|
+
"version_advance": { "$ref": "#/$defs/versionAdvance" }
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"frozen_set": { "$ref": "#/$defs/frozenSet" },
|
|
45
|
+
"evidence": {
|
|
46
|
+
"type": "object", "additionalProperties": true, "required": ["bundle_id", "query_fingerprint", "items"],
|
|
47
|
+
"properties": { "bundle_id": { "type": "string", "minLength": 1 }, "query_fingerprint": { "$ref": "#/$defs/sha256" }, "items": { "type": "array", "maxItems": 100 } }
|
|
48
|
+
},
|
|
49
|
+
"approval": {
|
|
50
|
+
"type": "object", "additionalProperties": true, "required": ["status", "mode"],
|
|
51
|
+
"properties": { "status": { "enum": ["pending", "approved", "rejected", "canceled"] }, "mode": { "enum": ["human", "operator"] }, "required_role": { "type": "string", "minLength": 1 }, "required_approvals": { "type": "integer", "minimum": 1, "maximum": 10 } }
|
|
52
|
+
},
|
|
53
|
+
"writeback": {
|
|
54
|
+
"type": "object", "additionalProperties": true, "required": ["status", "mode", "executor"],
|
|
55
|
+
"properties": { "status": { "enum": ["not_applied", "pending_worker", "applied", "conflict", "failed", "canceled", "reconciliation_required"] }, "mode": { "const": "trusted_worker_required" }, "executor": { "const": "sql_update" } }
|
|
56
|
+
},
|
|
57
|
+
"source_database_mutated": { "type": "boolean" },
|
|
58
|
+
"integrity": { "type": "object", "additionalProperties": false, "required": ["proposal_hash"], "properties": { "proposal_hash": { "$ref": "#/$defs/sha256" } } },
|
|
59
|
+
"created_at": { "type": "string", "format": "date-time" }
|
|
60
|
+
},
|
|
61
|
+
"allOf": [
|
|
62
|
+
{
|
|
63
|
+
"if": { "properties": { "operation": { "const": "set_update" } }, "required": ["operation"] },
|
|
64
|
+
"then": {
|
|
65
|
+
"properties": {
|
|
66
|
+
"patch": { "minProperties": 1 },
|
|
67
|
+
"guards": { "required": ["version_advance"], "properties": { "allowed_columns": { "minItems": 1 }, "version_advance": { "properties": { "strategy": { "const": "integer_increment" } } } } },
|
|
68
|
+
"frozen_set": { "properties": { "members": { "items": { "required": ["expected_version", "before_digest", "after_digest"], "not": { "anyOf": [{ "required": ["tombstone_digest"] }, { "required": ["deduplication"] }] } } } } }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"if": { "properties": { "operation": { "const": "set_delete" } }, "required": ["operation"] },
|
|
74
|
+
"then": {
|
|
75
|
+
"properties": {
|
|
76
|
+
"patch": { "maxProperties": 0 },
|
|
77
|
+
"guards": { "not": { "required": ["version_advance"] }, "properties": { "allowed_columns": { "maxItems": 0 } } },
|
|
78
|
+
"frozen_set": { "properties": { "members": { "items": { "required": ["expected_version", "before_digest", "tombstone_digest"], "properties": { "after": { "maxProperties": 0 } }, "not": { "anyOf": [{ "required": ["after_digest"] }, { "required": ["deduplication"] }] } } } } }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"if": { "properties": { "operation": { "const": "batch_insert" } }, "required": ["operation"] },
|
|
84
|
+
"then": {
|
|
85
|
+
"properties": {
|
|
86
|
+
"guards": { "not": { "required": ["version_advance"] }, "properties": { "allowed_columns": { "minItems": 1 } } },
|
|
87
|
+
"frozen_set": { "properties": { "members": { "items": { "required": ["after_digest", "deduplication"], "properties": { "before": { "maxProperties": 0 }, "after": { "minProperties": 1 } }, "not": { "anyOf": [{ "required": ["expected_version"] }, { "required": ["before_digest"] }, { "required": ["tombstone_digest"] }] } } } } }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"$defs": {
|
|
93
|
+
"safeIdentifier": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
|
|
94
|
+
"sha256": { "type": "string", "pattern": "^sha256:.+" },
|
|
95
|
+
"scalar": { "type": ["string", "number", "boolean", "null"] },
|
|
96
|
+
"scalarRecord": { "type": "object", "propertyNames": { "$ref": "#/$defs/safeIdentifier" }, "additionalProperties": { "$ref": "#/$defs/scalar" }, "maxProperties": 256 },
|
|
97
|
+
"setOperation": { "enum": ["set_update", "set_delete", "batch_insert"] },
|
|
98
|
+
"columnValue": { "type": "object", "additionalProperties": false, "required": ["column", "value"], "properties": { "column": { "$ref": "#/$defs/safeIdentifier" }, "value": { "$ref": "#/$defs/scalar" } } },
|
|
99
|
+
"versionAdvance": { "type": "object", "additionalProperties": false, "required": ["column", "strategy"], "properties": { "column": { "$ref": "#/$defs/safeIdentifier" }, "strategy": { "enum": ["integer_increment", "database_generated"] } } },
|
|
100
|
+
"deduplicationComponent": { "type": "object", "additionalProperties": false, "required": ["column", "value", "source"], "properties": { "column": { "$ref": "#/$defs/safeIdentifier" }, "value": { "$ref": "#/$defs/scalar" }, "source": { "enum": ["proposal_id", "trusted_tenant", "fixed"] } } },
|
|
101
|
+
"deduplication": { "type": "object", "additionalProperties": false, "required": ["components"], "properties": { "components": { "type": "array", "items": { "$ref": "#/$defs/deduplicationComponent" }, "minItems": 1, "maxItems": 8 } } },
|
|
102
|
+
"aggregateBound": { "type": "object", "additionalProperties": false, "required": ["column", "measure", "maximum", "actual"], "properties": { "column": { "$ref": "#/$defs/safeIdentifier" }, "measure": { "enum": ["before", "after", "absolute_delta"] }, "maximum": { "type": "number", "minimum": 0 }, "actual": { "type": "number", "minimum": 0 } } },
|
|
103
|
+
"frozenMember": {
|
|
104
|
+
"type": "object", "additionalProperties": false, "required": ["primary_key", "before", "after"],
|
|
105
|
+
"properties": { "primary_key": { "$ref": "#/$defs/columnValue" }, "expected_version": { "$ref": "#/$defs/columnValue" }, "before": { "$ref": "#/$defs/scalarRecord" }, "after": { "$ref": "#/$defs/scalarRecord" }, "before_digest": { "$ref": "#/$defs/sha256" }, "after_digest": { "$ref": "#/$defs/sha256" }, "tombstone_digest": { "$ref": "#/$defs/sha256" }, "deduplication": { "$ref": "#/$defs/deduplication" } }
|
|
106
|
+
},
|
|
107
|
+
"frozenSet": {
|
|
108
|
+
"type": "object", "additionalProperties": false, "required": ["max_rows", "row_count", "aggregate_bounds", "members", "set_digest"],
|
|
109
|
+
"properties": { "max_rows": { "type": "integer", "minimum": 1, "maximum": 100 }, "row_count": { "type": "integer", "minimum": 1, "maximum": 100 }, "aggregate_bounds": { "type": "array", "items": { "$ref": "#/$defs/aggregateBound" }, "minItems": 1, "maxItems": 8 }, "members": { "type": "array", "items": { "$ref": "#/$defs/frozenMember" }, "minItems": 1, "maxItems": 100 }, "set_digest": { "$ref": "#/$defs/sha256" } }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.synapsor.ai/synapsor.compensation-change-set.v1.schema.json",
|
|
4
|
+
"title": "Synapsor Reviewed Compensation Change Set v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "proposal_id", "proposal_version", "action", "mode", "principal", "scope", "source", "before", "patch", "after", "compensation", "guards", "evidence", "approval", "writeback", "source_database_mutated", "integrity", "created_at"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": "synapsor.compensation-change-set.v1" },
|
|
10
|
+
"proposal_id": { "type": "string", "minLength": 1 },
|
|
11
|
+
"proposal_version": { "type": "integer", "minimum": 1 },
|
|
12
|
+
"action": { "type": "string", "minLength": 1 },
|
|
13
|
+
"mode": { "const": "review_required" },
|
|
14
|
+
"principal": { "type": "object", "additionalProperties": false, "required": ["id", "source"], "properties": { "id": { "type": "string", "minLength": 1 }, "source": { "enum": ["trusted_session", "cloud_session", "environment", "static_dev"] } } },
|
|
15
|
+
"scope": { "type": "object", "additionalProperties": false, "required": ["tenant_id", "business_object", "object_id"], "properties": { "tenant_id": { "type": "string", "minLength": 1 }, "business_object": { "type": "string", "minLength": 1 }, "object_id": { "type": "string", "minLength": 1 } } },
|
|
16
|
+
"source": { "type": "object", "additionalProperties": false, "required": ["kind", "source_id", "schema", "table", "primary_key"], "properties": { "kind": { "enum": ["external_postgres", "external_mysql"] }, "source_id": { "type": "string", "minLength": 1 }, "schema": { "$ref": "#/$defs/identifier" }, "table": { "$ref": "#/$defs/identifier" }, "primary_key": { "type": "object", "additionalProperties": false, "required": ["column"], "properties": { "column": { "$ref": "#/$defs/identifier" }, "value": { "$ref": "#/$defs/scalar" } } } } },
|
|
17
|
+
"before": { "$ref": "#/$defs/record" }, "patch": { "$ref": "#/$defs/record" }, "after": { "$ref": "#/$defs/record" },
|
|
18
|
+
"compensation": { "type": "object", "additionalProperties": false, "required": ["descriptor", "forward_receipt_hash"], "properties": { "descriptor": { "$ref": "inverse-descriptor.v1.schema.json" }, "forward_receipt_hash": { "$ref": "#/$defs/sha256" } } },
|
|
19
|
+
"guards": { "type": "object", "additionalProperties": false, "required": ["tenant", "allowed_columns"], "properties": { "tenant": { "$ref": "#/$defs/columnValue" }, "allowed_columns": { "type": "array", "items": { "$ref": "#/$defs/identifier" }, "maxItems": 256, "uniqueItems": true } } },
|
|
20
|
+
"evidence": { "type": "object", "required": ["bundle_id", "query_fingerprint", "items"], "properties": { "bundle_id": { "type": "string", "minLength": 1 }, "query_fingerprint": { "$ref": "#/$defs/sha256" }, "items": { "type": "array", "maxItems": 100 } } },
|
|
21
|
+
"approval": { "type": "object", "required": ["status", "mode"], "properties": { "status": { "enum": ["pending", "approved", "rejected", "canceled"] }, "mode": { "enum": ["human", "operator"] }, "required_role": { "type": "string", "minLength": 1 }, "required_approvals": { "type": "integer", "minimum": 1, "maximum": 10 } } },
|
|
22
|
+
"writeback": { "type": "object", "additionalProperties": false, "required": ["status", "mode", "executor"], "properties": { "status": { "const": "not_applied" }, "mode": { "const": "trusted_worker_required" }, "executor": { "const": "sql_update" } } },
|
|
23
|
+
"source_database_mutated": { "const": false },
|
|
24
|
+
"integrity": { "type": "object", "additionalProperties": false, "required": ["proposal_hash"], "properties": { "proposal_hash": { "$ref": "#/$defs/sha256" } } },
|
|
25
|
+
"created_at": { "type": "string", "format": "date-time" }
|
|
26
|
+
},
|
|
27
|
+
"$defs": {
|
|
28
|
+
"identifier": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, "sha256": { "type": "string", "pattern": "^sha256:.+" }, "scalar": { "type": ["string", "number", "boolean", "null"] },
|
|
29
|
+
"record": { "type": "object", "propertyNames": { "$ref": "#/$defs/identifier" }, "additionalProperties": { "$ref": "#/$defs/scalar" }, "maxProperties": 256 },
|
|
30
|
+
"columnValue": { "type": "object", "additionalProperties": false, "required": ["column", "value"], "properties": { "column": { "$ref": "#/$defs/identifier" }, "value": { "$ref": "#/$defs/scalar" } } }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.synapsor.ai/synapsor.execution-receipt.v2.schema.json",
|
|
4
|
+
"title": "Synapsor Execution Receipt v2",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"writeback_job_id",
|
|
10
|
+
"proposal_id",
|
|
11
|
+
"proposal_hash",
|
|
12
|
+
"approval_id",
|
|
13
|
+
"runner_id",
|
|
14
|
+
"operation",
|
|
15
|
+
"receipt_authority",
|
|
16
|
+
"status",
|
|
17
|
+
"target",
|
|
18
|
+
"rows_affected",
|
|
19
|
+
"idempotency_key",
|
|
20
|
+
"source_database_mutated",
|
|
21
|
+
"safe_outcome_code",
|
|
22
|
+
"executed_at",
|
|
23
|
+
"receipt_hash"
|
|
24
|
+
],
|
|
25
|
+
"properties": {
|
|
26
|
+
"schema_version": { "const": "synapsor.execution-receipt.v2" },
|
|
27
|
+
"writeback_job_id": { "type": "string", "minLength": 1 },
|
|
28
|
+
"proposal_id": { "type": "string", "minLength": 1 },
|
|
29
|
+
"proposal_hash": { "$ref": "#/$defs/sha256" },
|
|
30
|
+
"approval_id": { "type": "string", "minLength": 1 },
|
|
31
|
+
"runner_id": { "type": "string", "minLength": 1 },
|
|
32
|
+
"operation": { "enum": ["single_row_update", "single_row_insert", "single_row_delete"] },
|
|
33
|
+
"receipt_authority": { "enum": ["source_db", "runner_ledger"] },
|
|
34
|
+
"status": { "enum": ["applied", "conflict", "failed", "canceled", "already_applied", "reconciliation_required"] },
|
|
35
|
+
"target": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"additionalProperties": false,
|
|
38
|
+
"required": ["source_id", "schema", "table", "identity"],
|
|
39
|
+
"properties": {
|
|
40
|
+
"source_id": { "type": "string", "minLength": 1 },
|
|
41
|
+
"schema": { "$ref": "#/$defs/safeIdentifier" },
|
|
42
|
+
"table": { "$ref": "#/$defs/safeIdentifier" },
|
|
43
|
+
"identity": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"items": { "$ref": "#/$defs/columnValue" },
|
|
46
|
+
"minItems": 1,
|
|
47
|
+
"maxItems": 8
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"rows_affected": { "type": "integer", "minimum": 0, "maximum": 1 },
|
|
52
|
+
"idempotency_key": { "type": "string", "minLength": 1 },
|
|
53
|
+
"before_digest": { "$ref": "#/$defs/sha256" },
|
|
54
|
+
"after_digest": { "$ref": "#/$defs/sha256" },
|
|
55
|
+
"tombstone_digest": { "$ref": "#/$defs/sha256" },
|
|
56
|
+
"source_database_mutated": { "type": "boolean" },
|
|
57
|
+
"safe_outcome_code": { "$ref": "#/$defs/code" },
|
|
58
|
+
"safe_error_code": { "$ref": "#/$defs/code" },
|
|
59
|
+
"executed_at": { "type": "string", "format": "date-time" },
|
|
60
|
+
"receipt_hash": { "$ref": "#/$defs/sha256" },
|
|
61
|
+
"reconciliation": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"required": ["intent_id", "reason"],
|
|
65
|
+
"properties": {
|
|
66
|
+
"intent_id": { "type": "string", "minLength": 1 },
|
|
67
|
+
"reason": { "type": "string", "minLength": 1 }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"allOf": [
|
|
72
|
+
{
|
|
73
|
+
"if": { "properties": { "status": { "const": "reconciliation_required" } } },
|
|
74
|
+
"then": { "required": ["reconciliation"] }
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"if": {
|
|
78
|
+
"properties": {
|
|
79
|
+
"operation": { "const": "single_row_delete" },
|
|
80
|
+
"status": { "enum": ["applied", "already_applied"] }
|
|
81
|
+
},
|
|
82
|
+
"required": ["operation", "status"]
|
|
83
|
+
},
|
|
84
|
+
"then": { "required": ["tombstone_digest"] }
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"if": { "properties": { "rows_affected": { "minimum": 1 } } },
|
|
88
|
+
"then": { "properties": { "source_database_mutated": { "const": true } } }
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"$defs": {
|
|
92
|
+
"safeIdentifier": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
|
|
93
|
+
"sha256": { "type": "string", "pattern": "^sha256:.+" },
|
|
94
|
+
"code": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" },
|
|
95
|
+
"scalar": { "type": ["string", "number", "boolean", "null"] },
|
|
96
|
+
"columnValue": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"additionalProperties": false,
|
|
99
|
+
"required": ["column", "value"],
|
|
100
|
+
"properties": {
|
|
101
|
+
"column": { "$ref": "#/$defs/safeIdentifier" },
|
|
102
|
+
"value": { "$ref": "#/$defs/scalar" }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|