Skip to content

Latest commit

 

History

History
444 lines (331 loc) · 20.8 KB

File metadata and controls

444 lines (331 loc) · 20.8 KB

Practify Protocol v0.3

语言无关的代码验证协议规范

"任何声称都必须有可验证的实践锚点。"

Status: Working Draft. Components are at different maturity levels — see Maturity.


Changelog from v0.1

Change Trigger Section
Stub-based uninstall — zero-dependency removal via pract_stub.py photo_screener trial: "卸载后代码还能跑吗?" 2. Uninstall Guarantee
Missing anchor severity layering — I/O functions get INFO, not WARNING photo_screener trial: 21 warnings, many on load_and_preprocess etc. 6.1 Severity Layering
Anchor registry interop — scanner recognizes out-of-line anchors photo_screener trial: pract_anchors.py invisible to scanner 6.2 Registry-Aware Scanning
@i_dont_know staleness — auto-escalate after 90 days 待补充260607: 噪声即课题; unverified unknowns rot 5.3 Staleness Detection

Changelog from v0.2

Change Trigger Section
Source provenance — @pt anchors MUST carry source field RE Framework integration: anchors without trace provenance are unfalsifiable 5.1 Test Anchor, 5.5 Source Provenance
Degraded verification — three operating modes for RE (full/partial/degraded) RE Framework: lifted .cpp often can't compile standalone 9. Degraded Verification
Uncompilable anchor state — honest declaration when code can't run RE Framework: binary-internal deps prevent practify test 5.4 Anchor Health States
Verify retry cap — max 3 Lift→Verify cycles before going back to A-layer RE Framework: prevent process entropy in unfalsifiable loops 9. Degraded Verification

1. Protocol Overview

Practify is a three-layer code verification protocol for vibe coding workflows:

Layer What it does When it runs Maturity
Scanner Detects defensive code patterns via AST analysis Compile-time (static) Verified — Python: 38 findings 0 FP. TypeScript: validated on test files.
Anchors Binds verifiable tests to function declarations Compile-time (declarative) + Runtime (validation) Experimental — 18/18 tests passed in photo_screener trial.
Noise Cards Accumulates runtime failures as structured knowledge Runtime (continuous) Unverified — schema defined, no project has accumulated >0 cards.

Core Principle (First Law)

Any claim must be convertible to a verifiable practice test, executable in finite steps with observable results. Otherwise it is invalid for the purpose of pursuing effectiveness.

"Only Offense, No Defense"

The protocol does not prohibit. It demands proof.

  • Traditional: "You cannot divide by zero." (defensive)
  • Practify: "Prove the divisor is non-zero, or handle the zero case." (offensive)

The single allowed defense is i_dont_know — an honest declaration that opens the battlefield for practice feedback.


2. Uninstall Guarantee

A protocol must not become a new form of technical debt.

The Practify Protocol guarantees that removing it from a project requires deleting at most two files and optionally one line per source file. Source files with residual anchor lines MUST continue to function correctly after removal.

2.1 Mechanism: pract_stub.py

Each practify-instrumented project contains a single stub file at the project root:

# pract_stub.py
# Generated by `practify init`. Delete this file to disable all anchors.
# Keep this file without practify installed: decorators degrade to no-ops.

try:
    from practify import test as _pract_test, i_dont_know as _pract_idk
except ImportError:
    # practify not installed — anchors silently become no-ops.
    # Code continues to run without modification.
    def _pract_test(description, test_fn):
        return lambda f: f

    def _pract_idk(what):
        return lambda f: f

# Public names
test = _pract_test
i_dont_know = _pract_idk

Source files import from the stub:

from pract_stub import test as pt, i_dont_know as idk

# @pract: anchors auto-degrade if pract_stub.py or practify/ is removed.
# @pt lines can remain in source — they become dead imports (harmless).

@pt("empty list", lambda: process([]) == [])
@idk("behavior with large files not yet verified")
def process(data):
    ...

2.2 Three Operating States

State pract_stub.py practify/ Behavior
Full Yes Yes Anchors register and validate. practify test works.
Silent Yes No Decorators are no-ops. Code runs. Anchors don't register.
Clean No No from pract_stub import fails. Remove that line. Code is pristine.

2.3 Removal Procedure

