substratum-cli 0.2.1__tar.gz → 0.2.3__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.
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/PKG-INFO +1 -1
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/__init__.py +1 -1
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/_boot.py +18 -12
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/_update.py +20 -6
- substratum_cli-0.2.3/substratum_cli/_vendor/client_gate.py +166 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/engine.py +8 -1
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/product_ops.py +87 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/remote.py +3 -1
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/session.py +24 -7
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli.egg-info/PKG-INFO +1 -1
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli.egg-info/SOURCES.txt +1 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/README.md +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/pyproject.toml +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/setup.cfg +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/README.md +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/__main__.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/_vendor/__init__.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/_vendor/_pro_test_derive.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/_wordmark.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/account.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/build_state.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/cli.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/codes.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/__init__.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/apply.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/auth.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/commit.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/explain.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/failing.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/fix.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/history.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/init_cmd.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/pr.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/replay.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/scaffold.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/show.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/undo.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/verify.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/commands/write.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/config.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/diffs.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/gitio.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/langscan.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/refusals.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/render.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/result.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/run_store.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/runid.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli/style.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli.egg-info/dependency_links.txt +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli.egg-info/entry_points.txt +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli.egg-info/requires.txt +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/substratum_cli.egg-info/top_level.txt +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_adaptive_prior.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_alias_warrant_trace.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_bidirectional_gate_contracts.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_coherence_evolution.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_coherences.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_connection_web_api.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_cross_entity_scoping.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_data.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_de28_negation_refuse.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_definite_description_handler.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_demo_regression_catalog.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_demo_response_shape.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_e1_e2_field_alignment.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_extraction_class_write_time.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_falsification_predictions.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_gap_d_wh_normalization.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_inference.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_inflection_cvc_rule.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_inflection_irregular.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_intervention.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_invariant.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_legal_citation_handler.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_lens_noop_seam.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_markup_tag_handler.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_nl_parser.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_nl_parser_adversarial.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_noise.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_pass2_contextual.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_perspectives.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_polysemous_token_alias_guard.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_possessive_normalization.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_predicate_canon.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_projection_gate.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_projection_gate_tag_question.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_query_normalizer.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_records_answer_format.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_referent_aboutness_gate.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_referent_quality_gate.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_refuse_message_topic_neutrality.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_relation_detection.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_relaxation.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_residual_content_gate.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_reverse_lookup_subject_filter.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_sec_statement_aliases.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_span_lens.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_span_lens_query_integration.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_substrate.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_synonymy_detector.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_table_ingest.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_triple_ingest.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_two_phase_schema.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_user_asserted_aliases.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_v2_api.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_v2_api_claims.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_v2_claim_determinism.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_word_role.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_word_role_wiring.py +0 -0
- {substratum_cli-0.2.1 → substratum_cli-0.2.3}/tests/test_xbrl_doc_aliases.py +0 -0
|
@@ -5,4 +5,4 @@ Import budget: this package is stdlib-only; the ONLY module reaching into backen
|
|
|
5
5
|
"""
|
|
6
6
|
# SINGLE SOURCE OF TRUTH for the release version. pyproject reads this (dynamic version), the boot shows
|
|
7
7
|
# it, and the update-check compares against it. To cut a release, bump ONLY this line, then publish.
|
|
8
|
-
__version__ = "0.2.
|
|
8
|
+
__version__ = "0.2.3"
|
|
@@ -13,9 +13,11 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import hashlib
|
|
15
15
|
|
|
16
|
-
# The PUBLIC
|
|
17
|
-
#
|
|
18
|
-
#
|
|
16
|
+
# The PUBLIC name of the first intelligence library -- a model version, like GPT-5.2 or Opus 4.8. Shown
|
|
17
|
+
# ONLY when this library actually backs the session: in AIRGAPPED-LOCAL mode (the engine + library shipped
|
|
18
|
+
# on-prem, present on this machine), or reported by the HOSTED engine once the full corpus is live on it.
|
|
19
|
+
# The boot NEVER shows this from a hardcoded local path -- the label must be a real representation of the
|
|
20
|
+
# intelligence serving the user. The internal mining tag (pattern_library_scaled_v15) stays internal.
|
|
19
21
|
RELEASE = "Substrate 1.0 Preview"
|
|
20
22
|
|
|
21
23
|
L = "\033[38;5;154m" # brand lime
|
|
@@ -53,18 +55,21 @@ def seal(lib_name: str, version: str) -> tuple[str, str]:
|
|
|
53
55
|
return sigil, digest
|
|
54
56
|
|
|
55
57
|
|
|
56
|
-
def render(name, census, langs,
|
|
57
|
-
color=True, ascii_mode=False, logged_in=True, update_to="") -> list[str]:
|
|
58
|
-
"""Return the boot lines. `
|
|
58
|
+
def render(name, census, langs, substrate, substrate_note, substrate_ok, standing, version,
|
|
59
|
+
color=True, ascii_mode=False, logged_in=True, update_to="", seal_key="") -> list[str]:
|
|
60
|
+
"""Return the boot lines. `substrate` is the HONEST name of the intelligence serving this session
|
|
61
|
+
(the hosted engine version, or 'not connected') -- NEVER a local path. `substrate_note`/`_ok` render
|
|
62
|
+
the connection state. `seal_key` is the stable identity the self-certificate hashes."""
|
|
59
63
|
def c(code, s):
|
|
60
64
|
return f"{code}{s}{R}" if color else s
|
|
61
65
|
|
|
62
|
-
sig, dig = seal(
|
|
66
|
+
sig, dig = seal(seal_key or substrate, version)
|
|
63
67
|
if ascii_mode:
|
|
64
68
|
sig = "#" + dig.split()[0]
|
|
65
69
|
dot = "-" if ascii_mode else "·" # separators degrade with the glyph set
|
|
66
70
|
if ascii_mode: # the renderer guarantees ascii output regardless of input
|
|
67
|
-
name, census, langs,
|
|
71
|
+
name, census, langs, substrate, substrate_note = (
|
|
72
|
+
str(s).replace("·", "-") for s in (name, census, langs, substrate, substrate_note))
|
|
68
73
|
W = 78 # certificate inner width
|
|
69
74
|
out = ["", ""]
|
|
70
75
|
|
|
@@ -99,8 +104,9 @@ def render(name, census, langs, libname, patterns, warm, standing, version,
|
|
|
99
104
|
pad = " " * max(0, W - 1 - _vis(inner_plain))
|
|
100
105
|
return c(CH, v_) + " " + inner_colored + pad + c(CH, v_)
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
glyph = ("*" if ascii_mode else "●") if substrate_ok else dot
|
|
108
|
+
lamp = f"{glyph} {substrate_note}"
|
|
109
|
+
lampc = c(GR if substrate_ok else CH, lamp)
|
|
104
110
|
st = standing or {}
|
|
105
111
|
runs, ver = st.get("runs", 0), st.get("verified", 0)
|
|
106
112
|
unsaved = st.get("unapplied_total", 0)
|
|
@@ -114,8 +120,8 @@ def render(name, census, langs, libname, patterns, warm, standing, version,
|
|
|
114
120
|
box.append(c(CH, v_) + " " + seal_inner_c + pad + right_c + c(CH, v_))
|
|
115
121
|
|
|
116
122
|
box.append(bar(label="SUBSTRATE"))
|
|
117
|
-
box.append(rowline(f"
|
|
118
|
-
c(LAB, "
|
|
123
|
+
box.append(rowline(f" engine {substrate} {lamp}",
|
|
124
|
+
c(LAB, " engine ") + c(DAT, substrate) + " " + lampc))
|
|
119
125
|
box.append(rowline(f" covers {langs}", c(LAB, " covers ") + c(DAT, langs)))
|
|
120
126
|
box.append(bar(label="REPO"))
|
|
121
127
|
box.append(rowline(f" project {name}", c(LAB, " project ") + c(DAT, name)))
|
|
@@ -27,15 +27,25 @@ def _version_tuple(s: str) -> tuple:
|
|
|
27
27
|
return tuple(out)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def
|
|
31
|
-
"""The newer published version if the cache knows of one, else "". Pure, instant, no network."""
|
|
30
|
+
def _cache() -> dict:
|
|
32
31
|
try:
|
|
33
|
-
|
|
32
|
+
return json.load(open(_cache_path(), encoding="utf-8")) or {}
|
|
34
33
|
except Exception:
|
|
35
|
-
return
|
|
34
|
+
return {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def available(current: str) -> str:
|
|
38
|
+
"""The newer published version if the cache knows of one, else "". Pure, instant, no network."""
|
|
39
|
+
latest = str(_cache().get("latest") or "")
|
|
36
40
|
return latest if latest and _version_tuple(latest) > _version_tuple(current) else ""
|
|
37
41
|
|
|
38
42
|
|
|
43
|
+
def engine_version() -> str:
|
|
44
|
+
"""The HOSTED engine's substrate version from the last check (cached), or "". This is what actually
|
|
45
|
+
serves fix/write for a thin client, so the boot shows THIS -- never a local path."""
|
|
46
|
+
return str(_cache().get("engine") or "")
|
|
47
|
+
|
|
48
|
+
|
|
39
49
|
def refresh_async() -> None:
|
|
40
50
|
"""Fire-and-forget: ask the server for the latest published client and cache it for next session.
|
|
41
51
|
The server sources this from PyPI, so publishing is all it takes -- no redeploy, no constant to bump."""
|
|
@@ -44,12 +54,16 @@ def refresh_async() -> None:
|
|
|
44
54
|
from . import remote
|
|
45
55
|
info = remote.server_info() or {}
|
|
46
56
|
latest = str(info.get("latest_client") or "")
|
|
47
|
-
|
|
57
|
+
engine = str(info.get("engine_version") or "")
|
|
58
|
+
if not (latest or engine):
|
|
48
59
|
return
|
|
49
60
|
path = _cache_path()
|
|
50
61
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
62
|
+
cur = _cache()
|
|
63
|
+
cur.update({"latest": latest or cur.get("latest", ""),
|
|
64
|
+
"engine": engine or cur.get("engine", ""), "t": int(time.time())})
|
|
51
65
|
with open(path, "w", encoding="utf-8") as fh:
|
|
52
|
-
json.dump(
|
|
66
|
+
json.dump(cur, fh)
|
|
53
67
|
except Exception:
|
|
54
68
|
pass
|
|
55
69
|
try:
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""client_gate -- the Docker-less INSTANCE-GRAIN gate for client repos.
|
|
2
|
+
|
|
3
|
+
The commercial unlock: the verified-or-refuse machine currently gates instance-grain
|
|
4
|
+
emissions through benchmark Docker images. A client repo has no image -- it has ITS OWN
|
|
5
|
+
tests. This gate runs them, with DIFFERENTIAL (fail_to_pass) semantics:
|
|
6
|
+
|
|
7
|
+
the named tests must FAIL on the UNPATCHED base and PASS with the candidate applied.
|
|
8
|
+
|
|
9
|
+
The differential is load-bearing for zero-wrong: a candidate that "passes" tests which
|
|
10
|
+
already passed at base has proven NOTHING about the fix (pass-because-it-tests-nothing);
|
|
11
|
+
it is refused NAMED (GATE_TESTS_PASS_AT_BASE), never shipped on that warrant.
|
|
12
|
+
|
|
13
|
+
Isolation: base_commit is materialized in a TEMP GIT WORKTREE (the proven toolchain_derive
|
|
14
|
+
pattern); the client checkout is never touched; the worktree is always removed.
|
|
15
|
+
|
|
16
|
+
Outcomes are a CLOSED set and only PASS ships:
|
|
17
|
+
PASS differential held (failed at base, passed with patch)
|
|
18
|
+
TEST_FAILED patch applied, tests still fail
|
|
19
|
+
GATE_TESTS_PASS_AT_BASE nothing to fix on this warrant -> refuse
|
|
20
|
+
GATE_BASE_ERROR tests could not RUN at base (env/collection) -> refuse
|
|
21
|
+
GATE_ERROR tests could not RUN with the patch -> refuse (never a pass)
|
|
22
|
+
GATE_TIMEOUT / GATE_WORKTREE_FAILED / GATE_NO_RUNNER named infrastructure refusals
|
|
23
|
+
|
|
24
|
+
Runner resolution (closed, explicit-first): caller-provided command > detected by repo
|
|
25
|
+
shape (pytest for python repos, `go test` for go.mod repos, npm test for package.json with
|
|
26
|
+
a test script). Detection failure refuses NAMED -- the gate never guesses how to test."""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import os
|
|
30
|
+
import shlex
|
|
31
|
+
import shutil
|
|
32
|
+
import subprocess
|
|
33
|
+
import tempfile
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
from typing import Dict, List, Optional, Sequence, Tuple
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class ClientGateResult:
|
|
40
|
+
passed: bool
|
|
41
|
+
code: str # closed set above
|
|
42
|
+
detail: str = ""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _run(cmd: Sequence[str], cwd: str, timeout: int) -> Tuple[Optional[int], str]:
|
|
46
|
+
try:
|
|
47
|
+
# children get a REAL-fs tmpdir: pytest's capture holds unlinked-open tempfiles,
|
|
48
|
+
# which drvfs/9p mounts cannot represent (FileNotFoundError at truncate).
|
|
49
|
+
# PYTHONDONTWRITEBYTECODE is a CORRECTNESS requirement, not hygiene: the base run
|
|
50
|
+
# writes __pycache__; a patched source of the same size within the same second is
|
|
51
|
+
# SHADOWED by the stale pyc (timestamp validation is second-granular), and the
|
|
52
|
+
# with-patch run silently re-imports the buggy module -- the differential then
|
|
53
|
+
# reports TEST_FAILED for a genuine fix (or worse directions on other shapes).
|
|
54
|
+
r = subprocess.run(list(cmd), cwd=cwd, capture_output=True, text=True,
|
|
55
|
+
timeout=timeout,
|
|
56
|
+
env=dict(os.environ, PYTHONHASHSEED="0", TMPDIR="/tmp",
|
|
57
|
+
PYTHONDONTWRITEBYTECODE="1"))
|
|
58
|
+
return r.returncode, (r.stdout + "\n" + r.stderr)[-4000:]
|
|
59
|
+
except subprocess.TimeoutExpired:
|
|
60
|
+
return None, "timeout"
|
|
61
|
+
except FileNotFoundError as e:
|
|
62
|
+
return None, f"runner missing: {e}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def resolve_test_command(repo_dir: str, test_cmd: Optional[str],
|
|
66
|
+
test_paths: Optional[Sequence[str]]) -> Optional[List[str]]:
|
|
67
|
+
"""The command that runs the WARRANT tests. Explicit beats detection; detection is a
|
|
68
|
+
closed table keyed by repo shape; anything else -> None (refuse, never guess)."""
|
|
69
|
+
test_cmd = test_cmd or os.environ.get("SUBSTRATUM_CLIENT_TEST_CMD")
|
|
70
|
+
if test_cmd:
|
|
71
|
+
return shlex.split(test_cmd)
|
|
72
|
+
paths = list(test_paths or [])
|
|
73
|
+
if os.path.exists(os.path.join(repo_dir, "go.mod")):
|
|
74
|
+
go = os.path.expanduser("~/.local/go/bin/go")
|
|
75
|
+
go = go if os.path.exists(go) else shutil.which("go")
|
|
76
|
+
if not go:
|
|
77
|
+
return None
|
|
78
|
+
# go test takes package dirs + -run for test-name selectors (pkg::Test form)
|
|
79
|
+
pkgs, runs = [], []
|
|
80
|
+
for p in paths:
|
|
81
|
+
if "::" in p:
|
|
82
|
+
d, name = p.split("::", 1)
|
|
83
|
+
pkgs.append("./" + os.path.dirname(d) if os.path.dirname(d) else "./...")
|
|
84
|
+
runs.append(name)
|
|
85
|
+
else:
|
|
86
|
+
pkgs.append("./" + p if not p.startswith("./") else p)
|
|
87
|
+
cmd = [go, "test"] + (sorted(set(pkgs)) or ["./..."])
|
|
88
|
+
if runs:
|
|
89
|
+
cmd += ["-run", "|".join(dict.fromkeys(runs))]
|
|
90
|
+
return cmd
|
|
91
|
+
if any(os.path.exists(os.path.join(repo_dir, f))
|
|
92
|
+
for f in ("pytest.ini", "setup.py", "pyproject.toml", "setup.cfg", "tox.ini")) \
|
|
93
|
+
or os.path.isdir(os.path.join(repo_dir, "tests")) or any(
|
|
94
|
+
p.endswith(".py") or "::" in p for p in paths):
|
|
95
|
+
import sys
|
|
96
|
+
return [sys.executable, "-m", "pytest", "-x", "-q", "-p", "no:cacheprovider",
|
|
97
|
+
*paths]
|
|
98
|
+
pkg = os.path.join(repo_dir, "package.json")
|
|
99
|
+
if os.path.exists(pkg):
|
|
100
|
+
try:
|
|
101
|
+
import json
|
|
102
|
+
if json.load(open(pkg)).get("scripts", {}).get("test"):
|
|
103
|
+
return ["npm", "test", "--silent"]
|
|
104
|
+
except Exception:
|
|
105
|
+
return None
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def run_client_gate(repo_dir: str, base_commit: str, edits: Dict[str, str],
|
|
110
|
+
test_paths: Optional[Sequence[str]] = None,
|
|
111
|
+
test_cmd: Optional[str] = None,
|
|
112
|
+
differential: bool = True,
|
|
113
|
+
timeout: int = 600,
|
|
114
|
+
transcript_sink: Optional[dict] = None) -> ClientGateResult:
|
|
115
|
+
"""THE gate. Materialize base_commit in a temp worktree; (differential) run the warrant
|
|
116
|
+
tests unpatched -- they must FAIL; write `edits`; run again -- they must PASS.
|
|
117
|
+
|
|
118
|
+
transcript_sink (out-param, additive; the proven refusal_sink precedent): when a dict is
|
|
119
|
+
passed, it is populated with {runner_command, base_rc, base_out, patched_rc, patched_out}
|
|
120
|
+
so the product surface can show the "base FAIL -> patched PASS" proof and store the gate
|
|
121
|
+
logs. Default None keeps every existing caller byte-identical."""
|
|
122
|
+
wt = tempfile.mkdtemp(prefix="cg_wt_", dir=os.environ.get("TMPDIR") or None)
|
|
123
|
+
try:
|
|
124
|
+
r = subprocess.run(["git", "-C", repo_dir, "worktree", "add", "--detach", "-f",
|
|
125
|
+
wt, base_commit], capture_output=True, text=True, timeout=120)
|
|
126
|
+
if r.returncode != 0:
|
|
127
|
+
return ClientGateResult(False, "GATE_WORKTREE_FAILED", r.stderr[-300:])
|
|
128
|
+
cmd = resolve_test_command(wt, test_cmd, test_paths)
|
|
129
|
+
if cmd is None:
|
|
130
|
+
return ClientGateResult(False, "GATE_NO_RUNNER")
|
|
131
|
+
if transcript_sink is not None:
|
|
132
|
+
transcript_sink["runner_command"] = list(cmd)
|
|
133
|
+
if differential:
|
|
134
|
+
rc, out = _run(cmd, wt, timeout)
|
|
135
|
+
if transcript_sink is not None:
|
|
136
|
+
transcript_sink["base_rc"] = rc
|
|
137
|
+
transcript_sink["base_out"] = out
|
|
138
|
+
if rc is None:
|
|
139
|
+
return ClientGateResult(False, "GATE_TIMEOUT" if out == "timeout"
|
|
140
|
+
else "GATE_BASE_ERROR", out[-300:])
|
|
141
|
+
if rc == 0:
|
|
142
|
+
return ClientGateResult(False, "GATE_TESTS_PASS_AT_BASE")
|
|
143
|
+
# rc != 0 at base: the warrant tests fail before the fix, as fail_to_pass
|
|
144
|
+
# requires. (A collection error also exits non-zero; the with-patch run below
|
|
145
|
+
# is the arbiter -- a real fix makes the SAME invocation exit 0.)
|
|
146
|
+
for rel, content in edits.items():
|
|
147
|
+
p = os.path.join(wt, rel)
|
|
148
|
+
os.makedirs(os.path.dirname(p) or wt, exist_ok=True)
|
|
149
|
+
with open(p, "w", encoding="utf-8") as fh:
|
|
150
|
+
fh.write(content)
|
|
151
|
+
rc, out = _run(cmd, wt, timeout)
|
|
152
|
+
if transcript_sink is not None:
|
|
153
|
+
transcript_sink["patched_rc"] = rc
|
|
154
|
+
transcript_sink["patched_out"] = out
|
|
155
|
+
if rc is None:
|
|
156
|
+
return ClientGateResult(False, "GATE_TIMEOUT" if out == "timeout"
|
|
157
|
+
else "GATE_ERROR", out[-300:])
|
|
158
|
+
if rc == 0:
|
|
159
|
+
return ClientGateResult(True, "PASS")
|
|
160
|
+
return ClientGateResult(False, "TEST_FAILED", out[-400:])
|
|
161
|
+
except subprocess.TimeoutExpired:
|
|
162
|
+
return ClientGateResult(False, "GATE_WORKTREE_FAILED", "worktree add timeout")
|
|
163
|
+
finally:
|
|
164
|
+
subprocess.run(["git", "-C", repo_dir, "worktree", "remove", "-f", wt],
|
|
165
|
+
capture_output=True)
|
|
166
|
+
shutil.rmtree(wt, ignore_errors=True)
|
|
@@ -26,10 +26,17 @@ _LEAF_CACHE: dict[str, ModuleType] = {}
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def _leaf(name: str) -> ModuleType:
|
|
29
|
-
"""Load `<engine>/<name>.py` in isolation (no backend __init__), cached.
|
|
29
|
+
"""Load `<engine>/<name>.py` in isolation (no backend __init__), cached. On a THIN CLIENT
|
|
30
|
+
(pip install; no engine checkout on disk) fall back to the copy vendored INSIDE the wheel --
|
|
31
|
+
a pip user must never depend on a path that only exists on a dev machine. Dev machines still
|
|
32
|
+
get the live engine file first, so engine edits are picked up without a re-vendor."""
|
|
30
33
|
if name in _LEAF_CACHE:
|
|
31
34
|
return _LEAF_CACHE[name]
|
|
32
35
|
path = os.path.join(_ENGINE_DIR, f"{name}.py")
|
|
36
|
+
if not os.path.isfile(path):
|
|
37
|
+
vendored = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_vendor", f"{name}.py")
|
|
38
|
+
if os.path.isfile(vendored):
|
|
39
|
+
path = vendored
|
|
33
40
|
spec = importlib.util.spec_from_file_location(f"_substratum_leaf_{name}", path)
|
|
34
41
|
if spec is None or spec.loader is None:
|
|
35
42
|
raise ImportError(f"cannot leaf-load {path}")
|
|
@@ -41,6 +41,15 @@ def op_fix_from_failing_test(repo: str, failing_tests: list[str], intent: str =
|
|
|
41
41
|
stage("GROUND", f"reading {len(failing_tests)} failing test(s)")
|
|
42
42
|
|
|
43
43
|
if not engine.available(): # client-only install: fix needs the engine (local or hosted)
|
|
44
|
+
# HOSTED: when logged in, the server SYNTHESIZES from focal snippets (only the focal module +
|
|
45
|
+
# test NAMES transit; test bodies and the wider repo never leave) and the candidate diff is
|
|
46
|
+
# gated HERE, differentially, on the user's own tests. The gate stays the arbiter, client-side.
|
|
47
|
+
from . import remote
|
|
48
|
+
if remote.enabled():
|
|
49
|
+
rr = _remote_fix(repo, base, rid, key, inputs, failing_tests, intent,
|
|
50
|
+
test_command, target, stage)
|
|
51
|
+
if rr is not None:
|
|
52
|
+
return rr
|
|
44
53
|
ref = refusals.build(codes.NO_ENGINE, engine_code="NO_ENGINE",
|
|
45
54
|
ctx={"target": target or "the failing test", "run_id": rid})
|
|
46
55
|
return _store(repo, _result.make(codes.REFUSED, rid, "fix", code=codes.NO_ENGINE, refusal=ref),
|
|
@@ -155,6 +164,84 @@ def _route_test_derived(repo, base_commit, failing_tests, intent, test_command,
|
|
|
155
164
|
return None
|
|
156
165
|
|
|
157
166
|
|
|
167
|
+
def _focal_context(repo: str, failing_tests: list, intent: str):
|
|
168
|
+
"""Build the focal payload for the hosted engine: parse the failing TEST locally (it never
|
|
169
|
+
transits), find the module it imports-and-calls, and send ONLY that module's content + names.
|
|
170
|
+
Returns the focal dict or None if no focal module is discoverable."""
|
|
171
|
+
test_files = sorted({t.split("::")[0] for t in failing_tests if "::" in t or t.endswith(".py")})
|
|
172
|
+
for tf in test_files:
|
|
173
|
+
tf_abs = os.path.join(repo, tf)
|
|
174
|
+
if not os.path.isfile(tf_abs):
|
|
175
|
+
continue
|
|
176
|
+
tsrc = open(tf_abs, encoding="utf-8", errors="replace").read()
|
|
177
|
+
try:
|
|
178
|
+
tree = ast.parse(tsrc)
|
|
179
|
+
except SyntaxError:
|
|
180
|
+
continue
|
|
181
|
+
for node in ast.walk(tree):
|
|
182
|
+
if isinstance(node, ast.ImportFrom) and node.module and node.level == 0:
|
|
183
|
+
mod_rel = node.module.replace(".", "/") + ".py"
|
|
184
|
+
mod_abs = os.path.join(repo, mod_rel)
|
|
185
|
+
if not os.path.isfile(mod_abs):
|
|
186
|
+
continue
|
|
187
|
+
for a in node.names:
|
|
188
|
+
if re.search(rf"\b{re.escape(a.name)}\s*\(", tsrc):
|
|
189
|
+
content = open(mod_abs, encoding="utf-8", errors="replace").read()
|
|
190
|
+
return {"files": {mod_rel: content}, "path": mod_rel,
|
|
191
|
+
"function": a.name, "buggy_source": content,
|
|
192
|
+
"repo": os.path.basename(repo), "intent": intent or ""}
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _remote_fix(repo, base, rid, key, inputs, failing_tests, intent, test_command, target, stage):
|
|
197
|
+
"""fix over the hosted engine: POST focal -> receive a candidate diff -> GATE IT HERE
|
|
198
|
+
(differential, vendored client gate) -> VERIFIED or a named refusal. Returns None only when
|
|
199
|
+
the remote is unreachable AND no focal exists (falls through to the NO_ENGINE refusal)."""
|
|
200
|
+
from . import diffs, remote
|
|
201
|
+
focal = _focal_context(repo, failing_tests, intent)
|
|
202
|
+
if focal is None:
|
|
203
|
+
ref = refusals.from_engine("no_candidate", ctx={"target": target, "run_id": rid})
|
|
204
|
+
return _store(repo, _result.make(codes.REFUSED, rid, "fix", code=ref.code, refusal=ref),
|
|
205
|
+
key, inputs)
|
|
206
|
+
stage("REMOTE", f"hosted engine synthesizes {focal['function']} (focal module only transits)")
|
|
207
|
+
try:
|
|
208
|
+
res = remote.candidate(list(failing_tests), focal)
|
|
209
|
+
except remote.RemoteError as e:
|
|
210
|
+
ref = refusals.build(codes.GATE_PRECONDITION, engine_code=str(e),
|
|
211
|
+
ctx={"target": target, "run_id": rid})
|
|
212
|
+
return _store(repo, _result.make(codes.REFUSED, rid, "fix",
|
|
213
|
+
code=codes.GATE_PRECONDITION, refusal=ref), key, inputs)
|
|
214
|
+
if (res.get("status") or "") != "candidate" or not res.get("diff"):
|
|
215
|
+
reason = res.get("refuse_reason") or "no_candidate"
|
|
216
|
+
ref = refusals.from_engine(reason, ctx={"target": target, "run_id": rid})
|
|
217
|
+
return _store(repo, _result.make(codes.REFUSED, rid, "fix", code=ref.code, refusal=ref),
|
|
218
|
+
key, inputs)
|
|
219
|
+
# the diff came back against the focal content we sent -- apply to that, then GATE locally
|
|
220
|
+
parsed = diffs.parse(res["diff"])
|
|
221
|
+
edits, stale = diffs.materialize({f.path: focal["files"].get(f.path, "") for f in parsed}, parsed)
|
|
222
|
+
if not edits:
|
|
223
|
+
ref = refusals.build(codes.HUNK_CONTEXT_STALE, engine_code="remote_diff_stale",
|
|
224
|
+
ctx={"target": ", ".join(stale) or target, "run_id": rid})
|
|
225
|
+
return _store(repo, _result.make(codes.UNVERIFIABLE, rid, "fix",
|
|
226
|
+
code=codes.HUNK_CONTEXT_STALE, refusal=ref), key, inputs)
|
|
227
|
+
stage("GATE", f"differential gate on {', '.join(failing_tests)}")
|
|
228
|
+
sink: dict = {}
|
|
229
|
+
cg = engine.client_gate.run_client_gate(repo, base, edits, test_paths=list(failing_tests),
|
|
230
|
+
test_cmd=test_command, differential=True,
|
|
231
|
+
transcript_sink=sink)
|
|
232
|
+
if not cg.passed:
|
|
233
|
+
ref = refusals.from_engine("candidate_failed_execution_gate",
|
|
234
|
+
ctx={"target": target, "run_id": rid})
|
|
235
|
+
return _store(repo, _result.make(codes.REFUSED, rid, "fix", code=ref.code, refusal=ref,
|
|
236
|
+
gate=_gate_from_sink(sink, failing_tests)), key, inputs)
|
|
237
|
+
prov = [str(p) for p in (res.get("provenance") or [])][:8] + ["execution_gate:client:PASS"]
|
|
238
|
+
diff = "".join(_diff_for(repo, base, p, c) for p, c in sorted(edits.items()))
|
|
239
|
+
out = _result.make(codes.VERIFIED, rid, "fix", diff=diff, files=tuple(sorted(edits)),
|
|
240
|
+
provenance=_result.Provenance(entries=tuple(prov)),
|
|
241
|
+
gate=_gate_from_sink(sink, failing_tests), data={"target": target})
|
|
242
|
+
return _store(repo, out, key, inputs)
|
|
243
|
+
|
|
244
|
+
|
|
158
245
|
def op_write_from_intent(repo: str, intent: str, signature: str,
|
|
159
246
|
into: Optional[str] = None, emit=None,
|
|
160
247
|
prefer_purpose: Optional[str] = None) -> "_result.SubstratumResult":
|
|
@@ -85,8 +85,10 @@ def clarify(intent: str, signature: str) -> dict:
|
|
|
85
85
|
def candidate(test_ids: list, focal: dict) -> dict:
|
|
86
86
|
"""POST /api/cli/candidate -> {status, diff, provenance, refuse_reason}. The server SYNTHESIZES;
|
|
87
87
|
the client gates the returned diff locally, so the user's code + tests never leave the machine."""
|
|
88
|
+
# generous timeout: the first call on a cold server pays the numpy import chain + the lazy
|
|
89
|
+
# library's resident index load before it can synthesize
|
|
88
90
|
return _post_json(account.server() + "/api/cli/candidate",
|
|
89
|
-
{"test_ids": list(test_ids), "focal": focal}, _auth_headers())
|
|
91
|
+
{"test_ids": list(test_ids), "focal": focal}, _auth_headers(), timeout=180)
|
|
90
92
|
|
|
91
93
|
|
|
92
94
|
# ---- GitHub device-flow login --------------------------------------------------------------------
|
|
@@ -108,33 +108,50 @@ class Session:
|
|
|
108
108
|
pn = S.Painter(self.color)
|
|
109
109
|
name = os.path.basename(self.repo) or self.repo
|
|
110
110
|
langs, files = self._repo_summary()
|
|
111
|
-
lib = engine.lib_roots.py_library_root()
|
|
112
|
-
libname = os.path.basename(lib) if lib else "none"
|
|
113
111
|
census = f"{files} files" + ((" · " + langs.replace(" ", " · ")) if langs else "")
|
|
114
112
|
st = build_state.standing(self.repo)
|
|
115
113
|
ascii_mode = S.use_ascii()
|
|
116
114
|
logged_in = account.is_logged_in()
|
|
117
115
|
_update.refresh_async() # refresh the update cache in the background (next session)
|
|
118
116
|
update_to = _update.available(_cver) # instant read from cache; "" if none or unknown
|
|
117
|
+
|
|
118
|
+
# HONEST substrate: which intelligence actually serves fix/write this session, and in which mode.
|
|
119
|
+
# Two real deployment modes: HOSTED (thin client -> engine on Fly) and AIRGAPPED-LOCAL (engine +
|
|
120
|
+
# library shipped on-prem). Never claim a substrate that is not backing this session.
|
|
121
|
+
local_present = False
|
|
122
|
+
try:
|
|
123
|
+
_p = engine.lib_roots.py_library_root()
|
|
124
|
+
local_present = bool(_p) and os.path.exists(_p)
|
|
125
|
+
except Exception:
|
|
126
|
+
local_present = False # a thin client has no local library -- that is fine
|
|
127
|
+
ev = _update.engine_version() # hosted engine's reported version (cached), "" if unknown
|
|
128
|
+
if logged_in: # hosted mode: write/fix reach the engine on the server
|
|
129
|
+
substrate = ev or "hosted engine"
|
|
130
|
+
sub_note, sub_ok, seal_key = (("hosted · connected", True, ev) if ev else ("connecting", False, "hosted"))
|
|
131
|
+
elif local_present: # airgapped / self-hosted: the on-disk library serves it
|
|
132
|
+
_warm = engine.is_warm()
|
|
133
|
+
substrate = _boot.RELEASE
|
|
134
|
+
sub_note, sub_ok, seal_key = (("local · ready" if _warm else "local · loading"), _warm, _boot.RELEASE)
|
|
135
|
+
else: # thin client, not connected -> claim nothing
|
|
136
|
+
substrate, sub_note, sub_ok, seal_key = "not connected", "login to connect", False, "offline"
|
|
119
137
|
# THE PROOF SEAL -- a self-certifying certificate boot: a byte-identical determinism seal (a real
|
|
120
138
|
# hash of engine + substrate + scheme, no time/session input), the struck-plane emblem (the logo),
|
|
121
139
|
# and a structured ledger. The sameness of the seal every boot IS the proof of determinism -- a
|
|
122
140
|
# token-streaming tool cannot draw a stable self-certificate. Needs a wide pane; compact below.
|
|
123
141
|
if S.seal_cols() >= 84:
|
|
124
|
-
for ln in _boot.render(name, census, "py · go · ts",
|
|
142
|
+
for ln in _boot.render(name, census, "py · go · ts", substrate, sub_note, sub_ok,
|
|
125
143
|
st, "0.1.0", color=self.color, ascii_mode=ascii_mode,
|
|
126
|
-
logged_in=logged_in, update_to=update_to):
|
|
144
|
+
logged_in=logged_in, update_to=update_to, seal_key=seal_key):
|
|
127
145
|
self._say(ln)
|
|
128
146
|
return
|
|
129
147
|
# compact identity (narrow pane): the mark + a lime-railed status
|
|
130
|
-
state =
|
|
131
|
-
else pn(S.g("dot") + " loading", S.CHROME))
|
|
148
|
+
state = pn(sub_note, S.PROVEN if sub_ok else S.CHROME)
|
|
132
149
|
rail = ("\033[38;5;154m▌\033[0m" if (self.color and not ascii_mode) else "|")
|
|
133
150
|
brand = (f"\033[38;5;154m{S.g('brand')}\033[0m" if (self.color and not ascii_mode)
|
|
134
151
|
else pn(S.g("brand"), S.PROVEN))
|
|
135
152
|
self._say(brand + " " + pn("SUBSTRATUM", S.BOLD) + pn(" proof-carrying · deterministic", S.LABEL))
|
|
136
153
|
self._say(f" {rail} " + pn(name, S.BOLD) + " " + pn(census, S.LABEL))
|
|
137
|
-
self._say(f" {rail} " + pn(
|
|
154
|
+
self._say(f" {rail} " + pn(substrate, S.LABEL) + " " + state)
|
|
138
155
|
if st:
|
|
139
156
|
bits = pn(f"{st['runs']} runs", S.LABEL)
|
|
140
157
|
if st.get("verified"):
|
|
@@ -32,6 +32,7 @@ substratum_cli.egg-info/requires.txt
|
|
|
32
32
|
substratum_cli.egg-info/top_level.txt
|
|
33
33
|
substratum_cli/_vendor/__init__.py
|
|
34
34
|
substratum_cli/_vendor/_pro_test_derive.py
|
|
35
|
+
substratum_cli/_vendor/client_gate.py
|
|
35
36
|
substratum_cli/commands/__init__.py
|
|
36
37
|
substratum_cli/commands/apply.py
|
|
37
38
|
substratum_cli/commands/auth.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|