← Back to Library

PoNIFs — Powerful, Non-Intuitive Features

concept ·  everyone on WIP

PoNIFs — Powerful, Non-Intuitive Features

In one sentence. A PoNIF is a WIP behaviour that violates conventional platform-development expectations by design — not a bug, but a trap that catches every new developer who assumes conventional semantics.

Why it exists. Each PoNIF buys a capability a conventionally-behaved design cannot provide — history that never breaks, schema evolution without migration, identity that doesn't need a caller-tracked ID, batch writes that don't abort on the first bad record. The cost is that assuming the conventional pattern instead produces a silent failure, not an error: the call succeeds, returns 200, and does the wrong thing. The PoNIFs resource exists to name each trap before a caller meets it live.

The model

WIP currently documents eight PoNIFs, each a fixed (number, name) pair with a description, a Trap (the conventional assumption that breaks) and a Rule (what to do instead). A closing section, "The Compactheimer's Warning," is not a numbered PoNIF but a self-check list for AI assistants specifically.

#PoNIFConventional assumption it breaks
1Nothing Ever DiesDeactivating an entity makes it fail for existing references too, not just new ones.
2Template Versioning — Update Does NOT ReplaceUpdating a schema replaces the old one; there is one active schema.
3Document Identity — The Hash DecidesPOST creates and PUT updates; the caller/URL controls identity, not the data.
4Bulk-First — 200 OK AlwaysAn HTTP 200 means the whole request succeeded.
5Registry Synonyms — Multiple IDs Are NormalOne real-world entity has exactly one platform ID; two IDs means corrupt data.
6Template Cache — Changes Aren't InstantA template update is visible on the very next call that resolves "latest".
7Edge Types Are Stored as TemplatesA template with two document-reference fields is just an entity template with foreign keys.
8versioned: false — Updates Overwrite In PlaceEvery update creates a new version — the rule PoNIF #2 itself just established.

How it works

The list is served as a single static MCP resource, wip://ponifs, defined by get_ponifs() in components/mcp-server/src/wip_mcp/server.py (@mcp.resource("wip://ponifs")). It returns one Markdown document — the eight numbered entries plus the closing warning — with no per-call computation; unlike the adjacent wip://query-assistant-prompt resource in the same file, it does not query live platform state to build itself.

The platform points callers at it explicitly rather than leaving discovery to chance: the wip://development-guide resource (same file) instructs, in its own words, "Read wip://ponifs before Phase 3. WIP has several powerful but non-intuitive behaviours that will cause silent failures if you rely on conventional assumptions" — i.e. before an agent starts designing a data model or writing code against WIP.

Each numbered entry follows the same shape: a short description of the actual behaviour, a Trap naming the conventional assumption that fails, and a Rule stating the corrective action. Several entries carry an additional named subsection where the behaviour itself has a further nuance — PoNIF #1's "Default, not absolute", PoNIF #2's "Corollary" and "v2 caveat", PoNIF #3's "What's NOT in the identity hash".

Rules & invariants

1. Nothing Ever Dies. Deactivation (soft delete) removes an entity from availability for new data but always still resolves for existing references — "inactive" means "retired," not "deleted." This is the platform default (a namespace's deletion_mode: "retain"), not an absolute: a namespace explicitly switched to deletion_mode: "full" (guarded — the wip namespace refuses the switch; retain → full requires confirm_enable_deletion=true) accepts hard_delete=true on delete operations, and the record is then permanently removed — existing references will NOT resolve. Mutable-terminology terms and binary files are hard-deletable regardless of deletion_mode.

2. Template Versioning — Update Does NOT Replace. Updating a template creates a new version; the old version stays active, and new documents may still be created against it. Rule: deactivate the old version explicitly with deactivate_template() unless multi-version operation is intended, and always pass template_version when creating documents. Corollary: identity_hash scopes to template_id (PoNIF #3), and template_id is stable across a template's versions — the one exception to "new version → new ID". Adding a non-identity field and re-submitting an existing document's identity values therefore yields an UPDATE (new document version, new field populated), not a CREATE. To actively re-pin existing documents onto a newer template version, migrate_documents (POST /documents/migrate) is the validated, identity-preserving bulk operation: dry-run validates each document against the target version and reports per-document readiness; apply then writes a new document version pinned to the target, keeping the same document_id and identity_hash. The source version may be inactive — migrate validates against the target, not the source. An identity-changing move is rejected as a fork, not a migrate. Migration is never automatic.

3. Document Identity — The Hash Decides. A template's identity_fields are hashed on write; the same hash versions an existing document (update), a different hash creates a new one (create) — one call, a natural-upsert. Zero identity_fields makes every submission a new document — append-only, no update path — and PATCH by document_id is rejected with error_code append_only (a document_id is a surrogate row handle; PATCH operates on logical identity, which an identity-less document doesn't have). template_version and namespace are not part of the identity hash — namespace scoping happens externally via the composite key (namespace, identity_hash, template_id), so the same identity_hash in two namespaces is two distinct entities. Adding a field to identity_fields is breaking: every existing document hashes differently on the next write, producing parallel orphan documents (the resource cites a real incident: an external loader computed identity from undeclared fields, every hash collided to empty, and 213 of 214 records were silently dropped). Adding a field to data.* outside identity_fields is non-breaking (PoNIF #2's corollary applies).

4. Bulk-First — 200 OK Always. Every WIP write API returns HTTP 200 even when individual items fail; per-item status lives in results[i].status (created, updated, error, skipped). The MCP server's single-item tools unwrap and surface errors automatically, but bulk tools (create_documents_bulk, etc.) require checking per-item results explicitly.

5. Registry Synonyms — Multiple IDs Are Normal. Any entity can carry multiple identifiers (synonyms); two WIP IDs resolving to the same real-world entity is a normal, expected state, not corruption. merge_entries() reconciles duplicates — synonyms let a bank ID, an ERP code, and a WIP UUID all resolve to the same entity.

6. Template Cache — Changes Aren't Instant. "Latest active" template resolution carries a 5-second cache TTL; after a template update, the old definition may still be served for up to 5 seconds. Lookups by an explicit version are cached permanently, since an explicit version is immutable. Rule: pass an explicit template_version, or wait out the 5-second window after a template change.

7. Edge Types Are Stored as Templates. WIP stores two conceptually distinct schemas in one representation: entity templates (usage: 'entity', the default) and edge types (usage: 'relationship'). Both live in the templates collection and share the same APIs, but document-store applies edge-type-specific handling to writes: extra cross-namespace and not-archived validation, lazy Mongo indexes on data.source_ref / data.target_ref, two dedicated query endpoints (/relationships, /traverse), and reporting-sync columns (source_ref_id / target_ref_id). The MCP tool create_edge_type exists specifically to surface this distinction at the agent-facing ingress. usage is immutable after creation. The allowed-endpoint set (source_templates / target_templates) is append-only, not frozen — widen it with add_edge_type_endpoints; removal stays unsupported.

8. versioned: false — Updates Overwrite In Place. A direct, named exception to PoNIF #2: edge types may declare versioned: false at creation, and documents under such an edge type stay at version=1 forever — updates overwrite the existing payload in place, and the previous data is gone. versioned: false requires non-empty identity_fields (overwrite-in-place re-addresses an entity, which requires an identity to re-address); this combination is rejected at both template create and update. versioned is immutable after creation, and (per the resource) is currently available only on edge types, always true for append-only (identity-less) templates.

Gotchas

See also

Generated from real source code, not hand-written — see the WIP Technical Library.