sentaince 0.1.0__py3-none-any.whl
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.
- cerebral/README.md +50 -0
- cerebral/__init__.py +17 -0
- cerebral/gauge/__init__.py +6 -0
- cerebral/gauge/fi_gauge.py +364 -0
- cerebral/gauge/resurrection_gauge.py +278 -0
- cerebral/intents.py +395 -0
- cerebral/journal.py +219 -0
- cerebral/tests/__init__.py +5 -0
- cerebral/tests/test_fi_gauge.py +154 -0
- cerebral/tests/test_governor_mcp.py +153 -0
- cerebral/tests/test_journal.py +191 -0
- cerebral/tests/test_resurrection.py +284 -0
- exocortex/MEMORY_GAUGE_DESIGN.md +545 -0
- exocortex/README.md +92 -0
- exocortex/STAGE1_SOMATIC_VERDICT.md +41 -0
- exocortex/STAGE2_EPISTEMIC_FINDING.md +42 -0
- exocortex/__init__.py +15 -0
- exocortex/adapter.py +134 -0
- exocortex/audit.py +72 -0
- exocortex/colony.py +317 -0
- exocortex/config.py +186 -0
- exocortex/cue_classifier.py +137 -0
- exocortex/deploy.py +356 -0
- exocortex/docs/BRIDGE_ORGAN_DESIGN.md +105 -0
- exocortex/docs/CORE.md +103 -0
- exocortex/docs/FEATURES.md +158 -0
- exocortex/docs/README.md +26 -0
- exocortex/docs/USERS_GUIDE.md +182 -0
- exocortex/docs/WHITEPAPER.md +108 -0
- exocortex/embed_classifier.py +127 -0
- exocortex/endocrine.py +41 -0
- exocortex/epistemic.py +81 -0
- exocortex/exocortex_config.json +72 -0
- exocortex/gauge/__init__.py +1 -0
- exocortex/gauge/analyze.py +258 -0
- exocortex/gauge/attribution_gauge.py +174 -0
- exocortex/gauge/bridge_gauge.py +216 -0
- exocortex/gauge/coldstart_gauge.py +166 -0
- exocortex/gauge/consolidation_gauge.py +260 -0
- exocortex/gauge/credit_funnel_gauge.py +262 -0
- exocortex/gauge/credit_hygiene_gauge.py +233 -0
- exocortex/gauge/dambrogio_gauge.py +291 -0
- exocortex/gauge/eligibility_gauge.py +261 -0
- exocortex/gauge/endocrine_gauge.py +368 -0
- exocortex/gauge/nonstationarity_gauge.py +378 -0
- exocortex/gauge/palace_gauge.py +131 -0
- exocortex/gauge/uncertainty_gauge.py +168 -0
- exocortex/genome.py +193 -0
- exocortex/hook.py +618 -0
- exocortex/integrity.py +242 -0
- exocortex/integrity_baseline.json +42 -0
- exocortex/interocept.py +42 -0
- exocortex/ledger.py +218 -0
- exocortex/mcp_server.py +588 -0
- exocortex/runner.py +281 -0
- exocortex/scenarios.py +180 -0
- exocortex/somatic.py +41 -0
- exocortex/state.py +155 -0
- exocortex/stream_runner.py +96 -0
- exocortex/streams/__init__.py +31 -0
- exocortex/streams/_ref/minilang.py +253 -0
- exocortex/streams/interp_v1.py +98 -0
- exocortex/streams/smoke.py +25 -0
- exocortex/testbed/README.md +222 -0
- exocortex/testbed/__init__.py +6 -0
- exocortex/testbed/attribution_run.py +273 -0
- exocortex/testbed/bridge_run.py +200 -0
- exocortex/testbed/ccr/config.example.json +28 -0
- exocortex/testbed/ccr/strip-thinking.js +45 -0
- exocortex/testbed/compose/autostart/exocortex-testbed-up.ps1 +37 -0
- exocortex/testbed/compose/autostart/install-autostart.ps1 +25 -0
- exocortex/testbed/compose/autostart/uninstall-autostart.ps1 +12 -0
- exocortex/testbed/compose/docker-compose.yml +116 -0
- exocortex/testbed/compose/grafana/dashboards/exocortex.json +236 -0
- exocortex/testbed/compose/grafana/dashboards/organism.json +987 -0
- exocortex/testbed/compose/grafana/provisioning/dashboards/dashboards.yml +10 -0
- exocortex/testbed/compose/grafana/provisioning/datasources/datasource.yml +18 -0
- exocortex/testbed/compose/prometheus.yml +10 -0
- exocortex/testbed/compose/promtail.yml +28 -0
- exocortex/testbed/cursor_probe/.gitignore +2 -0
- exocortex/testbed/cursor_probe/README.md +126 -0
- exocortex/testbed/cursor_probe/__init__.py +6 -0
- exocortex/testbed/cursor_probe/analyze.py +132 -0
- exocortex/testbed/cursor_probe/install.py +131 -0
- exocortex/testbed/cursor_probe/probe.py +219 -0
- exocortex/testbed/cursor_tests/test_adapter.py +165 -0
- exocortex/testbed/exporter/__init__.py +1 -0
- exocortex/testbed/exporter/metrics.py +916 -0
- exocortex/testbed/feeder.py +211 -0
- exocortex/testbed/proof_route_a.py +118 -0
- exocortex/tests/__init__.py +0 -0
- exocortex/tests/test_attribution_gauge.py +106 -0
- exocortex/tests/test_bridge_gauge.py +45 -0
- exocortex/tests/test_bridge_organ.py +262 -0
- exocortex/tests/test_bridge_run.py +23 -0
- exocortex/tests/test_consolidation_gauge.py +131 -0
- exocortex/tests/test_credit_funnel_gauge.py +146 -0
- exocortex/tests/test_credit_hygiene_gauge.py +55 -0
- exocortex/tests/test_deploy.py +88 -0
- exocortex/tests/test_exocortex.py +840 -0
- exocortex/tests/test_exporter_metrics.py +46 -0
- exocortex/tests/test_exporter_security.py +123 -0
- exocortex/tests/test_integrity.py +123 -0
- exocortex/tests/test_ledger.py +96 -0
- exocortex/tests/test_mcp_server.py +258 -0
- exocortex/tests/test_nonstationarity_gauge.py +110 -0
- exocortex/tests/test_provenance.py +87 -0
- exocortex/tests/test_uncertainty_gauge.py +55 -0
- exocortex/tests/test_wiki.py +488 -0
- exocortex/wiki/__init__.py +8 -0
- exocortex/wiki/attribute.py +126 -0
- exocortex/wiki/bridge.py +246 -0
- exocortex/wiki/digest.py +204 -0
- exocortex/wiki/node.py +128 -0
- exocortex/wiki/propose.py +175 -0
- exocortex/wiki/splice.py +131 -0
- exocortex/wiki/store.py +182 -0
- exocortex/wiki/wire.py +65 -0
- sentaince/__init__.py +8 -0
- sentaince/agents/__init__.py +1 -0
- sentaince/agents/metabolic.py +189 -0
- sentaince/agents/naive.py +33 -0
- sentaince/agents/organism.py +42 -0
- sentaince/interface/__init__.py +1 -0
- sentaince/interface/ollama.py +100 -0
- sentaince/interface/scripted.py +45 -0
- sentaince/interface/tools.py +62 -0
- sentaince/kernel/__init__.py +22 -0
- sentaince/organism/__init__.py +1 -0
- sentaince/organism/action_graph.py +53 -0
- sentaince/organism/anomaly.py +76 -0
- sentaince/organism/antibody.py +228 -0
- sentaince/organism/executor.py +33 -0
- sentaince/organism/gearbox.py +53 -0
- sentaince/organism/interlock.py +67 -0
- sentaince/organism/learned_signature.py +159 -0
- sentaince/organism/metabolism.py +39 -0
- sentaince/organism/outcome_oracle.py +168 -0
- sentaince-0.1.0.dist-info/METADATA +166 -0
- sentaince-0.1.0.dist-info/RECORD +144 -0
- sentaince-0.1.0.dist-info/WHEEL +4 -0
- sentaince-0.1.0.dist-info/entry_points.txt +4 -0
- sentaince-0.1.0.dist-info/licenses/LICENSE +201 -0
- sentaince-0.1.0.dist-info/licenses/NOTICE +10 -0
cerebral/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Cerebral Substrate (`cerebral/`) — Slice 0
|
|
2
|
+
|
|
3
|
+
The slow, off-hot-path organ of the SentAInce ecosystem. Where the fast body (the Claude Code / Cursor
|
|
4
|
+
hooks) earns and enforces in milliseconds, the Cerebral Substrate consolidates, organizes, and paces over
|
|
5
|
+
minutes — the altitude at which G.A.R.D. **Governance / Alliance / Dignity** finally make sense, plus
|
|
6
|
+
long-term memory. It is the neocortex to the colony's basal-ganglia + the wiki's hippocampus.
|
|
7
|
+
|
|
8
|
+
**Full design:** the Cerebral Substrate design review is kept outside this repo.
|
|
9
|
+
|
|
10
|
+
## Discipline (read first)
|
|
11
|
+
|
|
12
|
+
- **Additive & READ-ONLY.** This package observes and reorganizes the *record*; it never earns τ, never
|
|
13
|
+
writes a colony / session / audit, and never touches the vault it reads. Only an `exit 0` in a live body
|
|
14
|
+
earns memory (ADR-001). The Substrate is a **proposer/archivist, never a disposer**.
|
|
15
|
+
- **The lock stays the lock.** `pytest` (which collects only `tests/`) remains the 99-test build-gate.
|
|
16
|
+
This package's tests live under `cerebral/tests/` and run explicitly: `python -m pytest cerebral/tests`.
|
|
17
|
+
- **Gauge-first (ADR-002).** No organ is built before a gauge says the prize is real. Slice 0 is *only*
|
|
18
|
+
the gauge.
|
|
19
|
+
- **Live = demonstration, never evidence.** A run over a real vault is a labeled demonstration; it can
|
|
20
|
+
never move a locked verdict.
|
|
21
|
+
|
|
22
|
+
## Slice 0 — the TAO Resurrection Gauge
|
|
23
|
+
|
|
24
|
+
The Substrate's core mechanism is a **living ledger** with two registers: a *utility register* (pruned
|
|
25
|
+
colony routes, `+1/0/−1` by consequence) and an *intent register* (research threads). An **intent** is an
|
|
26
|
+
open loop with a TTL — it opens, and stays OPEN until its intent closes; one that goes stale past a
|
|
27
|
+
*reasonable timeframe* is a **crack-faller**, the resurrection target.
|
|
28
|
+
|
|
29
|
+
This gauge measures — read-only, offline — whether the intent register can surface genuinely-lost,
|
|
30
|
+
worth-resuming threads from a research vault. It harvests **declared** intents only (Markdown checkboxes,
|
|
31
|
+
task-status/manifest files, structured `ledger.json` decision labels) — never inferred from prose —
|
|
32
|
+
resolves dates, flags `OPEN ∧ stale`, and (once the PI labels a sample) reports
|
|
33
|
+
**precision @ worth-resuming**. That number gates the persistent ledger + Consolidator + Governor.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# emit the stale-open candidate list (read-only):
|
|
37
|
+
python -m cerebral.gauge.resurrection_gauge --vault ~/research-vault --now 2026-07-01 --json
|
|
38
|
+
|
|
39
|
+
# after labelling candidates → precision:
|
|
40
|
+
python -m cerebral.gauge.resurrection_gauge --vault <path> --now <ISO> --labels labels.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`spaCy` is **optional** — the gauge is pure-stdlib; spaCy only enriches the `(action, anticipated_result)`
|
|
44
|
+
decomposition and fails open to a regex extractor when absent.
|
|
45
|
+
|
|
46
|
+
## Not in this slice (gated on the gauge)
|
|
47
|
+
|
|
48
|
+
The persistent hash-chained JSONL journal, the utility register (colony-prune observation), the
|
|
49
|
+
Consolidator, the Governor read-view (`strategic_status`), the SQLite→Postgres store, the containerized
|
|
50
|
+
daemon, and the circadian actuator — all follow-on slices, built only if this gauge clears its bar.
|
cerebral/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""SentAInce Cerebral Substrate (Slice 0) — the additive, off-hot-path organ that will wire G.A.R.D.
|
|
2
|
+
Governance / Alliance / Dignity at portfolio altitude and hold long-term memory.
|
|
3
|
+
|
|
4
|
+
Everything here is ADDITIVE and READ-ONLY with respect to the organism: it observes and reorganizes the
|
|
5
|
+
*record*, but it never earns τ, never writes a colony / session / audit, and never touches the vault it
|
|
6
|
+
reads. Only an ``exit 0`` in a live body still earns memory (ADR-001). Nothing in this package modifies
|
|
7
|
+
the locked organs or the 99-test deterministic lock under ``tests/`` — this package's own tests live under
|
|
8
|
+
``cerebral/tests/`` and are run explicitly (``python -m pytest cerebral/tests``), mirroring ``battle/``.
|
|
9
|
+
|
|
10
|
+
Slice 0 scope (this commit): **gauge-first (ADR-002)**. Before building the persistent living ledger, the
|
|
11
|
+
Consolidator, the Governor, or any actuator, this ships ONE read-only offline gauge — the *TAO Resurrection
|
|
12
|
+
Gauge* — that measures whether the intent register can surface genuinely-lost, worth-resuming research
|
|
13
|
+
threads. The number it produces (precision @ worth-resuming) gates every downstream Substrate slice. A live
|
|
14
|
+
run over a real vault is a **labeled demonstration, never evidence**.
|
|
15
|
+
|
|
16
|
+
Design: ``../SentAInce_Cerebral_Substrate_Design_Review_v1.1.md`` (outside the repo, pre-counsel).
|
|
17
|
+
"""
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Cerebral Substrate gauges — read-only, pure-stdlib, fail-open offline instruments (ADR-002).
|
|
2
|
+
|
|
3
|
+
Run explicitly (never collected by the 99-lock; ``pyproject testpaths=["tests"]``):
|
|
4
|
+
|
|
5
|
+
python -m cerebral.gauge.resurrection_gauge --vault <path> --now <ISO> --json
|
|
6
|
+
"""
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""FI_hat Decisive Gauge — does the ATTEMPTS DENOMINATOR carry signal raw τ does not? (STATS)
|
|
2
|
+
|
|
3
|
+
The [[functional-information-ledger]]'s first instrument, and the decisive gauge the Cerebral Substrate
|
|
4
|
+
framing turns on. The colony's pheromone τ counts **successes** and is blind to **attempts** (it deposits
|
|
5
|
+
only on ``exit 0`` — ADR-001). Functional information adds the denominator:
|
|
6
|
+
|
|
7
|
+
FI_hat(o,m) = -log2( successes-like-m / attempts-like-m ) (KT / Jeffreys add-1/2 smoothed)
|
|
8
|
+
|
|
9
|
+
So FI_hat's novelty over τ is *entirely* the denominator — the failure count the roadmap has parked for a
|
|
10
|
+
year ([[exocortex-hook-integration-roadmap]] W4; ADR-004 decaying τ⁻). This gauge asks the yes/no question
|
|
11
|
+
before we build the ledger that would carry it:
|
|
12
|
+
|
|
13
|
+
Does knowing per-configuration RELIABILITY (attempts, not just successes) separate good routes from
|
|
14
|
+
clutter in a way the success-count alone cannot?
|
|
15
|
+
|
|
16
|
+
**Data.** The only live denominator on disk is the **audit log** (``audit.jsonl``): every Bash consequence
|
|
17
|
+
is stamped ``outcome: ok|fail``. τ (``colony_*.json``) has no denominator; the intent register's −1/0
|
|
18
|
+
(falsified/inconclusive) valence is unparsed in v1 (all closed intents read +1) — so the *procedural*
|
|
19
|
+
audit is the sole denominator source. We read (attempts n, successes k) per configuration at a chosen
|
|
20
|
+
**altitude** (Amendment 1, the reference-class problem): ``verb`` (``bash:git``, coarser, better powered)
|
|
21
|
+
or ``command_key`` (the strategy-lock's verb+first-token unit, finer, sparser).
|
|
22
|
+
|
|
23
|
+
**The decisive statistic — the frequency-matched outcome-shuffle null** (the project's own discipline,
|
|
24
|
+
``gauge/analyze.py`` ``_run_policy`` shuffle). The denominator is informative iff configurations genuinely
|
|
25
|
+
differ in reliability *beyond* Bernoulli chance at one shared base rate. We compute the between-config
|
|
26
|
+
reliability dispersion T, then permute the pooled ok/fail outcomes across the same attempt-slots (n_c
|
|
27
|
+
preserved = frequency-matched) R times and read a permutation p-value. τ is invariant to *where* the
|
|
28
|
+
failures land; T is exactly about that — so T's excess-over-null is the denominator's marginal signal.
|
|
29
|
+
|
|
30
|
+
**Verdict.**
|
|
31
|
+
· UNDERPOWERED — too few configs clear the attempt floor (the flagship single-dev regime; forces the
|
|
32
|
+
failure-ledger into existence but cannot size it — [[oss-community-strategy]]'s population argument).
|
|
33
|
+
· BUILD — reliability heterogeneity beats the shuffle null (p < ALPHA) → FI_hat is a genuine new organ.
|
|
34
|
+
· NULL — reliabilities indistinguishable from one shared coin → FI_hat is a better *language* for what τ
|
|
35
|
+
already does, not a new mechanism (still paper/moat value).
|
|
36
|
+
|
|
37
|
+
Read-only, pure-stdlib, deterministic (audit path(s) as args, seeded permutation, no wall clock), fail-open.
|
|
38
|
+
A run over a real audit is a **labeled demonstration, never evidence** (Live = demonstration; the C1–C7
|
|
39
|
+
lock rests on topology, not on this).
|
|
40
|
+
|
|
41
|
+
python -m cerebral.gauge.fi_gauge --audit .claude/exocortex/audit.jsonl # verb altitude
|
|
42
|
+
python -m cerebral.gauge.fi_gauge --audit A.jsonl --audit B.jsonl --altitude command_key
|
|
43
|
+
python -m cerebral.gauge.fi_gauge --audit A.jsonl --json
|
|
44
|
+
"""
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import argparse
|
|
48
|
+
import json
|
|
49
|
+
import math
|
|
50
|
+
import os
|
|
51
|
+
import random
|
|
52
|
+
from pathlib import Path
|
|
53
|
+
|
|
54
|
+
# ship/no-ship thresholds (thin — the numbers drive it)
|
|
55
|
+
MIN_ATTEMPTS = 5 # a configuration is POWERED (enters the test) only at ≥ this many attempts
|
|
56
|
+
MIN_POWERED = 6 # …and the whole gauge abstains (UNDERPOWERED) below this many powered configs
|
|
57
|
+
ALPHA = 0.05 # permutation significance for reliability-heterogeneity beyond the shuffle null
|
|
58
|
+
PERM_R = 2000 # permutation replicates (seeded → deterministic)
|
|
59
|
+
SEED = 0 # fixed RNG seed (mirror analyze.py `random.Random(seed)`)
|
|
60
|
+
|
|
61
|
+
# verbs whose failures are mechanical noise (path typos / trivial), not "this route is a bad idea" signal —
|
|
62
|
+
# reported so a BUILD is never claimed on cd-slash-typo dispersion. Descriptive; does NOT gate the test.
|
|
63
|
+
NOISE_VERBS = frozenset({"cd", "echo", "ls", "cat", "pwd", "mkdir", "touch", "which", "export", "env"})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ------------------------------------------------------------------ load + key
|
|
67
|
+
def _load_audit(paths: list) -> list:
|
|
68
|
+
"""All parseable JSONL records across the given audit files (fail-open: a missing/garbled file → skip)."""
|
|
69
|
+
rows: list = []
|
|
70
|
+
for p in paths:
|
|
71
|
+
try:
|
|
72
|
+
for line in Path(p).read_text(encoding="utf-8").splitlines():
|
|
73
|
+
line = line.strip()
|
|
74
|
+
if not line:
|
|
75
|
+
continue
|
|
76
|
+
try:
|
|
77
|
+
rows.append(json.loads(line))
|
|
78
|
+
except Exception:
|
|
79
|
+
continue
|
|
80
|
+
except Exception:
|
|
81
|
+
continue
|
|
82
|
+
return rows
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _verb(command: str) -> str:
|
|
86
|
+
tok = (command or "").strip().split()
|
|
87
|
+
return os.path.basename(tok[0]) if tok else "?"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _is_noise_verb(config_key: str) -> bool:
|
|
91
|
+
"""Is this config's leading verb mechanical noise (cd/echo/ls path-typos), not route-quality? Guarded
|
|
92
|
+
against empty/degenerate keys (a command starting with ``/`` yields ``bash:`` with no verb)."""
|
|
93
|
+
toks = config_key.replace("bash:", "").strip().split()
|
|
94
|
+
verb = toks[0].split("=")[0] if toks else ""
|
|
95
|
+
return verb in NOISE_VERBS
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _config_key(rec: dict, altitude: str) -> str:
|
|
99
|
+
"""The configuration ``m`` this Bash consequence belongs to, at the chosen reference-class altitude."""
|
|
100
|
+
if altitude == "command_key":
|
|
101
|
+
return rec.get("command_key") or (rec.get("command", "")[:24]) or "?"
|
|
102
|
+
return f"bash:{_verb(rec.get('command', ''))}" # 'verb' (default)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def build_configs(rows: list, altitude: str) -> dict:
|
|
106
|
+
"""{config_key: [attempts, successes]} over Bash records carrying an ``ok|fail`` outcome."""
|
|
107
|
+
cfg: dict = {}
|
|
108
|
+
for r in rows:
|
|
109
|
+
if r.get("tool") != "Bash" or r.get("outcome") not in ("ok", "fail"):
|
|
110
|
+
continue
|
|
111
|
+
k = _config_key(r, altitude)
|
|
112
|
+
rec = cfg.setdefault(k, [0, 0])
|
|
113
|
+
rec[0] += 1
|
|
114
|
+
if r.get("outcome") == "ok":
|
|
115
|
+
rec[1] += 1
|
|
116
|
+
return cfg
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------ FI math
|
|
120
|
+
def reliability(n: int, k: int) -> float:
|
|
121
|
+
"""KT / Jeffreys add-1/2 smoothed success rate p̂ = (k+½)/(n+1). Bounded off {0,1} so FI_hat is finite
|
|
122
|
+
on a single trial (the small-n discipline: never report an un-smoothed 0/1 as certainty)."""
|
|
123
|
+
return (k + 0.5) / (n + 1.0)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def fi_hat(n: int, k: int) -> float:
|
|
127
|
+
"""Functional information in bits: -log2(p̂). Monotone-DECREASING in reliability — a route that always
|
|
128
|
+
works is low-FI ('easy'); a rarely-succeeding one is high-FI (Szostak rarity). Same denominator either
|
|
129
|
+
polarity; curation reads p̂ (keep-reliable), the landscape reads FI_hat (rarity)."""
|
|
130
|
+
return -math.log2(reliability(n, k))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------ decisive statistic
|
|
134
|
+
def _dispersion(powered: list) -> tuple:
|
|
135
|
+
"""Between-config reliability dispersion on RAW proportions (the homogeneity statistic the permutation
|
|
136
|
+
preserves): T = Σ n_c (k_c/n_c − p̄)² / N, p̄ = ΣK/ΣN. Returns (T, N, K). Smoothing is for display/rank
|
|
137
|
+
only — the *test* is on raw rates so the null (a shuffle of raw outcomes) is exactly matched."""
|
|
138
|
+
N = sum(n for n, _ in powered)
|
|
139
|
+
K = sum(k for _, k in powered)
|
|
140
|
+
if N == 0:
|
|
141
|
+
return 0.0, 0, 0
|
|
142
|
+
pbar = K / N
|
|
143
|
+
T = sum(n * ((k / n) - pbar) ** 2 for n, k in powered) / N
|
|
144
|
+
return T, N, K
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def permutation_p(powered: list, r: int = PERM_R, seed: int = SEED) -> dict:
|
|
148
|
+
"""Frequency-matched outcome-shuffle null. Pool the ΣK successes + (ΣN−ΣK) failures, repeatedly deal
|
|
149
|
+
them at random into the same per-config attempt-slots (n_c preserved), recompute T. p = (#{T*≥T_obs}+1)/
|
|
150
|
+
(R+1). Degenerate pool (all ok or all fail) → no heterogeneity possible → p=1.0."""
|
|
151
|
+
T_obs, N, K = _dispersion(powered)
|
|
152
|
+
ns = [n for n, _ in powered]
|
|
153
|
+
if N == 0 or K == 0 or K == N: # no denominator variance at all
|
|
154
|
+
return {"T_obs": round(T_obs, 6), "p_value": 1.0, "n_perm": 0, "N": N, "K": K, "base_rate": None}
|
|
155
|
+
pool = [1] * K + [0] * (N - K)
|
|
156
|
+
rng = random.Random(seed)
|
|
157
|
+
ge = 0
|
|
158
|
+
for _ in range(r):
|
|
159
|
+
rng.shuffle(pool)
|
|
160
|
+
i = 0
|
|
161
|
+
Tp = 0.0
|
|
162
|
+
pbar = K / N
|
|
163
|
+
for n in ns:
|
|
164
|
+
k = sum(pool[i:i + n])
|
|
165
|
+
i += n
|
|
166
|
+
Tp += n * ((k / n) - pbar) ** 2
|
|
167
|
+
Tp /= N
|
|
168
|
+
if Tp >= T_obs - 1e-12:
|
|
169
|
+
ge += 1
|
|
170
|
+
return {"T_obs": round(T_obs, 6), "p_value": round((ge + 1) / (r + 1), 5),
|
|
171
|
+
"n_perm": r, "N": N, "K": K, "base_rate": round(K / N, 4)}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _spearman(xs: list, ys: list) -> "float | None":
|
|
175
|
+
"""Spearman rank correlation (avg-rank ties). None if <3 points or a constant vector. Descriptive:
|
|
176
|
+
how much the denominator (reliability) RE-RANKS configs vs the τ-analog (success count)."""
|
|
177
|
+
n = len(xs)
|
|
178
|
+
if n < 3:
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def ranks(v):
|
|
182
|
+
order = sorted(range(n), key=lambda i: v[i])
|
|
183
|
+
rk = [0.0] * n
|
|
184
|
+
i = 0
|
|
185
|
+
while i < n:
|
|
186
|
+
j = i
|
|
187
|
+
while j + 1 < n and v[order[j + 1]] == v[order[i]]:
|
|
188
|
+
j += 1
|
|
189
|
+
avg = (i + j) / 2.0 + 1.0
|
|
190
|
+
for t in range(i, j + 1):
|
|
191
|
+
rk[order[t]] = avg
|
|
192
|
+
i = j + 1
|
|
193
|
+
return rk
|
|
194
|
+
|
|
195
|
+
rx, ry = ranks(xs), ranks(ys)
|
|
196
|
+
mx, my = sum(rx) / n, sum(ry) / n
|
|
197
|
+
num = sum((rx[i] - mx) * (ry[i] - my) for i in range(n))
|
|
198
|
+
dx = math.sqrt(sum((rx[i] - mx) ** 2 for i in range(n)))
|
|
199
|
+
dy = math.sqrt(sum((ry[i] - my) ** 2 for i in range(n)))
|
|
200
|
+
if dx == 0 or dy == 0:
|
|
201
|
+
return None
|
|
202
|
+
return round(num / (dx * dy), 4)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ------------------------------------------------------------------ analysis
|
|
206
|
+
def analyze(cfg: dict, min_attempts: int, r: int, seed: int) -> dict:
|
|
207
|
+
"""Powered configs + FI/τ table, the permutation verdict, τ↔reliability disagreement, and the honest
|
|
208
|
+
noise readout. Pure function of (cfg, thresholds) → deterministic."""
|
|
209
|
+
rows = []
|
|
210
|
+
for key, (n, k) in cfg.items():
|
|
211
|
+
rows.append({
|
|
212
|
+
"config": key, "attempts": n, "successes": k, "failures": n - k,
|
|
213
|
+
"tau_analog": k, # the colony's numerator (ignores attempts)
|
|
214
|
+
"reliability": round(reliability(n, k), 4), # p̂ — the denominator's contribution
|
|
215
|
+
"fi_hat_bits": round(fi_hat(n, k), 4),
|
|
216
|
+
"powered": n >= min_attempts,
|
|
217
|
+
"noise_verb": _is_noise_verb(key),
|
|
218
|
+
})
|
|
219
|
+
rows.sort(key=lambda x: (-x["attempts"], x["config"]))
|
|
220
|
+
powered_rows = [x for x in rows if x["powered"]]
|
|
221
|
+
powered = [(x["attempts"], x["successes"]) for x in powered_rows]
|
|
222
|
+
|
|
223
|
+
perm = permutation_p(powered, r=r, seed=seed)
|
|
224
|
+
rho = _spearman([x["tau_analog"] for x in powered_rows],
|
|
225
|
+
[x["reliability"] for x in powered_rows])
|
|
226
|
+
|
|
227
|
+
# τ↔FI disagreement: high-τ (many successes) but low reliability = 'busy but flaky' — what FI demotes.
|
|
228
|
+
flaky = sorted([x for x in powered_rows if x["failures"] > 0],
|
|
229
|
+
key=lambda x: (x["reliability"], -x["attempts"]))[:8]
|
|
230
|
+
|
|
231
|
+
# honest noise readout — do the failures live on route-quality verbs or on cd/echo path-typo noise?
|
|
232
|
+
tot_fail = sum(x["failures"] for x in rows)
|
|
233
|
+
noise_fail = sum(x["failures"] for x in rows if x["noise_verb"])
|
|
234
|
+
return {
|
|
235
|
+
"counts": {
|
|
236
|
+
"configs": len(rows), "powered": len(powered_rows),
|
|
237
|
+
"attempts_powered": perm["N"], "successes_powered": perm["K"],
|
|
238
|
+
"total_failures": tot_fail, "noise_failures": noise_fail,
|
|
239
|
+
"noise_failure_frac": round(noise_fail / tot_fail, 3) if tot_fail else None,
|
|
240
|
+
},
|
|
241
|
+
"permutation": perm,
|
|
242
|
+
"spearman_tau_vs_reliability": rho,
|
|
243
|
+
"powered_configs": powered_rows,
|
|
244
|
+
"flaky_disagreements": flaky,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def verdict(a: dict, min_powered: int, alpha: float) -> dict:
|
|
249
|
+
"""UNDERPOWERED / BUILD / NULL from the powered count + the permutation p-value."""
|
|
250
|
+
c = a["counts"]
|
|
251
|
+
perm = a["permutation"]
|
|
252
|
+
powered = c["powered"]
|
|
253
|
+
p = perm["p_value"]
|
|
254
|
+
if powered < min_powered:
|
|
255
|
+
return {"signal": None, "label": "UNDERPOWERED", "p_value": p, "powered": powered,
|
|
256
|
+
"note": f"UNDERPOWERED — {powered} powered configs < {min_powered}; the denominator instrument "
|
|
257
|
+
f"is too thin at this traffic to size (single-dev regime). Forces the failure-ledger "
|
|
258
|
+
f"into existence but cannot gauge it here → needs the population/OSS regime."}
|
|
259
|
+
if perm["K"] in (0, perm["N"]) or perm["base_rate"] is None:
|
|
260
|
+
return {"signal": False, "label": "NULL", "p_value": p, "powered": powered,
|
|
261
|
+
"note": "NULL — no denominator variance (all-ok or all-fail across powered configs); FI_hat "
|
|
262
|
+
"collapses to a constant → strictly redundant with τ."}
|
|
263
|
+
if p < alpha:
|
|
264
|
+
nf = c["noise_failure_frac"]
|
|
265
|
+
caveat = (f" CAVEAT: {int((nf or 0) * 100)}% of failures are on mechanical-noise verbs "
|
|
266
|
+
f"(cd/echo/ls path-typos) — inspect flaky_disagreements before trusting the signal."
|
|
267
|
+
if (nf or 0) >= 0.5 else "")
|
|
268
|
+
return {"signal": True, "label": "BUILD", "p_value": p, "powered": powered,
|
|
269
|
+
"note": f"BUILD — reliability heterogeneity beats the shuffle null (p={p} < {alpha}) on "
|
|
270
|
+
f"{powered} powered configs → the denominator carries signal τ lacks; FI_hat is a "
|
|
271
|
+
f"genuine new organ.{caveat}"}
|
|
272
|
+
return {"signal": False, "label": "NULL", "p_value": p, "powered": powered,
|
|
273
|
+
"note": f"NULL — per-config reliabilities are indistinguishable from one shared base rate "
|
|
274
|
+
f"(p={p} ≥ {alpha}) on {powered} powered configs → the denominator adds nothing here; "
|
|
275
|
+
f"FI_hat is a better *language* for what τ already does, not a new mechanism."}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def run(audit, altitude: str = "verb", min_attempts: int = MIN_ATTEMPTS,
|
|
279
|
+
min_powered: int = MIN_POWERED, alpha: float = ALPHA, r: int = PERM_R, seed: int = SEED) -> dict:
|
|
280
|
+
"""``audit`` is either audit-file path(s) (str/Path or a list of them — the CLI) or an in-memory list of
|
|
281
|
+
record-dicts (tests). Polymorphic so a caller can gauge a live log OR a synthetic corpus deterministically."""
|
|
282
|
+
if altitude not in ("verb", "command_key"):
|
|
283
|
+
raise SystemExit(f"--altitude must be verb|command_key (got {altitude!r})")
|
|
284
|
+
if isinstance(audit, (str, Path)):
|
|
285
|
+
paths, rows = [str(audit)], _load_audit([audit])
|
|
286
|
+
elif audit and isinstance(audit[0], dict):
|
|
287
|
+
paths, rows = ["<in-memory>"], list(audit) # already-loaded records (tests / a live feed)
|
|
288
|
+
else:
|
|
289
|
+
paths, rows = [str(p) for p in audit], _load_audit(list(audit))
|
|
290
|
+
cfg = build_configs(rows, altitude)
|
|
291
|
+
a = analyze(cfg, min_attempts, r, seed)
|
|
292
|
+
return {
|
|
293
|
+
"audit_paths": [str(p) for p in paths], "altitude": altitude,
|
|
294
|
+
"records": len(rows), "counts": a["counts"], "permutation": a["permutation"],
|
|
295
|
+
"spearman_tau_vs_reliability": a["spearman_tau_vs_reliability"],
|
|
296
|
+
"powered_configs": a["powered_configs"], "flaky_disagreements": a["flaky_disagreements"],
|
|
297
|
+
"verdict": verdict(a, min_powered, alpha),
|
|
298
|
+
"thresholds": {"MIN_ATTEMPTS": min_attempts, "MIN_POWERED": min_powered,
|
|
299
|
+
"ALPHA": alpha, "PERM_R": r, "SEED": seed},
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ------------------------------------------------------------------ text output
|
|
304
|
+
def _fmt(res: dict, top: int = 15) -> str:
|
|
305
|
+
c = res["counts"]
|
|
306
|
+
perm = res["permutation"]
|
|
307
|
+
L = ["FI_hat DECISIVE GAUGE (does the ATTEMPTS denominator carry signal raw τ does not?)",
|
|
308
|
+
f" audit={', '.join(Path(p).name for p in res['audit_paths'])} altitude={res['altitude']} "
|
|
309
|
+
f"records={res['records']}", "",
|
|
310
|
+
f" configs={c['configs']} powered(n≥{res['thresholds']['MIN_ATTEMPTS']})={c['powered']} "
|
|
311
|
+
f"(attempts={c['attempts_powered']} successes={c['successes_powered']})",
|
|
312
|
+
f" failures: total={c['total_failures']} on-noise-verbs={c['noise_failures']} "
|
|
313
|
+
f"(noise_frac={c['noise_failure_frac']})",
|
|
314
|
+
f" base success rate (powered) = {perm['base_rate']}", ""]
|
|
315
|
+
if res["powered_configs"]:
|
|
316
|
+
L.append(f" POWERED configs [succ/att reliability p̂ FI_hat bits]:")
|
|
317
|
+
for x in res["powered_configs"][:top]:
|
|
318
|
+
flag = "NOISE" if x["noise_verb"] else ("flaky" if x["failures"] else "")
|
|
319
|
+
L.append(f" {x['config']:<30} {x['successes']:>4}/{x['attempts']:<4} "
|
|
320
|
+
f"p̂={x['reliability']:.2f} FI={x['fi_hat_bits']:.2f} {flag}")
|
|
321
|
+
L += ["",
|
|
322
|
+
f" DECISIVE — reliability-heterogeneity vs the frequency-matched shuffle null:",
|
|
323
|
+
f" dispersion T_obs={perm['T_obs']} permutation p={perm['p_value']} "
|
|
324
|
+
f"(R={perm['n_perm']}, seed={res['thresholds']['SEED']})",
|
|
325
|
+
f" Spearman(τ-analog k, reliability p̂) on powered = {res['spearman_tau_vs_reliability']} "
|
|
326
|
+
f"(≈1 → FI redundant with τ)"]
|
|
327
|
+
if res["flaky_disagreements"]:
|
|
328
|
+
L.append(" what FI would DEMOTE (high-τ but low reliability):")
|
|
329
|
+
for x in res["flaky_disagreements"]:
|
|
330
|
+
L.append(f" {x['config']:<30} {x['successes']}/{x['attempts']} ok p̂={x['reliability']:.2f}"
|
|
331
|
+
f"{' (mechanical noise)' if x['noise_verb'] else ''}")
|
|
332
|
+
v = res["verdict"]
|
|
333
|
+
L += ["", "VERDICT:",
|
|
334
|
+
f" label={v['label']} signal={v['signal']} p_value={v['p_value']} powered={v['powered']}",
|
|
335
|
+
f" => {v['note']}",
|
|
336
|
+
" NOTE: the audit is the SOLE live denominator (τ has none; intent −1/0 valence unparsed in v1).",
|
|
337
|
+
" Live = demonstration, never evidence — the C1–C7 lock rests on topology, not on this."]
|
|
338
|
+
return "\n".join(L) + "\n"
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def main(argv=None) -> int:
|
|
342
|
+
ap = argparse.ArgumentParser(description="FI_hat Decisive Gauge — does the attempts denominator beat τ?")
|
|
343
|
+
ap.add_argument("--audit", action="append", required=True, metavar="PATH",
|
|
344
|
+
help="path to an audit.jsonl (repeatable — pool several corpora)")
|
|
345
|
+
ap.add_argument("--altitude", default="verb", choices=["verb", "command_key"],
|
|
346
|
+
help="reference-class altitude for 'configurations like m' (Amendment 1; default verb)")
|
|
347
|
+
ap.add_argument("--min-attempts", type=int, default=MIN_ATTEMPTS,
|
|
348
|
+
help=f"a config enters the test at ≥ this many attempts (default {MIN_ATTEMPTS})")
|
|
349
|
+
ap.add_argument("--min-powered", type=int, default=MIN_POWERED,
|
|
350
|
+
help=f"abstain (UNDERPOWERED) below this many powered configs (default {MIN_POWERED})")
|
|
351
|
+
ap.add_argument("--alpha", type=float, default=ALPHA, help=f"permutation significance (default {ALPHA})")
|
|
352
|
+
ap.add_argument("--perm", type=int, default=PERM_R, help=f"permutation replicates (default {PERM_R})")
|
|
353
|
+
ap.add_argument("--seed", type=int, default=SEED, help=f"RNG seed (default {SEED} — deterministic)")
|
|
354
|
+
ap.add_argument("--json", action="store_true", help="machine-readable output")
|
|
355
|
+
args = ap.parse_args(argv)
|
|
356
|
+
|
|
357
|
+
res = run(args.audit, args.altitude, args.min_attempts, args.min_powered,
|
|
358
|
+
args.alpha, args.perm, args.seed)
|
|
359
|
+
print(json.dumps(res, indent=2) if args.json else _fmt(res), end="" if args.json else "\n")
|
|
360
|
+
return 0
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
if __name__ == "__main__":
|
|
364
|
+
raise SystemExit(main())
|