Skip to content

Commit 6dccb47

Browse files
authored
Merge pull request #157 from OrderLab/codex/constructor-receiver-snapshot
Snapshot constructor receivers generally without invoking application getters
2 parents 892ad8d + 98fed50 commit 6dccb47

5 files changed

Lines changed: 1347 additions & 31 deletions

File tree

‎docs/constructor-snapshots.md‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Constructor PRE snapshots
2+
3+
When argument dumping is enabled for a class's `__init__`, TrainCheck records
4+
the receiver before the initializer body runs. The receiver may be empty,
5+
partially initialized by a subclass, or being initialized again. The instrumenter
6+
identifies initializers from their owning class and attribute name; a standalone
7+
function named `__init__` keeps ordinary argument serialization.
8+
9+
The receiver is encoded under its static, fully qualified type name with an
10+
`_TRAINCHECK_CONSTRUCTOR_SNAPSHOT` marker. This is a deliberate change from
11+
ordinary object introspection for **all selected initializers**, including
12+
metaclass initializers. Other arguments and ordinary method calls keep their
13+
existing representation. The function's PRE, POST, and exception records remain.
14+
15+
## Stored-state contract
16+
17+
The marker contains:
18+
19+
| Field | Meaning |
20+
| --- | --- |
21+
| `phase` | `before_init`; this does not claim initialization succeeded. |
22+
| `coverage` | `stored_attributes_only`; computed properties are not captured. |
23+
| `object_id` | Identity of the actual receiver. |
24+
| `dictionary_status` | `captured`, `absent`, or `unavailable` when storage exists but no native accessor remains accessible. |
25+
| `attributes` | Selected entries from the instance dictionary or native class namespace. |
26+
| `slots` | Selected populated native slots, each with its declaring `owner_type`, `name`, and tagged `value`. |
27+
| `unset_slots` | Selected declared slots whose storage has not been assigned. |
28+
| `requested_attributes` | The explicit selector, or `null` for all accessible stored fields. |
29+
| `unavailable_attributes` | Requested names with no captured dictionary entry or populated slot. |
30+
31+
Dictionary entries and slot records are separate. A base slot, a derived slot,
32+
and a dictionary entry can all have the same name and hold different values;
33+
none overwrites another. Slot selectors use the stored name, including Python's
34+
name mangling for private slots. A requested name selects each matching storage
35+
location. An unset location stays explicit even if another matching slot has a
36+
value. An empty selector is distinct from unselected/all-field collection.
37+
38+
Exact built-in scalars retain their values. Nonfinite floats have explicit
39+
`nan`, `+inf`, and `-inf` tags. Other values, including containers and tensors,
40+
are **identity/type references**, not recursive content snapshots or tensor
41+
hashes. Missing storage is never represented as a stored `None` value.
42+
43+
## Avoiding application execution
44+
45+
The snapshot reads native dictionary and slot descriptors from raw class
46+
namespaces. It bypasses receiver `__getattribute__`, `__getattr__`, `__dir__`,
47+
properties, custom descriptors, and custom metaclass lookup/properties. It also
48+
bypasses overridden dictionary iteration methods and custom metaclass equality.
49+
Constructor receivers are excluded from proxy-marker scans and argument
50+
unproxying on this dumping path; ordinary arguments keep those behaviors.
51+
52+
Normal positional construction and explicit keyword-receiver calls are covered.
53+
If a native callable exposes no signature, its positional receiver is supported;
54+
no keyword receiver name is guessed. Missing-receiver errors are produced by
55+
the original callable and retain an exception record.
56+
57+
Native extension state without a recognized storage descriptor, replaced slot
58+
descriptors, and computed attributes are outside this snapshot's coverage.
59+
Dictionary storage hidden behind a replacement property is marked unavailable
60+
unless an inherited native accessor still exposes it. Snapshots are synchronous,
61+
do not cache application metadata, and do not promise an atomic view during
62+
concurrent mutation of the same object's storage.
63+
64+
Existing invariants that depended on introspected constructor receiver fields
65+
need recollection and activation validation against this representation. A
66+
requested property is explicitly unavailable, not a successfully observed value.

0 commit comments

Comments
 (0)