programgarden-core 2.7.0__tar.gz → 3.0.0__tar.gz
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.
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/PKG-INFO +2 -52
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/README.md +1 -51
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/bases/listener.py +1 -1
- programgarden_core-3.0.0/programgarden_core/code_node.py +29 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/pyproject.toml +1 -1
- programgarden_core-2.7.0/programgarden_core/code_node.py +0 -419
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/__init__.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/bases/__init__.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/bases/client.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/bases/components.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/bases/mixins.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/bases/products.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/bases/sql.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/bases/storage.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/exceptions.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/expression/__init__.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/expression/evaluator.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/expression/reference.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/i18n/__init__.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/i18n/locales/en.json +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/i18n/locales/ko.json +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/i18n/translator.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/korea_alias.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/market_calendar.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/__init__.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/connection_rule.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/credential.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/edge.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/event.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/exchange.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/field_binding.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/job.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/order_diagnostics.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/plugin_resource.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/resilience.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/resource.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/validation.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/workflow.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/__init__.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/account_futures.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/account_korea_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/account_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/ai.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_futures.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/base.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/broker.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/calculation.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/code.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/condition.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/contract_info_futures.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/data.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/data_futures.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/data_korea_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/data_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/display.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/event.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/fundamental_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/infra.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/open_orders_futures.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/open_orders_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/order.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/orderable_quantity_futures.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/portfolio.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/ranking_korea_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/ranking_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_futures.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_korea_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/risk.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/session_gate.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_futures.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_korea_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_stock.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/trigger.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/presets/__init__.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/presets/news_analyst.json +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/presets/risk_manager.json +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/presets/strategist.json +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/presets/technical_analyst.json +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/registry/__init__.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/registry/credential_registry.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/registry/node_registry.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/registry/plugin_registry.py +0 -0
- {programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/retry_executor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: programgarden-core
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의
|
|
5
5
|
License-Expression: AGPL-3.0-or-later
|
|
6
6
|
Author: 프로그램동산
|
|
@@ -15,24 +15,8 @@ Requires-Dist: pydantic (>=2.0.1,<3.0.0)
|
|
|
15
15
|
Requires-Dist: tzdata (>=2025.2,<2026.0)
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
|
-
The 2.6.2 release candidate [futures minute alias correction](../../docs/futures-minute-alias-contract.md) aligns canonical minute periods with the existing one-page o3103 path and rejects unsupported hourly/adjustment claims. Complete-range coverage and consumer adoption are not implied.
|
|
19
|
-
|
|
20
|
-
US regular-session requests can opt into SessionGateNode.exchange_calendar (XNYS/XNAS) for scheduled holidays and early closes. See [session gates](../../docs/session-gates-and-guarded-split.md).
|
|
21
|
-
|
|
22
18
|
# ProgramGarden Core
|
|
23
19
|
|
|
24
|
-
An unreleased [stock history interval correction](../../docs/stock-history-interval-contract.md)
|
|
25
|
-
limits the overseas-stock catalog to its implemented g3204 periods and rejects
|
|
26
|
-
unsupported intraday requests before execution. Package adoption is pending.
|
|
27
|
-
|
|
28
|
-
Unreleased schema additions: OverseasFuturesContractInfoNode, SQLiteNode's opt-in
|
|
29
|
-
storage_scope=execution, and SessionGateNode's XHKG calendar. See the
|
|
30
|
-
[runtime contract](../../docs/futures-contract-details-and-rollover-state.md).
|
|
31
|
-
|
|
32
|
-
The REST overseas-stock position catalog declares `sellable_qty`, matching the
|
|
33
|
-
broker-reported capacity preserved by the engine. Missing evidence remains null;
|
|
34
|
-
see [the contract](../../docs/overseas-stock-sellable-quantity.md).
|
|
35
|
-
|
|
36
20
|
ProgramGarden은 AI 시대에 맞춰 파이썬을 모르는 투자자도 개인화된 시스템 트레이딩을 자동으로 수행할 수 있게 돕는 오픈소스입니다. 본 저장소는 노드 기반 DSL의 핵심 타입, 베이스 클래스, 레지스트리, i18n을 정의하는 "코어" 모듈입니다.
|
|
37
21
|
|
|
38
22
|
- 문서(비개발자 빠른 시작): https://programgarden.gitbook.io/docs/invest/non_dev_quick_guide
|
|
@@ -119,44 +103,10 @@ programgarden_core/
|
|
|
119
103
|
|
|
120
104
|
자세한 변경 사항은 `CHANGELOG.md`를 참고하세요.
|
|
121
105
|
|
|
122
|
-
## Workflow PnL event contract
|
|
123
|
-
|
|
124
|
-
Futures workflow PnL events preserve native gross estimates separately from
|
|
125
|
-
accounting results. `workflow_*`, `other_*`, `total_*`, account and competition
|
|
126
|
-
monetary/rate scalars remain null when accounting evidence is unavailable.
|
|
127
|
-
`pnl_by_currency` contains gross price-change subtotals by contract currency;
|
|
128
|
-
`monetary_positions` retains their basis/status and unmodified, unconfirmed
|
|
129
|
-
`broker_pnl_amount`. No fee, FX, margin/equity return or verified contest score
|
|
130
|
-
is inferred from these estimates. Actual zero and negative amounts are retained.
|
|
131
|
-
`currency` is null for mixed or unavailable currencies. Consumers must handle
|
|
132
|
-
nullable monetary fields and must not coerce them to zero.
|
|
133
|
-
|
|
134
|
-
`WorkflowPnLEvent.personal_metrics` is an optional versioned envelope for retained
|
|
135
|
-
local workflow executions. Version 1 includes `scope` (kind, product, provider,
|
|
136
|
-
trading mode), timezone-aware `as_of`, `basis`, executed-order count/status,
|
|
137
|
-
per-symbol/exchange/currency realized amounts/status, and unavailable MDD.
|
|
138
|
-
It is unverified personal evidence, independent of whole-account contest results.
|
|
139
|
-
Stock realized amounts are stored long-only FIFO gross values excluding fees;
|
|
140
|
-
unknown currency stays null. Futures monetary values and portfolio MDD require
|
|
141
|
-
accounting evidence absent from this ledger and remain null. Consumers must
|
|
142
|
-
preserve null/status and use the latest cumulative observation, not sum dates.
|
|
143
|
-
|
|
144
106
|
## Workflow account boundary
|
|
145
107
|
|
|
146
108
|
Each workflow allows one account and one product through at most one broker
|
|
147
109
|
connection. Additional connections produce `DUPLICATE_BROKER_NODE`, including
|
|
148
110
|
unbound or cross-product nodes. Reuse one broker for all consumers. Account-free
|
|
149
|
-
workflows and unrelated credentials remain supported.
|
|
150
|
-
[the shared contract](../../docs/workflow_account_policy.md).
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
## Cancellation result contract (unreleased P4)
|
|
154
|
-
|
|
155
|
-
CancelOrder metadata describes request acknowledgement (`accepted`,
|
|
156
|
-
`confirmation_pending=true`), not final cancellation. Replacement orders require
|
|
157
|
-
separate matching completion evidence. See
|
|
158
|
-
[the cancellation contract](../../docs/cancellation-acknowledgements.md).
|
|
159
|
-
|
|
160
|
-
Session windows and IfNode routing inside Split are described in
|
|
161
|
-
[Session gates and guarded Split](../../docs/session-gates-and-guarded-split.md).
|
|
111
|
+
workflows and unrelated credentials remain supported.
|
|
162
112
|
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
The 2.6.2 release candidate [futures minute alias correction](../../docs/futures-minute-alias-contract.md) aligns canonical minute periods with the existing one-page o3103 path and rejects unsupported hourly/adjustment claims. Complete-range coverage and consumer adoption are not implied.
|
|
2
|
-
|
|
3
|
-
US regular-session requests can opt into SessionGateNode.exchange_calendar (XNYS/XNAS) for scheduled holidays and early closes. See [session gates](../../docs/session-gates-and-guarded-split.md).
|
|
4
|
-
|
|
5
1
|
# ProgramGarden Core
|
|
6
2
|
|
|
7
|
-
An unreleased [stock history interval correction](../../docs/stock-history-interval-contract.md)
|
|
8
|
-
limits the overseas-stock catalog to its implemented g3204 periods and rejects
|
|
9
|
-
unsupported intraday requests before execution. Package adoption is pending.
|
|
10
|
-
|
|
11
|
-
Unreleased schema additions: OverseasFuturesContractInfoNode, SQLiteNode's opt-in
|
|
12
|
-
storage_scope=execution, and SessionGateNode's XHKG calendar. See the
|
|
13
|
-
[runtime contract](../../docs/futures-contract-details-and-rollover-state.md).
|
|
14
|
-
|
|
15
|
-
The REST overseas-stock position catalog declares `sellable_qty`, matching the
|
|
16
|
-
broker-reported capacity preserved by the engine. Missing evidence remains null;
|
|
17
|
-
see [the contract](../../docs/overseas-stock-sellable-quantity.md).
|
|
18
|
-
|
|
19
3
|
ProgramGarden은 AI 시대에 맞춰 파이썬을 모르는 투자자도 개인화된 시스템 트레이딩을 자동으로 수행할 수 있게 돕는 오픈소스입니다. 본 저장소는 노드 기반 DSL의 핵심 타입, 베이스 클래스, 레지스트리, i18n을 정의하는 "코어" 모듈입니다.
|
|
20
4
|
|
|
21
5
|
- 문서(비개발자 빠른 시작): https://programgarden.gitbook.io/docs/invest/non_dev_quick_guide
|
|
@@ -102,43 +86,9 @@ programgarden_core/
|
|
|
102
86
|
|
|
103
87
|
자세한 변경 사항은 `CHANGELOG.md`를 참고하세요.
|
|
104
88
|
|
|
105
|
-
## Workflow PnL event contract
|
|
106
|
-
|
|
107
|
-
Futures workflow PnL events preserve native gross estimates separately from
|
|
108
|
-
accounting results. `workflow_*`, `other_*`, `total_*`, account and competition
|
|
109
|
-
monetary/rate scalars remain null when accounting evidence is unavailable.
|
|
110
|
-
`pnl_by_currency` contains gross price-change subtotals by contract currency;
|
|
111
|
-
`monetary_positions` retains their basis/status and unmodified, unconfirmed
|
|
112
|
-
`broker_pnl_amount`. No fee, FX, margin/equity return or verified contest score
|
|
113
|
-
is inferred from these estimates. Actual zero and negative amounts are retained.
|
|
114
|
-
`currency` is null for mixed or unavailable currencies. Consumers must handle
|
|
115
|
-
nullable monetary fields and must not coerce them to zero.
|
|
116
|
-
|
|
117
|
-
`WorkflowPnLEvent.personal_metrics` is an optional versioned envelope for retained
|
|
118
|
-
local workflow executions. Version 1 includes `scope` (kind, product, provider,
|
|
119
|
-
trading mode), timezone-aware `as_of`, `basis`, executed-order count/status,
|
|
120
|
-
per-symbol/exchange/currency realized amounts/status, and unavailable MDD.
|
|
121
|
-
It is unverified personal evidence, independent of whole-account contest results.
|
|
122
|
-
Stock realized amounts are stored long-only FIFO gross values excluding fees;
|
|
123
|
-
unknown currency stays null. Futures monetary values and portfolio MDD require
|
|
124
|
-
accounting evidence absent from this ledger and remain null. Consumers must
|
|
125
|
-
preserve null/status and use the latest cumulative observation, not sum dates.
|
|
126
|
-
|
|
127
89
|
## Workflow account boundary
|
|
128
90
|
|
|
129
91
|
Each workflow allows one account and one product through at most one broker
|
|
130
92
|
connection. Additional connections produce `DUPLICATE_BROKER_NODE`, including
|
|
131
93
|
unbound or cross-product nodes. Reuse one broker for all consumers. Account-free
|
|
132
|
-
workflows and unrelated credentials remain supported.
|
|
133
|
-
[the shared contract](../../docs/workflow_account_policy.md).
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
## Cancellation result contract (unreleased P4)
|
|
137
|
-
|
|
138
|
-
CancelOrder metadata describes request acknowledgement (`accepted`,
|
|
139
|
-
`confirmation_pending=true`), not final cancellation. Replacement orders require
|
|
140
|
-
separate matching completion evidence. See
|
|
141
|
-
[the cancellation contract](../../docs/cancellation-acknowledgements.md).
|
|
142
|
-
|
|
143
|
-
Session windows and IfNode routing inside Split are described in
|
|
144
|
-
[Session gates and guarded Split](../../docs/session-gates-and-guarded-split.md).
|
|
94
|
+
workflows and unrelated credentials remain supported.
|
|
@@ -346,7 +346,7 @@ class WorkflowPnLEvent:
|
|
|
346
346
|
# 워크플로우 **비율**을 못 낸 사유. 값을 비우는 것만으로는 "안 샀다" 와 "증권사가
|
|
347
347
|
# 평단·현재가를 안 보냈다" 가 구분되지 않는다 — 서버가 이 사유를 그대로 적재한다.
|
|
348
348
|
# 어휘: no_workflow_positions | basis_unreported | price_unreported | no_tracker |
|
|
349
|
-
# computation_failed (
|
|
349
|
+
# computation_failed (플랫폼의 수익률 미산출 사유 어휘와 공용).
|
|
350
350
|
# 🔴 monetary_* 를 재사용하지 않는다 — 그건 선물 전용 계약이고, 주식 이벤트에서
|
|
351
351
|
# monetary_status is None 임을 고정하는 테스트가 있다.
|
|
352
352
|
workflow_rate_unavailable_reason: Optional[str] = None
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Public stub of the CodeNode support module. The user-code screener and sandbox are
|
|
2
|
+
not part of the open-source build; CodeNode is declared so workflows parse and pass
|
|
3
|
+
structural validation, but execution refuses (see WorkflowExecutor)."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CodeScreenResult:
|
|
7
|
+
"""Structural-validation result. The open-source build performs no code screening."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.ok = True
|
|
11
|
+
self.error_code = None
|
|
12
|
+
self.message = None
|
|
13
|
+
self.suggestion = None
|
|
14
|
+
self.details = {}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def compile_code_node(code="", node_id="code", *args, **kwargs):
|
|
18
|
+
return CodeScreenResult()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
DEFAULT_ALLOWED_IMPORTS: frozenset = frozenset({
|
|
22
|
+
"math", "cmath", "statistics", "decimal", "fractions", "numbers",
|
|
23
|
+
"random", "json", "re", "datetime", "time", "calendar",
|
|
24
|
+
"collections", "itertools", "functools",
|
|
25
|
+
"string", "textwrap", "unicodedata",
|
|
26
|
+
"heapq", "bisect", "array", "copy",
|
|
27
|
+
"dataclasses", "enum", "typing",
|
|
28
|
+
"hashlib", "hmac", "base64", "binascii", "zlib",
|
|
29
|
+
})
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
homepage = "https://programgarden.com"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
name = "programgarden-core"
|
|
8
|
-
version = "
|
|
8
|
+
version = "3.0.0"
|
|
9
9
|
license = "AGPL-3.0-or-later"
|
|
10
10
|
description = "ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의"
|
|
11
11
|
readme = "README.md"
|
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
"""ProgramGarden Core — CodeNode static compilation & AST security screening.
|
|
2
|
-
|
|
3
|
-
This module is the single source of truth for the CodeNode security lists and
|
|
4
|
-
the compile/screen pipeline. It is intentionally dependency-free (stdlib only)
|
|
5
|
-
so it can be reused verbatim by:
|
|
6
|
-
|
|
7
|
-
1. `WorkflowResolver.validate()` — pre-flight structured errors before run.
|
|
8
|
-
2. `CodeNodeExecutor` / subprocess worker — screen again right before `exec`.
|
|
9
|
-
3. An external web-platform "share gate" server — re-run the same hard AST scan.
|
|
10
|
-
|
|
11
|
-
Security model (see plan §2): CodeNode runs untrusted Python. Defense is layered:
|
|
12
|
-
|
|
13
|
-
* Layer 1 — scrubbed context (runtime, programgarden): the code never receives
|
|
14
|
-
credential accessors.
|
|
15
|
-
* Layer 2 — restricted builtins + AST denylist (THIS module): a curated
|
|
16
|
-
`__builtins__` (no eval/exec/open/getattr/...), a safe `__import__` gated by a
|
|
17
|
-
whitelist, and a static AST scan that rejects dangerous imports, builtin
|
|
18
|
-
calls, and introspection-escape dunders BEFORE any code runs.
|
|
19
|
-
* Layer 3 — binding seal (runtime, resolver): credentials cannot flow into
|
|
20
|
-
`data`/`params`.
|
|
21
|
-
* Layer 4 — subprocess isolation (runtime, programgarden): the code always runs
|
|
22
|
-
in a credential-free child process.
|
|
23
|
-
|
|
24
|
-
`compile_code_node` NEVER executes the code — it only parses, screens, and
|
|
25
|
-
compiles. Execution (exec + calling `execute`) is the runtime's responsibility.
|
|
26
|
-
|
|
27
|
-
All user-facing strings are English (AI-chatbot consumer contract).
|
|
28
|
-
"""
|
|
29
|
-
from __future__ import annotations
|
|
30
|
-
|
|
31
|
-
import ast
|
|
32
|
-
import builtins as _builtins
|
|
33
|
-
from dataclasses import dataclass, field
|
|
34
|
-
from types import CodeType
|
|
35
|
-
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
36
|
-
|
|
37
|
-
# Name of the entry-point coroutine the code text must define.
|
|
38
|
-
EXECUTE_FN_NAME = "execute"
|
|
39
|
-
|
|
40
|
-
# ⚠️ SECURITY POSTURE — READ THIS.
|
|
41
|
-
# AST screening + restricted builtins CANNOT be a sound sandbox for CPython.
|
|
42
|
-
# A determined attacker who can run Python can, in the general case, escape any
|
|
43
|
-
# in-process denylist (this is a well-known result; see the pysandbox author's
|
|
44
|
-
# own withdrawal). An adversarial audit of an earlier version of this file
|
|
45
|
-
# reproduced RCE via `operator.attrgetter`, `random._os`, and string-obfuscated
|
|
46
|
-
# dunders. The rules below are HARDENED defense-in-depth that close every
|
|
47
|
-
# demonstrated vector and raise the bar substantially — but they are NOT a
|
|
48
|
-
# containment guarantee. TRUE containment of untrusted code is the runtime's
|
|
49
|
-
# job (subprocess with no in-memory keys) + the operator's job (per-user pod
|
|
50
|
-
# isolation, no-egress NetworkPolicy, restricted file perms, scrubbed env).
|
|
51
|
-
# Treat CodeNode as "run trusted code, defense-in-depth against mistakes and
|
|
52
|
-
# opportunistic abuse", not "safe to run arbitrary hostile code".
|
|
53
|
-
|
|
54
|
-
# ── Whitelist: modules importable from CodeNode code ────────────────────────
|
|
55
|
-
# Pure-computation stdlib only. `operator` is deliberately EXCLUDED — its
|
|
56
|
-
# attrgetter/methodcaller are getattr/dispatch substitutes. Modules that
|
|
57
|
-
# re-export os/sys as public attributes (e.g. random._os, uuid.os) are still
|
|
58
|
-
# covered by the attribute screen below, not by this set. The embedding app may
|
|
59
|
-
# EXTEND this via `allowed_imports=` (e.g. numpy) but the default is minimal.
|
|
60
|
-
DEFAULT_ALLOWED_IMPORTS: frozenset = frozenset({
|
|
61
|
-
"math", "cmath", "statistics", "decimal", "fractions", "numbers",
|
|
62
|
-
"random", "json", "re", "datetime", "time", "calendar",
|
|
63
|
-
"collections", "itertools", "functools",
|
|
64
|
-
"string", "textwrap", "unicodedata",
|
|
65
|
-
"heapq", "bisect", "array", "copy",
|
|
66
|
-
"dataclasses", "enum", "typing",
|
|
67
|
-
"hashlib", "hmac", "base64", "binascii", "zlib",
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
# ── Denylist: modules called out for a clearer error message ────────────────
|
|
71
|
-
# Enforcement is whitelist-based (not in DEFAULT_ALLOWED_IMPORTS → blocked);
|
|
72
|
-
# this set only makes the message explicit for well-known dangerous modules.
|
|
73
|
-
FORBIDDEN_MODULES: frozenset = frozenset({
|
|
74
|
-
"os", "sys", "subprocess", "socket", "shutil", "importlib", "imp",
|
|
75
|
-
"ctypes", "cffi", "gc", "inspect", "pathlib", "glob", "tempfile", "io",
|
|
76
|
-
"urllib", "http", "requests", "ftplib", "smtplib", "asyncio",
|
|
77
|
-
"multiprocessing", "threading", "concurrent", "_thread", "operator",
|
|
78
|
-
"builtins", "marshal", "pickle", "shelve", "dbm", "sqlite3",
|
|
79
|
-
"code", "codeop", "pty", "resource", "signal", "mmap", "fcntl",
|
|
80
|
-
"platform", "webbrowser", "ssl", "select", "selectors", "uuid",
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
# ── Denylist: builtin names that must never be called or aliased ────────────
|
|
84
|
-
FORBIDDEN_BUILTINS: frozenset = frozenset({
|
|
85
|
-
"eval", "exec", "compile", "open", "__import__",
|
|
86
|
-
"getattr", "setattr", "delattr",
|
|
87
|
-
"globals", "locals", "vars",
|
|
88
|
-
"input", "breakpoint", "help", "memoryview",
|
|
89
|
-
"exit", "quit", "__build_class__",
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
# ── Denylist: dangerous PUBLIC attribute names (module re-exports & escapes) ──
|
|
93
|
-
# Many stdlib modules re-export `os`/`sys` as public attributes (statistics.sys,
|
|
94
|
-
# uuid.os, ...) — walking those reaches os.system. `mro`/`modules`/`system`/etc.
|
|
95
|
-
# are the follow-on hops. Blocking these attribute NAMES (in addition to the
|
|
96
|
-
# blanket underscore rule below) severs the module-attribute escape path.
|
|
97
|
-
FORBIDDEN_ATTR_NAMES: frozenset = frozenset({
|
|
98
|
-
"os", "sys", "subprocess", "socket", "builtins", "importlib",
|
|
99
|
-
"modules", "system", "popen", "spawn", "spawnl", "spawnv", "spawnve",
|
|
100
|
-
"fork", "forkpty", "execv", "execve", "execl", "execlp", "execvp",
|
|
101
|
-
"mro", "import_module", "load_module", "reload", "getattr", "setattr",
|
|
102
|
-
"delattr", "globals", "environ", "getenv", "putenv", "connect",
|
|
103
|
-
"urlopen", "call", "run", "check_output", "Popen", "loader", "spec",
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
# ── Denylist: introspection-escape / exfiltration dunders & frame attrs ──────
|
|
107
|
-
# NOTE: enforcement is the underscore rule in _scan_ast — any ATTRIBUTE beginning
|
|
108
|
-
# with '_' is rejected (subsumes every dunder and private module re-export like
|
|
109
|
-
# random._os); STRING literals are rejected when they contain a dunder or name a
|
|
110
|
-
# blocked attribute re-export (bare single-underscore data-key strings such as
|
|
111
|
-
# "_partial_failure" are allowed). This explicit set is retained for message clarity.
|
|
112
|
-
FORBIDDEN_DUNDERS: frozenset = frozenset({
|
|
113
|
-
"__class__", "__bases__", "__base__", "__subclasses__", "__mro__",
|
|
114
|
-
"__globals__", "__code__", "__closure__", "__func__", "__self__",
|
|
115
|
-
"__dict__", "__getattribute__", "__setattr__", "__delattr__",
|
|
116
|
-
"__reduce__", "__reduce_ex__", "__builtins__", "__import__",
|
|
117
|
-
"__loader__", "__spec__", "__init_subclass__", "__subclasshook__",
|
|
118
|
-
"__getattr__", "__wrapped__", "__objclass__",
|
|
119
|
-
"gi_frame", "gi_code", "cr_frame", "cr_code", "ag_frame",
|
|
120
|
-
"f_back", "f_globals", "f_locals", "f_builtins", "tb_frame",
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
# Format replacement-field that performs attribute access, e.g. "{0.__class__}"
|
|
124
|
-
# or "{x.sys}" — used to walk attributes via str.format without an AST Attribute
|
|
125
|
-
# node. Rejected wholesale in string constants.
|
|
126
|
-
import re as _re
|
|
127
|
-
_FORMAT_ATTR_RE = _re.compile(r"\{[^{}]*\.[^{}]*\}")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@dataclass
|
|
131
|
-
class CodeScreenResult:
|
|
132
|
-
"""Structured outcome of `compile_code_node`. `ok=False` carries an
|
|
133
|
-
AI-chatbot-consumable error (code + message + suggestion + location)."""
|
|
134
|
-
|
|
135
|
-
ok: bool
|
|
136
|
-
code_object: Optional[CodeType] = None
|
|
137
|
-
error_code: Optional[str] = None # one of CODE_NODE_SYNTAX_ERROR / _FORBIDDEN / _NO_EXECUTE
|
|
138
|
-
message: Optional[str] = None
|
|
139
|
-
suggestion: Optional[str] = None
|
|
140
|
-
line: Optional[int] = None
|
|
141
|
-
offset: Optional[int] = None
|
|
142
|
-
details: Dict[str, Any] = field(default_factory=dict)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _module_root(name: Optional[str]) -> str:
|
|
146
|
-
return (name or "").split(".")[0]
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _scan_ast(tree: ast.AST, allowed_imports: Set[str]) -> List[Dict[str, Any]]:
|
|
150
|
-
"""Return a list of violation dicts (empty when clean).
|
|
151
|
-
|
|
152
|
-
Each violation: {kind, name, line, col, reason}. `kind` ∈
|
|
153
|
-
{import, builtin, dunder}.
|
|
154
|
-
"""
|
|
155
|
-
violations: List[Dict[str, Any]] = []
|
|
156
|
-
|
|
157
|
-
for node in ast.walk(tree):
|
|
158
|
-
# ── Imports (whitelist-enforced) ──
|
|
159
|
-
if isinstance(node, ast.Import):
|
|
160
|
-
for alias in node.names:
|
|
161
|
-
root = _module_root(alias.name)
|
|
162
|
-
if root not in allowed_imports:
|
|
163
|
-
reason = (
|
|
164
|
-
f"module '{alias.name}' is blocked (filesystem/process/network access)"
|
|
165
|
-
if root in FORBIDDEN_MODULES
|
|
166
|
-
else f"module '{alias.name}' is not in the allowed import list"
|
|
167
|
-
)
|
|
168
|
-
violations.append({
|
|
169
|
-
"kind": "import", "name": alias.name,
|
|
170
|
-
"line": node.lineno, "col": node.col_offset, "reason": reason,
|
|
171
|
-
})
|
|
172
|
-
elif isinstance(node, ast.ImportFrom):
|
|
173
|
-
root = _module_root(node.module)
|
|
174
|
-
# `from . import x` (level>0) is always blocked — no package context.
|
|
175
|
-
if node.level and node.level > 0:
|
|
176
|
-
violations.append({
|
|
177
|
-
"kind": "import", "name": node.module or ".",
|
|
178
|
-
"line": node.lineno, "col": node.col_offset,
|
|
179
|
-
"reason": "relative imports are not allowed in CodeNode",
|
|
180
|
-
})
|
|
181
|
-
elif root not in allowed_imports:
|
|
182
|
-
reason = (
|
|
183
|
-
f"module '{node.module}' is blocked (filesystem/process/network access)"
|
|
184
|
-
if root in FORBIDDEN_MODULES
|
|
185
|
-
else f"module '{node.module}' is not in the allowed import list"
|
|
186
|
-
)
|
|
187
|
-
violations.append({
|
|
188
|
-
"kind": "import", "name": node.module or "",
|
|
189
|
-
"line": node.lineno, "col": node.col_offset, "reason": reason,
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
# ── Forbidden builtin names (call or alias) ──
|
|
193
|
-
elif isinstance(node, ast.Name):
|
|
194
|
-
if node.id in FORBIDDEN_BUILTINS:
|
|
195
|
-
violations.append({
|
|
196
|
-
"kind": "builtin", "name": node.id,
|
|
197
|
-
"line": node.lineno, "col": node.col_offset,
|
|
198
|
-
"reason": f"use of '{node.id}' is not allowed",
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
# ── Attribute access screen ──
|
|
202
|
-
# (1) BLANKET: any attribute beginning with '_' is rejected — this
|
|
203
|
-
# subsumes every dunder (__class__/__globals__/__subclasses__/...)
|
|
204
|
-
# AND private module re-exports (random._os, random._urandom, ...).
|
|
205
|
-
# (2) A denylist of dangerous PUBLIC names (module re-exports & shell
|
|
206
|
-
# hops): statistics.sys, uuid.os, x.system, x.modules, type().mro().
|
|
207
|
-
elif isinstance(node, ast.Attribute):
|
|
208
|
-
if node.attr.startswith("_"):
|
|
209
|
-
violations.append({
|
|
210
|
-
"kind": "dunder", "name": node.attr,
|
|
211
|
-
"line": node.lineno, "col": node.col_offset,
|
|
212
|
-
"reason": f"access to underscore attribute '{node.attr}' is not allowed (introspection escape)",
|
|
213
|
-
})
|
|
214
|
-
elif node.attr in FORBIDDEN_ATTR_NAMES:
|
|
215
|
-
violations.append({
|
|
216
|
-
"kind": "attr", "name": node.attr,
|
|
217
|
-
"line": node.lineno, "col": node.col_offset,
|
|
218
|
-
"reason": f"attribute '{node.attr}' access is not allowed (module/escape re-export)",
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
# ── String-constant screen ──
|
|
222
|
-
# Defeats attribute-walk via strings: getattr/attrgetter args, str.format
|
|
223
|
-
# attribute fields, subscript-string dunder access. Reject strings that
|
|
224
|
-
# start with '_', contain a dunder, name a dangerous attribute, or embed
|
|
225
|
-
# a format field with attribute access.
|
|
226
|
-
elif isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
227
|
-
v = node.value
|
|
228
|
-
bad = None
|
|
229
|
-
# A single leading underscore names a documented native data marker
|
|
230
|
-
# read as a dict key (e.g. balance["_partial_failure"], "_source"),
|
|
231
|
-
# so it is allowed. Still rejected: dunder-style names, and blocked
|
|
232
|
-
# public/underscored attribute re-exports ("os"/"sys"/"system"/"_os"/
|
|
233
|
-
# "_sys"... -> in FORBIDDEN_ATTR_NAMES via lstrip("_")), and attribute-
|
|
234
|
-
# walking format fields. getattr/attrgetter/operator/format are
|
|
235
|
-
# independently blocked, so a bare underscore data-key string cannot
|
|
236
|
-
# be turned into attribute access.
|
|
237
|
-
if "__" in v:
|
|
238
|
-
bad = "string literal contains a dunder (introspection escape)"
|
|
239
|
-
elif v.lstrip("_") in FORBIDDEN_ATTR_NAMES:
|
|
240
|
-
bad = f"string literal '{v}' names a blocked attribute (introspection escape)"
|
|
241
|
-
elif _FORMAT_ATTR_RE.search(v):
|
|
242
|
-
bad = "format string performs attribute access (introspection escape)"
|
|
243
|
-
if bad:
|
|
244
|
-
violations.append({
|
|
245
|
-
"kind": "string", "name": v[:40],
|
|
246
|
-
"line": node.lineno, "col": node.col_offset,
|
|
247
|
-
"reason": bad,
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
return violations
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def _has_execute(tree: ast.Module) -> bool:
|
|
254
|
-
for node in tree.body:
|
|
255
|
-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == EXECUTE_FN_NAME:
|
|
256
|
-
return True
|
|
257
|
-
return False
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def compile_code_node(
|
|
261
|
-
code: str,
|
|
262
|
-
node_id: str,
|
|
263
|
-
*,
|
|
264
|
-
screen: bool = True,
|
|
265
|
-
allowed_imports: Optional[Set[str]] = None,
|
|
266
|
-
) -> CodeScreenResult:
|
|
267
|
-
"""Parse, security-screen, and compile CodeNode source WITHOUT executing it.
|
|
268
|
-
|
|
269
|
-
Pipeline:
|
|
270
|
-
1. `ast.parse` — SyntaxError → CODE_NODE_SYNTAX_ERROR (line/offset).
|
|
271
|
-
2. AST denylist scan (when `screen=True`) — CODE_NODE_FORBIDDEN.
|
|
272
|
-
3. `execute` function must be defined — CODE_NODE_NO_EXECUTE.
|
|
273
|
-
4. `compile(filename=f'<code:{node_id}>')` — preserves traceback lines.
|
|
274
|
-
|
|
275
|
-
Args:
|
|
276
|
-
code: Python source text (must define `execute`).
|
|
277
|
-
node_id: node id, used only for the compiled filename (traceback anchor).
|
|
278
|
-
screen: run the AST denylist scan (always True in production).
|
|
279
|
-
allowed_imports: override the import whitelist (defaults to
|
|
280
|
-
DEFAULT_ALLOWED_IMPORTS). Enforced both here and by the runtime
|
|
281
|
-
safe `__import__`.
|
|
282
|
-
|
|
283
|
-
Returns:
|
|
284
|
-
CodeScreenResult — `ok=True` with `code_object`, or `ok=False` with a
|
|
285
|
-
structured, English, chatbot-consumable error. Never raises for
|
|
286
|
-
screening/syntax failures (they are returned as data).
|
|
287
|
-
"""
|
|
288
|
-
allowed = set(allowed_imports) if allowed_imports is not None else set(DEFAULT_ALLOWED_IMPORTS)
|
|
289
|
-
|
|
290
|
-
if not isinstance(code, str) or not code.strip():
|
|
291
|
-
return CodeScreenResult(
|
|
292
|
-
ok=False,
|
|
293
|
-
error_code="CODE_NODE_SYNTAX_ERROR",
|
|
294
|
-
message="CodeNode 'code' is empty.",
|
|
295
|
-
suggestion="Provide Python source that defines `async def execute(data, params, context)`.",
|
|
296
|
-
line=1, offset=0,
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
# 1. Parse
|
|
300
|
-
try:
|
|
301
|
-
tree = ast.parse(code, filename=f"<code:{node_id}>", mode="exec")
|
|
302
|
-
except SyntaxError as e:
|
|
303
|
-
return CodeScreenResult(
|
|
304
|
-
ok=False,
|
|
305
|
-
error_code="CODE_NODE_SYNTAX_ERROR",
|
|
306
|
-
message=f"CodeNode syntax error: {e.msg}",
|
|
307
|
-
suggestion="Fix the Python syntax at the reported line/offset.",
|
|
308
|
-
line=e.lineno,
|
|
309
|
-
offset=e.offset,
|
|
310
|
-
details={"raw": str(e)},
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
# 2. Screen
|
|
314
|
-
if screen:
|
|
315
|
-
violations = _scan_ast(tree, allowed)
|
|
316
|
-
if violations:
|
|
317
|
-
first = violations[0]
|
|
318
|
-
names = sorted({v["name"] for v in violations})
|
|
319
|
-
return CodeScreenResult(
|
|
320
|
-
ok=False,
|
|
321
|
-
error_code="CODE_NODE_FORBIDDEN",
|
|
322
|
-
message=(
|
|
323
|
-
f"CodeNode uses forbidden construct(s): {', '.join(names)} "
|
|
324
|
-
f"({first['reason']})."
|
|
325
|
-
),
|
|
326
|
-
suggestion=(
|
|
327
|
-
"Remove the blocked import/builtin/introspection. CodeNode allows "
|
|
328
|
-
"pure-computation stdlib only (math, statistics, json, datetime, ...); "
|
|
329
|
-
"credentials, filesystem, network, subprocess, and object-graph "
|
|
330
|
-
"introspection are not available."
|
|
331
|
-
),
|
|
332
|
-
line=first["line"],
|
|
333
|
-
offset=first["col"],
|
|
334
|
-
details={"violations": violations, "forbidden_names": names},
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
# 3. execute() must exist
|
|
338
|
-
if not _has_execute(tree):
|
|
339
|
-
return CodeScreenResult(
|
|
340
|
-
ok=False,
|
|
341
|
-
error_code="CODE_NODE_NO_EXECUTE",
|
|
342
|
-
message=f"CodeNode code must define a function named '{EXECUTE_FN_NAME}'.",
|
|
343
|
-
suggestion=(
|
|
344
|
-
"Define `async def execute(data, params, context):` (a plain "
|
|
345
|
-
"`def execute(...)` is also accepted and auto-wrapped)."
|
|
346
|
-
),
|
|
347
|
-
line=1, offset=0,
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
# 4. Compile (never exec here)
|
|
351
|
-
try:
|
|
352
|
-
code_object = compile(tree, filename=f"<code:{node_id}>", mode="exec")
|
|
353
|
-
except (SyntaxError, ValueError) as e: # e.g. too-deeply-nested
|
|
354
|
-
return CodeScreenResult(
|
|
355
|
-
ok=False,
|
|
356
|
-
error_code="CODE_NODE_SYNTAX_ERROR",
|
|
357
|
-
message=f"CodeNode failed to compile: {e}",
|
|
358
|
-
suggestion="Simplify the code so it compiles as a module.",
|
|
359
|
-
line=getattr(e, "lineno", 1) or 1,
|
|
360
|
-
offset=getattr(e, "offset", 0) or 0,
|
|
361
|
-
details={"raw": str(e)},
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
return CodeScreenResult(ok=True, code_object=code_object)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
# The real import machinery, captured once so the sandbox can delegate to it
|
|
368
|
-
# after the whitelist check.
|
|
369
|
-
_REAL_IMPORT = _builtins.__import__
|
|
370
|
-
|
|
371
|
-
# Curated safe builtins — everything a pure-compute function needs, and nothing
|
|
372
|
-
# that grants escape (no eval/exec/compile/open/getattr/setattr/globals/...).
|
|
373
|
-
_SAFE_BUILTIN_NAMES: Tuple[str, ...] = (
|
|
374
|
-
"abs", "all", "any", "ascii", "bin", "bool", "bytearray", "bytes",
|
|
375
|
-
"callable", "chr", "complex", "dict", "divmod", "enumerate", "filter",
|
|
376
|
-
"float", "format", "frozenset", "hash", "hex", "int", "isinstance",
|
|
377
|
-
"issubclass", "iter", "len", "list", "map", "max", "min", "next", "oct",
|
|
378
|
-
"ord", "pow", "print", "range", "repr", "reversed", "round", "set",
|
|
379
|
-
"slice", "sorted", "str", "sum", "tuple", "type", "zip", "hasattr",
|
|
380
|
-
# exceptions & sentinels
|
|
381
|
-
"Exception", "BaseException", "ValueError", "TypeError", "KeyError",
|
|
382
|
-
"IndexError", "AttributeError", "ZeroDivisionError", "ArithmeticError",
|
|
383
|
-
"OverflowError", "RuntimeError", "StopIteration", "StopAsyncIteration",
|
|
384
|
-
"NotImplementedError", "AssertionError", "LookupError", "NameError",
|
|
385
|
-
"RecursionError", "FloatingPointError", "GeneratorExit", "KeyboardInterrupt",
|
|
386
|
-
"True", "False", "None", "NotImplemented", "Ellipsis",
|
|
387
|
-
"object", "property", "staticmethod", "classmethod", "super",
|
|
388
|
-
# __build_class__ is required for `class` statements in user code; it is not
|
|
389
|
-
# itself an escape vector (dunder screen blocks the dangerous follow-ups).
|
|
390
|
-
"__build_class__",
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
def build_restricted_builtins(allowed_imports: Optional[Set[str]] = None) -> Dict[str, Any]:
|
|
395
|
-
"""Build the `__builtins__` dict used when exec-ing CodeNode source.
|
|
396
|
-
|
|
397
|
-
Contains only the curated safe builtins plus a whitelist-gated
|
|
398
|
-
`__import__`. Pure function (no exec, stdlib only) so it lives in core and
|
|
399
|
-
is reused by the runtime worker.
|
|
400
|
-
"""
|
|
401
|
-
allowed = set(allowed_imports) if allowed_imports is not None else set(DEFAULT_ALLOWED_IMPORTS)
|
|
402
|
-
|
|
403
|
-
def _safe_import(name, globals=None, locals=None, fromlist=(), level=0):
|
|
404
|
-
if level and level != 0:
|
|
405
|
-
raise ImportError("relative imports are not allowed in CodeNode")
|
|
406
|
-
root = _module_root(name)
|
|
407
|
-
if root not in allowed:
|
|
408
|
-
raise ImportError(
|
|
409
|
-
f"import of '{name}' is not allowed in CodeNode "
|
|
410
|
-
f"(allowed: pure-computation stdlib only)"
|
|
411
|
-
)
|
|
412
|
-
return _REAL_IMPORT(name, globals, locals, fromlist, level)
|
|
413
|
-
|
|
414
|
-
safe: Dict[str, Any] = {}
|
|
415
|
-
for bn in _SAFE_BUILTIN_NAMES:
|
|
416
|
-
if hasattr(_builtins, bn):
|
|
417
|
-
safe[bn] = getattr(_builtins, bn)
|
|
418
|
-
safe["__import__"] = _safe_import
|
|
419
|
-
return safe
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/bases/components.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/expression/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/expression/evaluator.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/expression/reference.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/i18n/locales/en.json
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/i18n/locales/ko.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/connection_rule.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/credential.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/field_binding.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/order_diagnostics.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/plugin_resource.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/resilience.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/models/validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/account_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/account_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/backtest_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/calculation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/data_futures.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/data_korea_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/data_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/fundamental_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/open_orders_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/ranking_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/realtime_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/session_gate.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_futures.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_korea_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/nodes/symbol_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/presets/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/presets/news_analyst.json
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/presets/risk_manager.json
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/presets/strategist.json
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/registry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/registry/node_registry.py
RENAMED
|
File without changes
|
{programgarden_core-2.7.0 → programgarden_core-3.0.0}/programgarden_core/registry/plugin_registry.py
RENAMED
|
File without changes
|
|
File without changes
|