1. Delete pract_stub.py
2. Delete practify/ directory (or uninstall practify pip package)
3. (Optional) Remove `from pract_stub import ...` lines from source files.
   These lines will cause ImportError if not removed, but:
   - A single sed/grep fixes all files: grep -rl "pract_stub" . | xargs sed -i '/pract_stub/d'
   - Or leave them — the ImportError is clean and explicit.

No AST-level code rewriting is required. No scanning the entire project to strip decorators.

2.4 Cross-Language Notes

  • Python: Decorator-based no-op (as above)
  • TypeScript: JSDoc annotations (/** @pract.test ... */) are comments — removing practify requires zero code changes. The annotations become inert documentation.
  • Rust: Proc macros expand to no-ops when the pract crate is removed from Cargo.toml.
  • All languages: Anchors MUST be removable by deleting a single dependency declaration. Annotations in source MUST be inert when the dependency is absent.

3. Noise Card JSON Schema

(Unchanged from v0.1 — minor field clarifications)

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://practify.dev/noise-card-v0.2.json",
  "type": "object",
  "required": ["noise_id", "timestamp", "trigger", "function_name", "observed", "expected"],
  "properties": {
    "noise_id": {
      "type": "string",
      "description": "Globally unique identifier"
    },
    "timestamp": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 UTC timestamp of failure observation"
    },
    "trigger": {
      "type": "string",
      "description": "Exact input or condition that triggered the failure"
    },
    "function_name": {
      "type": "string",
      "description": "Fully qualified function name"
    },
    "observed": {
      "type": "string",
      "description": "What actually happened. Specific — stack traces, return values, error messages."
    },
    "expected": {
      "type": "string",
      "description": "What should have happened per the function's claimed behavior."
    },
    "anchor_violated": {
      "type": "string",
      "description": "Which anchor (test description) was violated, if any."
    },
    "discovery": {
      "type": "string",
      "description": "What new knowledge did this failure produce? What did we not know before?"
    },
    "curriculum": {
      "type": "string",
      "description": "Concise, reusable lesson for AI context injection. Must be actionable."
    },
    "converted_to_test": {
      "type": "string",
      "description": "Description of the regression test created from this noise card."
    },
    "resolved": {
      "type": "boolean",
      "default": false
    },
    "resolved_at": {
      "type": "string",
      "format": "date-time"
    },
    "tags": {
      "type": "array",
      "items": {"type": "string"},
      "description": "Classification tags"
    },
    "context_snippet": {
      "type": "string",
      "description": "Code snippet surrounding the failure point (max 500 chars)"
    },
    "language": {
      "type": "string",
      "description": "Origin language — enables cross-language curriculum"
    }
  }
}

4. AI Context Format

(Unchanged from v0.1)


5. Anchor Semantics

5.1 Test Anchor

Property Description
Purpose A verifiable practice test bound to a function declaration
Semantics "I claim this function behaves correctly under condition X, and here is a reproducible test."
Required fields description (human-readable), test_fn (executable predicate returning boolean)
Required field (v0.3) source (provenance string): MUST record where the test vector data came from — trace_id, register/memory snapshot, observation timestamp. See 5.5 Source Provenance.
Compile-time check Every public function MUST have at least one test anchor OR one i_dont_know anchor
Runtime check Test anchors SHOULD be executable via practify test. If code cannot be independently compiled (e.g., RE-lifted code with internal binary dependencies), see 9. Degraded Verification.

5.2 I-Don't-Know Anchor

Property Description
Purpose Honest declaration of a cognitive boundary
Semantics "This function has edge cases I haven't verified yet. I am actively inviting practice feedback."
Required fields what (specific description of what is unknown)
Optional field (v0.3) source (provenance string): for RE use cases, records what static analysis prompted this unknown — F5 output, missing trace coverage, etc. Helps distinguish "I don't know because I haven't looked" from "I looked and genuinely can't determine."
Difference from TODO TODO = "I know what to do but haven't done it." I-don't-know = "I don't yet know what the correct behavior is."

5.3 Staleness Detection

@i_dont_know anchors created more than 90 days ago, on functions that have been modified since the anchor was created, MUST be escalated:

  • Scanner severity: INFO → WARNING
  • Message: "This cognitive boundary was declared N days ago. Has sufficient practice data accumulated to convert it to a @pract.test?"

Implementations SHOULD record the creation date of each @i_dont_know anchor. The pract_stub.py-based approach records this in the anchor file itself.

5.4 Anchor Health States

