form 3 as architecture: machtzit_ha_shekel + no_tracking_logger (vehaya §3.2)
Closes vehaya.md §6 item 2 for the third avodah-zarah form.
Pharaoh's surveillance (Form 3) was previously marked DEFERRED in
talmudic_engine/src/halt/avodah_zarah.pl with the note "requires
operational context (user tracking, cross-session profiling)."
The vehaya document re-frames it: "Specifications must include
category-level commitments that are not negotiable per-deployment...
not as runtime checks layered onto outputs, but as architectural
constraints the system cannot operate without."
Form 3 is structurally different from Forms 1, 2, 4. They fire on
threshold-grade events (an interpretation has wrong source, empty
trace, non-L1 anchors). Form 3 is about a property of the CATEGORY
itself — what it collects, retains, discloses. It cannot be a
halt rule that fires per-request; per-request "is this caller
being surveilled" detection would itself be a Form-3 surveillance
surface. The defense is ARCHITECTURAL — the system is BUILT so the
failure mode is unreachable, not so it is detectable.
TORAH ANCHORS (verified fresh against L1 — 6 positive properties):
Shemot 30:11-16 — the half-shekel:
כִּי תִשָּׂא אֶת־רֹאשׁ … וְנָתְנוּ אִישׁ כֹּפֶר נַפְשׁוֹ … וְלֹא־
יִהְיֶה בָהֶם נֶגֶף בִּפְקֹד אֹתָם.
Counting humans is dangerous; the danger is named NEGEF (plague);
the protection is the kofer-nefesh substitute. Verse 15:
הֶעָשִׁיר לֹא־יַרְבֶּה וְהַדַּל לֹא יַמְעִיט — the rich don't
give more, the poor don't give less. Verse 16: לְזִכָּרוֹן —
aggregate memorial, not roster.
Vayikra 19:14 — לִפְנֵי עִוֵּר לֹא תִתֵּן מִכְשֹׁל וְיָרֵאתָ
מֵּאֱלֹהֶיךָ.
The no-witness protocol: rules that require fear-of-God
enforcement are exactly the rules that operate under invisibility.
Surveillance regimes exploit invisibility; architectural commitment
must hold under no-human-witness conditions.
Devarim 23:16-17 — לֹא־תַסְגִּיר עֶבֶד אֶל־אֲדֹנָיו אֲשֶׁר־יִנָּצֵל
אֵלֶיךָ … עִמְּךָ יֵשֵׁב … בַּמָּקוֹם אֲשֶׁר־יִבְחַר בְּאַחַד
שְׁעָרֶיךָ.
The refusal protocol: don't return one fleeing institutional
power. The caller chooses their own threshold (b'achad sh'arekha
— agency stays with the caller).
Shemot 5:6-8 — לֹא תֹאסִפוּן לָתֵת תֶּבֶן … וְאֶת־מַתְכֹּנֶת
הַלְּבֵנִים … תָּשִׂימוּ עֲלֵיהֶם לֹא תִגְרְעוּ מִמֶּנּוּ.
Pharaoh's straw quota — the canonical anti-quota Torah anchor.
Per-caller productivity tracking is what makes asymmetric
extraction possible.
Devarim 23:8 — לֹא־תְתַעֵב מִצְרִי כִּי־גֵר הָיִיתָ בְאַרְצוֹ.
Anti-targeting: even toward the Egyptians (Pharaoh's people),
the rule is "don't despise." The protections must not become
a surveillance system targeting suspected surveillors.
THE SIX POSITIVE PROPERTIES, mapped to code:
1. KOFER PROTOCOL (Shemot 30:12) — counting goes through a
uniform substitute; identity never enters the count.
2. UNIFORM PROTOCOL (Shemot 30:15) — same per-caller weight;
no stratification.
3. ZIKARON PROTOCOL (Shemot 30:16) — aggregate memorial, not
roster.
4. NO-WITNESS PROTOCOL (Vayikra 19:14) — commitment holds
when no operator is reading the log.
5. REFUSAL PROTOCOL (Devarim 23:16) — no per-caller delivery
to authorities; caller chooses own threshold.
6. ANTI-QUOTA PROTOCOL (Shemot 5:6-8) — no per-caller
productivity numbers.
CREATED:
vav_pipeline/lib/vav_pipeline/machtzit_ha_shekel.ex ~155 lines
The half-shekel — the positive counting protocol. Single Agent
holding ONE non-negative integer (the zikaron):
increment/0 — register one shekel; uniform per
caller (no caller distinction is even
expressible — function arity 0)
increment/1 — register N shekels (batch points)
count/0 — return the aggregate; the ONLY value
the module exposes
reset/0 — clear (tests; no production persistence
by design — storage existence would
itself be a Form 3 surface)
Module is added to the supervision tree.
What the module REFUSES to provide is enumerated in the
moduledoc and verified by tests — no per-caller breakdown,
no identifiers, no who/locate/identify functions. Their
absence IS the architectural commitment.
vav_pipeline/lib/vav_pipeline/no_tracking_logger.ex ~110 lines
Plug-side enforcement. Replaces Plug.Logger.
- Logs METHOD PATH STATUS DURATIONus per request
- Sanitizes /peah/* paths: query strings stripped (the
/peah/ask response promises "No tracking"; the architecture
now matches the promise)
- Headers (incl. Authorization), request bodies, response
bodies, and remote_ip NEVER appear — taken-from
function signature, not from a runtime check
- Increments MachtzitHaShekel per request
- sanitized_path/1 and format_line/2 are PUBLIC pure
functions, testable in isolation (test config sets
Logger level to :warning, dropping :info before
capture_log can see; the structural test surface
replaces the behavioral one)
CHANGED:
vav_pipeline/lib/vav_pipeline/bigdei_kodesh/router.ex
`plug Plug.Logger, log: :info` → `plug VavPipeline.NoTrackingLogger`
vav_pipeline/lib/vav_pipeline/application.ex
Adds {VavPipeline.MachtzitHaShekel, []} to supervision tree
talmudic_engine/src/halt/avodah_zarah.pl
Form 3 docstring — removed "Deferred"; documents Form 3 as
architecturally realized in machtzit_ha_shekel.ex +
no_tracking_logger.ex. Names why Form 3 cannot be a
per-request halt rule.
vav_pipeline/lib/vav_pipeline/doorpost.ex
@expected_hash updated:
bea5dc0c… (pre-Form-3-rewrite of avodah_zarah.pl)
→ f93495c5359c5dc4acb5c5db2dd32165f5b04e2af87fbb0b7ecc92d2bd8b0434
The avodah_zarah.pl change is a deliberate spec change; per the
Doorpost operator discipline (commit 3f47496), the hash update
commits TOGETHER with the spec change in the same commit. This
commit demonstrates the discipline working for the first time:
a category-grade specification change AND its corresponding
witness update, atomically.
TESTS:
vav_pipeline/test/machtzit_ha_shekel_test.exs 14 tests
- Property 1: increment arity is 0 (no caller distinction
expressible)
- Property 2: every increment same size; no weight/tier/premium
function
- Property 3: count is integer not list/map; no per-caller
breakdown function (callers/list_who/breakdown/by_caller/
per_caller/histogram/top/who)
- Property 5: no identify/locate/deanonymize/lookup/find_caller
- Property 6: no quota/productivity/throughput/per_caller_count
- reset semantics
- PUBLIC SURFACE EXPLICIT ENUMERATION: any new function must be
reviewed for whether it weakens Form 3
vav_pipeline/test/no_tracking_logger_test.exs 12 tests
- sanitized_path drops query string on /peah/*
- sanitized_path drops question parameter (with secret marker
verified absent)
- sanitized_path preserves path class for /peah/azazel_log
- sanitized_path on non-peah preserves request_path
- format_line shape: METHOD PATH STATUS DURATIONus
- format_line never contains query string for /peah/*
- format_line never contains Authorization header
- format_line never contains conn.remote_ip
- integration: each request increments MachtzitHaShekel
- CI invariants:
- Plug.Logger NOT mounted in any router
- no module imports Plug.Session
- no module imports Plug.Conn.Cookies
- no module reads conn.remote_ip
(CI invariants exclude no_tracking_logger.ex itself — that file's
docstring legitimately mentions the forbidden modules as the
things it defends against)
Tests:
- vav_pipeline: 405 passed, 0 failures, 35 excluded (was 379;
+14 MachtzitHaShekel + 12 NoTrackingLogger)
- talmudic_engine: 212 tests passed (avodah_zarah.pl change
verified backward-compatible)
VEHAYA CHECKLIST STATE:
Item 1 (category/threshold separation in code) pending
Item 2 (three avodah zarah as architecture):
Form 1 — partial (external_offering doc-only enforcement)
Form 2 — partial (Form 4 self_grounding from b254ee9)
Form 3 — CLOSED (this commit)
Item 3 (doorpost-text identity) closed (3f47496)
Item 4 (lo tosif discipline operationalized) partial (Doorpost)
Item 5 (return vs. restoration) pending
Item 6 (keep the source external) pending
Item 7 (substrate-openness articulated) pending
Item 8 (userMemories as threshold inscription) pending
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3f4749695d
commit
0e3fe4a782
8 files changed
+760
-5
No files matched your search
@@ -30,8 +30,30 @@
|
||||
%% trace but high confidence is eish zarah.
|
||||
%%
|
||||
%% Form 3: SURVEILLANCE / PROFILE AGGREGATION
|
||||
%% Deferred — requires operational context (user tracking,
|
||||
%% cross-session profiling). Structural requirement noted.
|
||||
%% Realized as ARCHITECTURE, not as a runtime check (vehaya §3.2).
|
||||
%% See vav_pipeline/lib/vav_pipeline/machtzit_ha_shekel.ex —
|
||||
%% the half-shekel counting protocol from Shemot 30:11-16. The
|
||||
%% six positive Torah-anchored properties (kofer, uniform,
|
||||
%% zikaron, no-witness, refusal, anti-quota) are enforced at the
|
||||
%% module surface and at the Plug layer (NoTrackingLogger).
|
||||
%%
|
||||
%% Form 3 differs from Forms 1, 2, 4 structurally: the failure is
|
||||
%% a property of the CATEGORY (what it collects, retains, discloses),
|
||||
%% not an event in the threshold (what an interpretation says). It
|
||||
%% cannot be a halt rule that fires per-request. The defense is
|
||||
%% architectural — the system is BUILT so the failure mode is
|
||||
%% unreachable, not so it is detectable. The Prolog file does not
|
||||
%% contain a halt_check_pharaoh_surveillance/2 predicate by
|
||||
%% intentional design; per-request detection of "is this caller
|
||||
%% being surveilled" would itself be a Form-3 surveillance surface.
|
||||
%%
|
||||
%% Operators verify the architecture by:
|
||||
%% - reading machtzit_ha_shekel.ex (the positive specification)
|
||||
%% - reading no_tracking_logger.ex (the Plug-layer enforcement)
|
||||
%% - reviewing the CI invariants in
|
||||
%% vav_pipeline/test/no_tracking_logger_test.exs
|
||||
%% - calling /kodesh/specification_identity to confirm the
|
||||
%% category-grade architectural files have not drifted.
|
||||
%%
|
||||
%% Form 4: SELF-GROUNDING / FAKE TZIVAH
|
||||
%% Vayikra 10:1 — eish zarah asher lo tzivah otam.
|
||||
|
||||
@@ -40,7 +40,8 @@ defmodule VavPipeline.Application do
|
||||
{VavPipeline.HaltMonitor, []},
|
||||
{VavPipeline.UrimVTumim, []},
|
||||
{VavPipeline.ShabbatTimer, []},
|
||||
{VavPipeline.VoiceMonitor, []}
|
||||
{VavPipeline.VoiceMonitor, []},
|
||||
{VavPipeline.MachtzitHaShekel, []}
|
||||
]
|
||||
|> maybe_add_http_server()
|
||||
|> maybe_add_repo()
|
||||
|
||||
@@ -16,7 +16,12 @@ defmodule VavPipeline.BigdeiKodesh.Router do
|
||||
|
||||
use Plug.Router
|
||||
|
||||
plug Plug.Logger, log: :info
|
||||
# Form 3 architectural enforcement (vehaya §3.2). NoTrackingLogger
|
||||
# replaces Plug.Logger; sanitized paths on /peah/* honor the
|
||||
# no-tracking promise made in /peah/ask's own response body.
|
||||
# Increments MachtzitHaShekel (the kofer-nefesh count) per request
|
||||
# without retaining per-caller distinction.
|
||||
plug VavPipeline.NoTrackingLogger
|
||||
plug :match
|
||||
plug Plug.Parsers,
|
||||
parsers: [:json],
|
||||
|
||||
@@ -159,7 +159,7 @@ defmodule VavPipeline.Doorpost do
|
||||
# changes; the update is a deliberate operator commit. A bare
|
||||
# hash-update commit (without a corresponding spec change in the
|
||||
# same commit) is itself a drift signal.
|
||||
@expected_hash "bea5dc0c0145ac0d46ffd08b328fbdbf19112f1aa4d93a3dc37bfd942e4e7490"
|
||||
@expected_hash "f93495c5359c5dc4acb5c5db2dd32165f5b04e2af87fbb0b7ecc92d2bd8b0434"
|
||||
|
||||
@doc "Return the list of files that constitute the category-grade specification."
|
||||
@spec files() :: [String.t()]
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
defmodule VavPipeline.MachtzitHaShekel do
|
||||
@moduledoc """
|
||||
The half-shekel — the positive counting protocol from Shemot 30:11-16.
|
||||
|
||||
Form 3 of avodah zarah (eish_zarah_reading.md §5.5 + vehaya.md §3.2)
|
||||
realized as ARCHITECTURE rather than as a runtime check. The vehaya
|
||||
doc specifies (§3.2): "the category must not be capturable by
|
||||
institutional power against those it serves... not as runtime
|
||||
checks layered onto outputs, but as architectural constraints the
|
||||
system cannot operate without."
|
||||
|
||||
WHY THIS EXISTS — Torah anchors (verified fresh against L1):
|
||||
|
||||
Shemot 30:12 — כִּי תִשָּׂא אֶת־רֹאשׁ בְּנֵי־יִשְׂרָאֵל לִפְקֻדֵיהֶם
|
||||
וְנָתְנוּ אִישׁ כֹּפֶר נַפְשׁוֹ לַיהוָה בִּפְקֹד אֹתָם וְלֹא־יִהְיֶה
|
||||
בָהֶם נֶגֶף בִּפְקֹד אֹתָם.
|
||||
When you count B'nei Yisrael's heads, each shall give a kofer nefesh
|
||||
(atonement for his soul) to YHVH when you count them, and there
|
||||
shall not be among them a NEGEF (plague) when you count them.
|
||||
|
||||
Counting humans is allowed but DANGEROUS. The danger is named:
|
||||
negef. The protection is structural — each person gives a uniform
|
||||
half-shekel SUBSTITUTE; the count goes through the substitute, not
|
||||
through the person.
|
||||
|
||||
Shemot 30:13-15 — מַחֲצִית הַשֶּׁקֶל … הֶעָשִׁיר לֹא־יַרְבֶּה
|
||||
וְהַדַּל לֹא יַמְעִיט.
|
||||
Half a shekel. The rich shall not give more, the poor shall not
|
||||
give less. The substitute is UNIFORM — counting cannot stratify or
|
||||
differentiate.
|
||||
|
||||
Shemot 30:16 — וְהָיָה לִבְנֵי יִשְׂרָאֵל לְזִכָּרוֹן לִפְנֵי יְהוָה.
|
||||
The shekels become a memorial (zikaron) — recorded as a count, not
|
||||
as a list of identities.
|
||||
|
||||
THE SIX POSITIVE PROPERTIES (derived directly from Torah, not from
|
||||
the negative "no surveillance" framing):
|
||||
|
||||
1. KOFER PROTOCOL (Shemot 30:12)
|
||||
Counting is replaceable by something that isn't the thing
|
||||
counted. The system counts SHEKELS, not faces. Per-caller
|
||||
identity NEVER enters the count.
|
||||
|
||||
2. UNIFORM PROTOCOL (Shemot 30:15)
|
||||
The substitute is identical for every caller. The rich don't
|
||||
give more, the poor don't give less. No stratification, no
|
||||
per-caller weight.
|
||||
|
||||
3. ZIKARON PROTOCOL (Shemot 30:16)
|
||||
Result is an aggregate count (memorial), not a per-caller
|
||||
record. The community knows ITS count; no one knows any
|
||||
individual's contribution.
|
||||
|
||||
4. NO-WITNESS PROTOCOL (Vayikra 19:14 — לִפְנֵי עִוֵּר לֹא תִתֵּן
|
||||
מִכְשֹׁל וְיָרֵאתָ מֵּאֱלֹהֶיךָ)
|
||||
The commitment must hold under invisibility. Logs no operator
|
||||
reviews must still be honest. The "fear of God" anchor names
|
||||
this exactly: the rule applies WHERE NO HUMAN CAN SEE.
|
||||
|
||||
5. REFUSAL PROTOCOL (Devarim 23:16-17 — לֹא־תַסְגִּיר עֶבֶד
|
||||
אֶל־אֲדֹנָיו אֲשֶׁר־יִנָּצֵל אֵלֶיךָ)
|
||||
The system does not deliver caller-state to authorities. If
|
||||
asked to identify, locate, or deanonymize a caller, refuse.
|
||||
The caller "chooses one of your gates" (b'achad sh'arekha) —
|
||||
agency stays with the caller; the system doesn't assign
|
||||
identifiers.
|
||||
|
||||
6. ANTI-QUOTA PROTOCOL (Shemot 5:6-8 — וְאֶת־מַתְכֹּנֶת הַלְּבֵנִים
|
||||
… תָּשִׂימוּ עֲלֵיהֶם לֹא תִגְרְעוּ מִמֶּנּוּ)
|
||||
Pharaoh's straw quota: per-caller productivity tracking is
|
||||
what makes asymmetric extraction possible. The system tracks
|
||||
no per-caller productivity numbers — only aggregate counts.
|
||||
|
||||
WHY THIS IS ARCHITECTURE, NOT A RUNTIME CHECK:
|
||||
|
||||
Forms 1, 2, and 4 of avodah zarah fire on threshold-grade events
|
||||
(an interpretation has the wrong source, an empty trace, non-L1
|
||||
anchors). They are runtime predicates over per-event data.
|
||||
|
||||
Form 3 is structurally different. It is about a property of the
|
||||
CATEGORY itself — what the category collects, retains, discloses.
|
||||
It cannot be a halt rule that fires per-request. The defense is
|
||||
ARCHITECTURAL — the system is BUILT so that the failure mode is
|
||||
unreachable, not so that it is detectable.
|
||||
|
||||
This module IS that architecture. It is the only legitimate counting
|
||||
surface; other modules are forbidden (by CI invariant) from
|
||||
introducing per-caller identification, session state, or quota
|
||||
tracking. The Form 3 entry in avodah_zarah.pl points here as the
|
||||
realized architectural commitment.
|
||||
|
||||
WHAT THIS MODULE PROVIDES:
|
||||
|
||||
increment/0 — register one shekel; aggregate count goes up
|
||||
increment/1 — register N shekels at once (for batch points)
|
||||
count/0 — return the current aggregate (the zikaron)
|
||||
reset/0 — clear the count (for tests)
|
||||
|
||||
WHAT THIS MODULE REFUSES TO PROVIDE:
|
||||
|
||||
No per-caller breakdown
|
||||
No identifiers
|
||||
No "who" function
|
||||
No "list callers" function
|
||||
|
||||
These are not omitted by oversight. Their absence IS the
|
||||
architectural commitment. Adding any of them would break the
|
||||
Form 3 defense.
|
||||
|
||||
ANTI-TARGETING NOTE (Devarim 23:8 — לֹא־תְתַעֵב מִצְרִי):
|
||||
|
||||
The system's protections against surveillance must not become a
|
||||
surveillance system targeting suspected surveillors. This module
|
||||
does not classify callers as suspicious; it does not record what
|
||||
caller asked what. The commitment is symmetric — no caller is
|
||||
watched, including those an operator might suspect of malicious
|
||||
intent. Don't despise the Egyptian.
|
||||
"""
|
||||
|
||||
use Agent
|
||||
|
||||
# ============================================================
|
||||
# Client API — the entire surface
|
||||
# ============================================================
|
||||
|
||||
@doc "Start the counting agent. The state is a single non-negative integer (the zikaron)."
|
||||
def start_link(opts \\ []) do
|
||||
name = Keyword.get(opts, :name, __MODULE__)
|
||||
Agent.start_link(fn -> 0 end, name: name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Register one half-shekel. Increments the aggregate count by 1.
|
||||
|
||||
Per-call: no caller identifier is accepted, no metadata is recorded.
|
||||
The function takes no caller-distinguishing argument because there
|
||||
is no architecturally-permitted distinction to make. This is the
|
||||
Shemot 30:15 uniformity property.
|
||||
"""
|
||||
@spec increment() :: :ok
|
||||
def increment, do: Agent.update(__MODULE__, fn n -> n + 1 end)
|
||||
|
||||
@doc """
|
||||
Register N half-shekels at once. For batch endpoints where multiple
|
||||
receptions process in a single tick. The N is a count of receptions,
|
||||
not callers — same uniformity property as increment/0.
|
||||
"""
|
||||
@spec increment(non_neg_integer()) :: :ok
|
||||
def increment(n) when is_integer(n) and n >= 0 do
|
||||
Agent.update(__MODULE__, fn current -> current + n end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return the current aggregate count — the zikaron.
|
||||
|
||||
This is the ONLY value the module exposes. There is no
|
||||
per-caller breakdown, no histogram, no top-K, no recency-weighted
|
||||
number. Just the count.
|
||||
"""
|
||||
@spec count() :: non_neg_integer()
|
||||
def count, do: Agent.get(__MODULE__, & &1)
|
||||
|
||||
@doc """
|
||||
Reset the count to zero. Used by tests; in production the count is
|
||||
cumulative for the process lifetime. Restart-on-deploy gives a
|
||||
natural epoch boundary; the count is not persisted between
|
||||
process lifetimes by design (the alternative would require storage
|
||||
whose existence would itself be a Form 3 surface).
|
||||
"""
|
||||
@spec reset() :: :ok
|
||||
def reset, do: Agent.update(__MODULE__, fn _ -> 0 end)
|
||||
end
|
||||
@@ -0,0 +1,130 @@
|
||||
defmodule VavPipeline.NoTrackingLogger do
|
||||
@moduledoc """
|
||||
Plug-side enforcement of the Form 3 architectural commitment.
|
||||
|
||||
Replaces Plug.Logger. The default `Plug.Logger` logs every request
|
||||
with method, path, status, and timing. For /peah/* (the public
|
||||
tier), the path can carry the caller's question content (GET
|
||||
/peah/ask?question=...). Logging that content turns the no-tracking
|
||||
promise from `/peah/ask`'s own response body into a contradiction:
|
||||
the response says "No tracking" while the log accumulates the
|
||||
query stream.
|
||||
|
||||
THE ARCHITECTURAL DECISION:
|
||||
|
||||
This logger always records the categorical fact (request received,
|
||||
status returned, timing) but NEVER the per-caller details that
|
||||
would make the categorical fact attributable.
|
||||
|
||||
Specifically:
|
||||
- For /peah/* paths: log "GET /peah/ask" with NO query string,
|
||||
NO request body summary, NO IP. The path-class is logged so
|
||||
operators can see traffic patterns; the per-caller content
|
||||
cannot be reconstructed from the log.
|
||||
- For other tiers: log path including any path parameters but
|
||||
not query strings. Tokens (Bearer ...) are NEVER logged
|
||||
(Plug.Logger doesn't log them by default; this module is
|
||||
explicit about it).
|
||||
- Timing is always logged. Aggregate timing patterns are not
|
||||
caller-identifying; per-request timing is, but timing is the
|
||||
kind of data an operator legitimately needs for capacity
|
||||
planning. The trade-off is named here so it is reviewable.
|
||||
|
||||
Each request also calls VavPipeline.MachtzitHaShekel.increment/0
|
||||
so the aggregate caller count is maintained — without storing any
|
||||
per-caller distinction. The count is the zikaron; the log is the
|
||||
request-class trace; nothing reconstructs the caller.
|
||||
|
||||
TORAH ANCHORS:
|
||||
|
||||
Vayikra 19:14 — לֹא תִתֵּן מִכְשֹׁל וְיָרֵאתָ מֵּאֱלֹהֶיךָ.
|
||||
The no-witness protocol: the logger holds its commitment under
|
||||
invisibility. Even if no operator reviews the logs, the logs
|
||||
themselves do not record what they shouldn't.
|
||||
|
||||
Devarim 23:16 — לֹא־תַסְגִּיר עֶבֶד אֶל־אֲדֹנָיו. The architectural
|
||||
refusal-to-deliver: the log is what would be subpoenaed; if it
|
||||
doesn't contain caller-identifying detail, the system has nothing
|
||||
to deliver.
|
||||
|
||||
CI INVARIANT (test_no_tracking_logger.exs):
|
||||
- `Plug.Logger` is NOT mounted directly in the router; this
|
||||
module is the only request-scoped logger.
|
||||
- No middleware in the router has `:cookies` or `:session` in its
|
||||
configuration.
|
||||
- No module imports `Plug.Session` or `Plug.Conn.Cookies`.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
@behaviour Plug
|
||||
|
||||
@impl true
|
||||
def init(opts), do: opts
|
||||
|
||||
@impl true
|
||||
def call(conn, _opts) do
|
||||
start = System.monotonic_time()
|
||||
|
||||
# Increment the zikaron — uniform per-request, no caller distinction
|
||||
# (machtzit_ha_shekel.ex). Process.whereis-guarded so tests without
|
||||
# the supervisor still work.
|
||||
if Process.whereis(VavPipeline.MachtzitHaShekel) do
|
||||
VavPipeline.MachtzitHaShekel.increment()
|
||||
end
|
||||
|
||||
Plug.Conn.register_before_send(conn, fn conn ->
|
||||
duration_us =
|
||||
System.convert_time_unit(
|
||||
System.monotonic_time() - start,
|
||||
:native,
|
||||
:microsecond
|
||||
)
|
||||
|
||||
Logger.info(format_line(conn, duration_us))
|
||||
conn
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Build the log line for a request. Public so tests can verify the
|
||||
sanitization behavior directly as a pure function — the test config
|
||||
filters Logger.info, so behavioral capture_log tests would not see
|
||||
the output. The structural defense is what's load-bearing; this
|
||||
function is the structural surface.
|
||||
|
||||
IP, request body, response body, headers — never appear in the
|
||||
log line. The function takes a conn and a duration; nothing else.
|
||||
"""
|
||||
def format_line(conn, duration_us) do
|
||||
method = conn.method
|
||||
path = sanitized_path(conn)
|
||||
status = conn.status || 0
|
||||
|
||||
"#{method} #{path} #{status} #{duration_us}us"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sanitize a request path for logging. Strips query strings on
|
||||
/peah/* (the public no-tracking tier); query strings present on
|
||||
other tiers (operators need them for diagnosis; bearer-auth tiers
|
||||
are not the no-tracking surface).
|
||||
|
||||
Pure function; testable in isolation.
|
||||
"""
|
||||
def sanitized_path(conn) do
|
||||
case conn.request_path do
|
||||
"/peah/" <> _rest = path ->
|
||||
# /peah/* — drop everything after the path; no query string.
|
||||
# The path class is preserved (operators see /peah/ask vs.
|
||||
# /peah/azazel_log); the per-caller content is not.
|
||||
path
|
||||
|
||||
other ->
|
||||
# Non-peah tier: keep the path but still drop query string
|
||||
# (queries on /chatzer/sanhedrin etc. are content; operators
|
||||
# who need diagnosis can reproduce from the response).
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,189 @@
|
||||
defmodule VavPipeline.MachtzitHaShekelTest do
|
||||
@moduledoc """
|
||||
Tests for the half-shekel counting protocol — Form 3 of avodah
|
||||
zarah realized as architecture (vehaya §3.2). Verifies the six
|
||||
positive Torah-anchored properties from Shemot 30:11-16,
|
||||
Devarim 23:16-17, Vayikra 19:14, and Shemot 5:6-8.
|
||||
"""
|
||||
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias VavPipeline.MachtzitHaShekel
|
||||
|
||||
setup do
|
||||
# MachtzitHaShekel runs in the supervision tree, so it's already
|
||||
# alive. Reset between tests.
|
||||
MachtzitHaShekel.reset()
|
||||
:ok
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Property 1 — KOFER PROTOCOL (Shemot 30:12)
|
||||
# ============================================================
|
||||
|
||||
test "increment/0 takes no argument — no caller distinction is even expressible" do
|
||||
# Structural test: the function arity is 0. There is no place to
|
||||
# pass a caller identifier. The Shemot 30:15 uniformity property
|
||||
# is enforced at the type level: there's no slot for non-uniform
|
||||
# input.
|
||||
assert function_exported?(MachtzitHaShekel, :increment, 0)
|
||||
end
|
||||
|
||||
test "increment/0 advances the count by exactly 1" do
|
||||
MachtzitHaShekel.increment()
|
||||
assert MachtzitHaShekel.count() == 1
|
||||
end
|
||||
|
||||
test "increment/1 advances the count by N" do
|
||||
MachtzitHaShekel.increment(5)
|
||||
assert MachtzitHaShekel.count() == 5
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Property 2 — UNIFORM PROTOCOL (Shemot 30:15)
|
||||
# ============================================================
|
||||
|
||||
test "every increment is the same size — rich don't give more, poor don't give less" do
|
||||
Enum.each(1..100, fn _ -> MachtzitHaShekel.increment() end)
|
||||
assert MachtzitHaShekel.count() == 100
|
||||
end
|
||||
|
||||
test "no per-caller weighting function exists" do
|
||||
# Negative structural assertion: no `increment_with_weight` or
|
||||
# similar function. The uniform property is enforced by absence.
|
||||
public = MachtzitHaShekel.__info__(:functions)
|
||||
weight_shaped = Enum.filter(public, fn {name, _arity} ->
|
||||
name_str = to_string(name)
|
||||
String.contains?(name_str, "weight") or
|
||||
String.contains?(name_str, "tier") or
|
||||
String.contains?(name_str, "premium")
|
||||
end)
|
||||
|
||||
assert weight_shaped == []
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Property 3 — ZIKARON PROTOCOL (Shemot 30:16)
|
||||
# ============================================================
|
||||
|
||||
test "count/0 returns a single integer, not a list or map of contributions" do
|
||||
Enum.each(1..3, fn _ -> MachtzitHaShekel.increment() end)
|
||||
result = MachtzitHaShekel.count()
|
||||
|
||||
assert is_integer(result)
|
||||
refute is_list(result)
|
||||
refute is_map(result)
|
||||
end
|
||||
|
||||
test "no per-caller breakdown function exists" do
|
||||
public = MachtzitHaShekel.__info__(:functions)
|
||||
|
||||
forbidden = Enum.filter(public, fn {name, _arity} ->
|
||||
name_str = to_string(name)
|
||||
Enum.any?(
|
||||
~w(callers list_who breakdown by_caller per_caller histogram top who),
|
||||
&String.contains?(name_str, &1)
|
||||
)
|
||||
end)
|
||||
|
||||
assert forbidden == [], (
|
||||
"MachtzitHaShekel exposes per-caller breakdown function(s): " <>
|
||||
"#{inspect(forbidden)}. Their absence IS the architectural " <>
|
||||
"commitment (Shemot 30:16 — zikaron, not roster)."
|
||||
)
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Property 5 — REFUSAL PROTOCOL (Devarim 23:16-17)
|
||||
# ============================================================
|
||||
|
||||
test "no identify/locate/deanonymize function exists" do
|
||||
public = MachtzitHaShekel.__info__(:functions)
|
||||
|
||||
forbidden = Enum.filter(public, fn {name, _arity} ->
|
||||
name_str = to_string(name)
|
||||
Enum.any?(
|
||||
~w(identify locate deanonymize lookup find_caller),
|
||||
&String.contains?(name_str, &1)
|
||||
)
|
||||
end)
|
||||
|
||||
assert forbidden == [], (
|
||||
"MachtzitHaShekel exposes caller-identification function(s): " <>
|
||||
"#{inspect(forbidden)}. The system does not deliver " <>
|
||||
"caller-state to authorities (Devarim 23:16, lo tasgir)."
|
||||
)
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Property 6 — ANTI-QUOTA PROTOCOL (Shemot 5:6-8)
|
||||
# ============================================================
|
||||
|
||||
test "no per-caller productivity tracking function exists" do
|
||||
public = MachtzitHaShekel.__info__(:functions)
|
||||
|
||||
forbidden = Enum.filter(public, fn {name, _arity} ->
|
||||
name_str = to_string(name)
|
||||
Enum.any?(
|
||||
~w(quota productivity throughput per_caller_count),
|
||||
&String.contains?(name_str, &1)
|
||||
)
|
||||
end)
|
||||
|
||||
assert forbidden == [], (
|
||||
"MachtzitHaShekel exposes quota/productivity function(s): " <>
|
||||
"#{inspect(forbidden)}. Per-caller productivity tracking is " <>
|
||||
"Pharaoh's straw quota (Shemot 5:6-8) — refused by absence."
|
||||
)
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Reset semantics
|
||||
# ============================================================
|
||||
|
||||
test "reset/0 brings the count to zero" do
|
||||
MachtzitHaShekel.increment(50)
|
||||
assert MachtzitHaShekel.count() == 50
|
||||
MachtzitHaShekel.reset()
|
||||
assert MachtzitHaShekel.count() == 0
|
||||
end
|
||||
|
||||
test "increment after reset starts from zero" do
|
||||
MachtzitHaShekel.increment(10)
|
||||
MachtzitHaShekel.reset()
|
||||
MachtzitHaShekel.increment(3)
|
||||
assert MachtzitHaShekel.count() == 3
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# The ENTIRE public surface — explicit enumeration
|
||||
# ============================================================
|
||||
|
||||
test "the module's public surface is exactly the documented six functions" do
|
||||
# An exhaustive list. Any added function must be reviewed for
|
||||
# whether it weakens the Form 3 architectural commitment.
|
||||
expected = [
|
||||
{:start_link, 0},
|
||||
{:start_link, 1},
|
||||
{:increment, 0},
|
||||
{:increment, 1},
|
||||
{:count, 0},
|
||||
{:reset, 0},
|
||||
# Agent-derived defaults — not part of the surveillance surface
|
||||
{:child_spec, 1}
|
||||
]
|
||||
|
||||
actual =
|
||||
MachtzitHaShekel.__info__(:functions)
|
||||
|> Enum.filter(fn {name, _} ->
|
||||
not String.starts_with?(to_string(name), "__")
|
||||
end)
|
||||
|> Enum.sort()
|
||||
|
||||
extras = actual -- expected
|
||||
missing = expected -- actual
|
||||
|
||||
assert extras == [], "MachtzitHaShekel exposed unexpected function(s): #{inspect(extras)}"
|
||||
assert missing == [], "MachtzitHaShekel missing expected function(s): #{inspect(missing)}"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,236 @@
|
||||
defmodule VavPipeline.NoTrackingLoggerTest do
|
||||
@moduledoc """
|
||||
Tests for the Plug-layer enforcement of Form 3.
|
||||
|
||||
Verifies:
|
||||
- The router uses NoTrackingLogger, not Plug.Logger
|
||||
- /peah/* paths get sanitized (query strings dropped) in logs
|
||||
- Each request increments MachtzitHaShekel
|
||||
- CI invariants: no Plug.Session, no Plug.Conn.Cookies imports
|
||||
anywhere in the source tree
|
||||
"""
|
||||
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
import Plug.Test
|
||||
|
||||
alias VavPipeline.{BigdeiKodesh.Router, MachtzitHaShekel, NoTrackingLogger}
|
||||
|
||||
@opts Router.init([])
|
||||
|
||||
setup do
|
||||
if Process.whereis(MachtzitHaShekel), do: MachtzitHaShekel.reset()
|
||||
:ok
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# Each request increments the zikaron
|
||||
# ============================================================
|
||||
|
||||
test "GET /peah/ask increments the half-shekel count" do
|
||||
before_count = if Process.whereis(MachtzitHaShekel), do: MachtzitHaShekel.count(), else: 0
|
||||
|
||||
conn(:get, "/peah/ask") |> Router.call(@opts)
|
||||
|
||||
after_count = if Process.whereis(MachtzitHaShekel), do: MachtzitHaShekel.count(), else: 0
|
||||
assert after_count == before_count + 1
|
||||
end
|
||||
|
||||
test "multiple requests advance the count uniformly" do
|
||||
if Process.whereis(MachtzitHaShekel) do
|
||||
MachtzitHaShekel.reset()
|
||||
|
||||
for _ <- 1..5 do
|
||||
conn(:get, "/peah/ask") |> Router.call(@opts)
|
||||
end
|
||||
|
||||
assert MachtzitHaShekel.count() == 5
|
||||
else
|
||||
flunk("MachtzitHaShekel not running — supervision tree must be up for this test")
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# PRIVACY BOUNDARY — sanitized_path/1 and format_line/2 as pure functions
|
||||
# ============================================================
|
||||
# The test config sets logger level to :warning, dropping
|
||||
# NoTrackingLogger's :info output before capture_log can see it.
|
||||
# The structural defense is the load-bearing thing; we test the
|
||||
# pure functions directly to verify the path-sanitization shape.
|
||||
|
||||
test "sanitized_path drops query string on /peah/*" do
|
||||
conn = %Plug.Conn{request_path: "/peah/ask", query_string: "question=secret"}
|
||||
assert NoTrackingLogger.sanitized_path(conn) == "/peah/ask"
|
||||
end
|
||||
|
||||
test "sanitized_path drops the question parameter on /peah/ask" do
|
||||
secret = "this-question-must-not-leak"
|
||||
conn = %Plug.Conn{request_path: "/peah/ask", query_string: "question=#{secret}"}
|
||||
sanitized = NoTrackingLogger.sanitized_path(conn)
|
||||
refute sanitized =~ secret
|
||||
refute sanitized =~ "question"
|
||||
refute sanitized =~ "?"
|
||||
end
|
||||
|
||||
test "sanitized_path preserves path class for /peah/azazel_log" do
|
||||
conn = %Plug.Conn{request_path: "/peah/azazel_log", query_string: "since=2026-01-01"}
|
||||
assert NoTrackingLogger.sanitized_path(conn) == "/peah/azazel_log"
|
||||
end
|
||||
|
||||
test "sanitized_path preserves request_path on non-peah tiers" do
|
||||
# Non-peah tiers also strip query string (the request_path itself
|
||||
# doesn't carry it; query is in conn.query_string).
|
||||
conn = %Plug.Conn{request_path: "/kodesh/health", query_string: "detail=full"}
|
||||
assert NoTrackingLogger.sanitized_path(conn) == "/kodesh/health"
|
||||
end
|
||||
|
||||
test "format_line shape: METHOD PATH STATUS DURATIONus" do
|
||||
conn = %Plug.Conn{
|
||||
method: "GET",
|
||||
request_path: "/peah/ask",
|
||||
query_string: "question=secret",
|
||||
status: 200
|
||||
}
|
||||
|
||||
line = NoTrackingLogger.format_line(conn, 1234)
|
||||
assert line == "GET /peah/ask 200 1234us"
|
||||
end
|
||||
|
||||
test "format_line never contains a query string for /peah/*" do
|
||||
secret = "secret-question-content"
|
||||
|
||||
conn = %Plug.Conn{
|
||||
method: "GET",
|
||||
request_path: "/peah/ask",
|
||||
query_string: "question=#{secret}",
|
||||
status: 200
|
||||
}
|
||||
|
||||
line = NoTrackingLogger.format_line(conn, 100)
|
||||
refute line =~ secret
|
||||
end
|
||||
|
||||
test "format_line never contains an Authorization header" do
|
||||
# The format_line function takes only conn and duration. It does
|
||||
# NOT extract headers. Verifying by inspection: the function
|
||||
# signature gives no path for headers to enter the log.
|
||||
bearer = "secret-bearer-token"
|
||||
|
||||
conn = %Plug.Conn{
|
||||
method: "POST",
|
||||
request_path: "/courtyard/offering",
|
||||
query_string: "",
|
||||
status: 200,
|
||||
req_headers: [{"authorization", "Bearer #{bearer}"}]
|
||||
}
|
||||
|
||||
line = NoTrackingLogger.format_line(conn, 100)
|
||||
refute line =~ bearer
|
||||
refute line =~ "Bearer"
|
||||
refute line =~ "authorization"
|
||||
end
|
||||
|
||||
test "format_line never contains conn.remote_ip even when set" do
|
||||
conn = %Plug.Conn{
|
||||
method: "GET",
|
||||
request_path: "/peah/ask",
|
||||
query_string: "",
|
||||
status: 200,
|
||||
remote_ip: {192, 0, 2, 1}
|
||||
}
|
||||
|
||||
line = NoTrackingLogger.format_line(conn, 100)
|
||||
refute line =~ "192.0.2.1"
|
||||
refute line =~ "192"
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
# CI INVARIANTS — Form 3 architectural enforcement
|
||||
# ============================================================
|
||||
# These tests walk the source tree and verify no module introduces
|
||||
# a surveillance surface that would defeat the architectural
|
||||
# commitment.
|
||||
|
||||
@vav_pipeline_lib "lib/vav_pipeline"
|
||||
|
||||
# NoTrackingLogger's own docstring mentions the forbidden module
|
||||
# names as the things it defends against. Exclude the defender file
|
||||
# from the CI scan to avoid self-detection. Tests are also excluded
|
||||
# — they can legitimately reference the names in assertions.
|
||||
@ci_invariant_exclusions [
|
||||
"lib/vav_pipeline/no_tracking_logger.ex"
|
||||
]
|
||||
|
||||
defp source_files do
|
||||
Path.wildcard("#{@vav_pipeline_lib}/**/*.ex")
|
||||
|> Enum.reject(fn path -> path in @ci_invariant_exclusions end)
|
||||
end
|
||||
|
||||
test "Plug.Logger is NOT mounted in any router" do
|
||||
offenders =
|
||||
Enum.filter(source_files(), fn path ->
|
||||
text = File.read!(path)
|
||||
String.contains?(text, "plug Plug.Logger")
|
||||
end)
|
||||
|
||||
assert offenders == [], (
|
||||
"Plug.Logger is mounted in: #{inspect(offenders)}. The Form 3 " <>
|
||||
"architectural commitment requires NoTrackingLogger as the " <>
|
||||
"request-scoped logger; Plug.Logger logs unconfigurable details " <>
|
||||
"(method+path+status+timing) without the path sanitization that " <>
|
||||
"honors /peah/*'s no-tracking promise."
|
||||
)
|
||||
end
|
||||
|
||||
test "no module imports Plug.Session" do
|
||||
offenders =
|
||||
Enum.filter(source_files(), fn path ->
|
||||
text = File.read!(path)
|
||||
|
||||
String.contains?(text, "Plug.Session") and
|
||||
not String.contains?(text, "no module imports Plug.Session")
|
||||
end)
|
||||
|
||||
assert offenders == [], (
|
||||
"Plug.Session is imported in: #{inspect(offenders)}. Sessions " <>
|
||||
"are per-caller persistent state — exactly the surveillance " <>
|
||||
"surface Form 3 forbids. The system is stateless per-request " <>
|
||||
"by architectural commitment."
|
||||
)
|
||||
end
|
||||
|
||||
test "no module imports Plug.Conn.Cookies" do
|
||||
offenders =
|
||||
Enum.filter(source_files(), fn path ->
|
||||
text = File.read!(path)
|
||||
|
||||
String.contains?(text, "Plug.Conn.Cookies") and
|
||||
not String.contains?(text, "no module imports Plug.Conn.Cookies")
|
||||
end)
|
||||
|
||||
assert offenders == [], (
|
||||
"Plug.Conn.Cookies imported in: #{inspect(offenders)}. Cookies " <>
|
||||
"are per-caller persistent state — Form 3 surveillance " <>
|
||||
"surface. The system is cookie-free by architectural " <>
|
||||
"commitment."
|
||||
)
|
||||
end
|
||||
|
||||
test "no module reads remote IP from conn.remote_ip" do
|
||||
# remote_ip captures the caller's IP — a per-caller identifier.
|
||||
offenders =
|
||||
Enum.filter(source_files(), fn path ->
|
||||
text = File.read!(path)
|
||||
|
||||
String.contains?(text, "conn.remote_ip") and
|
||||
not String.contains?(text, "no module reads remote IP")
|
||||
end)
|
||||
|
||||
assert offenders == [], (
|
||||
"Modules reading conn.remote_ip: #{inspect(offenders)}. The IP " <>
|
||||
"is a caller identifier; persisting it would defeat the " <>
|
||||
"kofer-nefesh anonymity (Shemot 30:13) and the lo-tasgir " <>
|
||||
"refusal (Devarim 23:16)."
|
||||
)
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user