State Condition Meaning
healthy All tests pass Function's claimed behavior is verified
unverified Only i_dont_know anchors, no tests Exploration zone
degrading Has tests but some fail Previously verified behavior is now broken
stale_unknown i_dont_know > 90 days, function modified Cognitive boundary overdue for resolution
skeleton No anchors at all Violation of First Law
uncompilable (v0.3) Has anchors but code cannot be independently compiled Anchors carry source provenance but practify test cannot execute. Verification deferred to runtime trace comparison. Common in RE use cases.

5.5 Source Provenance (v0.3)

Each test anchor must answer: "Where did this data come from?"

Without source provenance, a test anchor is unfalsifiable — it could be derived from the same B1 hypothesis it claims to verify (circular reasoning), or fabricated entirely. Source provenance is the A-layer anchor point that breaks this circle.

Source string format:

source="<source_type>:<binary_or_file>!<function>#<id>, offset=<addr>, <key_observations> observed <ISO8601_timestamp>"

Source types:

Type Meaning Allowed for @pt Allowed for @idk
trace Dynamic debugging: register/memory snapshot from Frida/x64dbg/etc.
memory Memory dump: extracted constant tables, vtables, string tables
static Static analysis inference (F5 output, IDA disassembly, Ghidra decompiler)

Rule: @pt MUST use trace or memory source. @pt with static source or without source → INVALID (rejected at review).

Rule: @idk MAY use static source — honestly stating "this unknown was inferred from static analysis, not observed in trace."

Examples:

# Valid @pt — trace-based source
@pt("volume=5 → eax=1",
    lambda: speak(Animal(), 5) == 1,
    source="trace:foo.dll!speak#002, offset=0x1A, edx=5 eax=1 observed 2026-06-18T10:00:05Z")

# Valid @idk — static-based source, honest about origin
@idk("volume=100 时是否会溢出?",
     source="static:foo.dll!speak@0x1400077c0, F5 shows cmp edx,64h but trace never hit edx≥100")

# INVALID — @pt has no source
@pt("volume=5 → eax=1", lambda: speak(Animal(), 5) == 1)  # ← rejected

Implementation note: The source parameter is a string. Implementations MAY validate its format but MUST preserve it verbatim. The pract_stub.py-based approach passes source as an additional keyword argument to the decorator; when practify is not installed, the stub silently discards it (the anchor degrades to no-op but the source string remains in source code for audit).


6. Scanner Pattern Catalog

6.1 Severity Layering

The missing-anchor pattern (P3) MUST be severity-layered based on function characteristics:

Function Category Severity Rationale
Pure logic, no I/O calls WARNING High anchor value — easy to test, high regression risk
Contains I/O calls (open, requests, Image.open, etc.) INFO Low anchor value — test requires mocking or real resources. Suggest @i_dont_know.
Name starts with test_ SKIP Already a test function. Don't flag.
Name starts with _ SKIP Private/internal. Don't flag.

I/O detection keywords (language-agnostic):

  • File: open, read, write, Path, fs, file
  • Network: requests, fetch, http, curl, socket, connect
  • Image: Image, PIL, imread, imwrite, decode, encode
  • Database: execute, query, cursor, connect, collection

Functions matching ≥2 I/O keywords are classified as I/O-heavy.

6.2 Registry-Aware Scanning

The scanner MUST consult the runtime anchor registry before reporting missing-anchor.

When practify test is run, all decorated functions register their anchors. The scanner checks this registry:

  • Function found in registry with ≥1 test anchor → do not report
  • Function found in registry with only i_dont_knowdo not report (it's in honest exploration state)
  • Function found in registry with no anchors → report MISSING_ANCHOR

This enables out-of-line anchor files (pract_anchors.py) — anchors registered on wrapper functions whose names follow the convention _anchor_{function_name} are associated with the target function.

6.3 Pattern Definitions

P3: Missing Practice Anchor (REVISED)

Property Value
Severity WARNING (pure logic) / INFO (I/O-heavy) / SKIP (test_ prefix or private)
Definition A public function without a test anchor or i_dont_know declaration, and not found in the anchor registry
Cross-language Same severity layering applies in all implementations

P1, P2, P4-P6

(Unchanged from v0.1: Swallowed Exception, Bare Exception Handler, Defensive Null Propagation, Trivially True Test, Vague TODO)


7. Implementation Compliance Levels

Level Requirements
Level 1 — Scanner Implements P1-P6 with severity layering (6.1) and registry awareness (6.2). CLI.
Level 2 — Anchors Level 1 + stub-based anchor system (Section 2) with test/i_dont_know decorators.
Level 3 — Noise Level 2 + noise card creation and AI context export. Staleness detection (5.3).
Level 4 — Full Protocol Level 3 + runtime noise card accumulation integrated with test runner.

8. Maturity

Component Python TypeScript Maturity Latest Evidence
Scanner Verified photo_screener: 38 findings, 0 FP
Anchors Experimental photo_screener: 18/18 tests passed, 0 bugs found, 0 regressions
Source Provenance (v0.3) Conjecture Field defined in protocol. 0 RE projects have produced sourced anchors.
Noise Cards Unverified 0 cards accumulated
AI Context Conjecture 0 injection cycles run
Stub Uninstall Verified Tested: delete stub + practify, code still runs via no-op fallback
Degraded Verification (v0.3) Conjecture Modes defined. No RE project has exercised Partial/Degraded paths.

9. Degraded Verification (v0.3 Draft)

Not all anchored code can be independently compiled and run. This is not a failure of the protocol — it is an honest recognition that A-layer verification has material prerequisites. When those prerequisites are absent, the protocol MUST degrade gracefully rather than pretend.

9.1 Three Operating Modes

Mode Condition practify test Confidence auto-promotion Anchor source requirement
Full Practify installed AND code self-contained (no unresolved external deps) ✅ Runs ✅ draft→candidate @pt MUST have trace/memory source
Partial Practify installed BUT code has unresolved dependencies (common in RE: lifted code depends on internal binary symbols) ❌ Cannot run ❌ Manual only @pt MUST still carry source — the anchor serves as documented hypothesis until runtime verification becomes possible
Degraded Practify not installed ❌ N/A ❌ Manual only Source provenance still required in _anchors.py for audit trail

9.2 Self-Containment Classification

Before practify test is invoked, the function MUST be classified:

Self-contained: No calls to external functions, no global variable references, no custom types from outside the translation unit. → Eligible for Full mode.

Has-deps: Calls other functions OR references global state OR uses custom types. → Check dependency resolution:

  • All dependencies are themselves self-contained AND lifted → merge and compile → Full mode
  • Any dependency is unresolved → Partial mode → record in uncompilable_functions.yaml

9.3 Uncompilable Functions Manifesto

# uncompilable_functions.yaml
- function: speak
  source_location: "foo.dll:0x1400077c0"
  uncompilable_reason: "depends on AudioDevice::write (0x140008000) and Animal::vftable"
  unresolved_deps:
    - type: function
      name: AudioDevice::write
      address: "foo.dll:0x140008000"
      lift_status: not_started
    - type: vtable
      name: Animal::vftable
      address: "foo.dll:0x140007000"
  suggested_path: "Lift AudioDevice::write first, then retry speak compilation"
  anchor_count: 4
  anchor_sources_valid: true  # all @pt have trace/memory source

9.4 Retry Cap (Anti-Entropy)

When Partial or Degraded mode is active, the Lift→Verify cycle has a hard cap of 3 attempts per function before the methodology forces a return to A-layer data collection (Scout phase / dynamic tracing).

Rationale: repeatedly tweaking B1 hypotheses (Lift code) without new A-layer data (traces) is the definition of process entropy (实践偏离:过程熵增). The cap breaks this cycle by refusing to let B1 iterate in isolation.

9.5 Degraded Mode Honesty Statement

When operating in Partial or Degraded mode, tools and exports MUST prefix their output with:

"以下验证结果基于降级模式([部分/降级])。置信度未自动提升。所有 test anchor 均记录了数据来源,但尚未通过可公共观测的实践检验。confirmed 状态需要人工对照 trace 证据后手动授予。"

This is not defensive — it is an honest declaration of the current verification ceiling. The protocol remains useful: anchors document hypotheses with provenance, noise cards accumulate known failures, and the structure preserves everything needed for full verification when A-layer conditions permit.


10. Versioning

  • Protocol versions are v{major}.{minor}.
  • Minor version changes MUST be backward-compatible (old noise cards remain readable).
  • Current version: v0.3 — pre-stable. All components subject to change based on practice feedback.

"This specification is a working hypothesis. Its truth will be determined not by argument, but by whether it produces more reliable code in practice. The source provenance requirement (v0.3) is a wager: that requiring test data to carry its A-layer origin will break the circular reasoning that makes AI-generated code verification unfalsifiable. The wager will be settled not in this document, but in real RE projects."

— First Law, Applied Reflexively