Skip to content

Slice C: instance-only qualifying characteristics#50

Merged
david-w-t merged 9 commits into
davidwt-com:mainfrom
david-w-t:develop
Jun 28, 2026
Merged

Slice C: instance-only qualifying characteristics#50
david-w-t merged 9 commits into
davidwt-com:mainfrom
david-w-t:develop

Conversation

@david-w-t

@david-w-t david-w-t commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Slice C — Instance-only qualifying characteristics

A class can now declare a qualifying characteristic (QC) as instance-only:
the attribute is relevant to instances, but binding a value at the class
level
is a category error (e.g. a serial_number is meaningful per instance,
never class-wide). The marker is a boolean key instance_only => true
colocated on the class node's QC AVP map:

#{attribute => A, value => undefined, instance_only => true}

Setting the marker

  • graphdb_class:add_qualifying_characteristic/3(ClassNref, AttrNref, #{instance_only => true}); the existing /2 stays the unflagged declare.
  • create_class/3 — accepts an instance-only QC in its initial AVP list.

The marker is currently not settable through update_node_avps/mutate:
slice B's AVP well-formedness check rejects the extra key. That restriction
is a side effect of the slice-B grammar, not a decided long-term contract —
whether toggling a QC's instance-only status should become a first-class
mutation is recorded as an open investigation under the slice-C deferred
follow-ons in TASKS.md.

Enforcement — three class-level value-binding gates

All return the bare reason {error, {instance_only_attribute, AttrNref}}:

Gate Rejects
bind_qc_value/3 a direct class-level bind on a marked QC
create_class/3 an initial AVP that is instance_only and carries a concrete value
update_node_avps/2 a value-bearing update to a marked stored entry (covers mutate/1 too, both composing update_node_avps_in_txn/3)

Enforcement is local to the class node written. A value-bearing update
includes value => undefined (slice B's upsert would otherwise strip the
marker); a delete is permitted.

Scope / deferred

Inherited enforcement (subclass-redeclare bypass), the per-template attribute
list, template-bound variant values, and the marker-mutability question above
are deliberately deferred and recorded in TASKS.md.

Tests

Full suite green: 488 CT + 133 EUnit = 621, 0 failures, 0 compile
warnings. New: 7 EUnit (class predicates) + 4 EUnit (check_instance_only/2)

  • 8 CT across the three gates and the mutate/1 path; reject tests assert the
    full error tuple and prove transaction rollback.

Design: docs/designs/slice-c-instance-only-qc-design.md
Plan: docs/superpowers/plans/2026-06-27-slice-c-instance-only-qc.md

🤖 Generated with Claude Code

david-w-t and others added 9 commits June 27, 2026 06:53
Design doc for the instance-only QC marker and its class-level
enforcement. Decisions: flag lives on the class QC AVP
(#{attribute, value => undefined, instance_only => true}); three
enforcement gates (bind_qc_value/3, create_class/3, update_node_avps/2)
reject a class-level value bind on a marked attribute; local enforcement
only (inherited bypass deferred). Records template-attribute-list,
template-bound values, and inherited enforcement as TASKS.md follow-ons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Add two pure helper functions to graphdb_class for instance-only
qualifying characteristics: is_instance_only/1 and
validate_instance_only_avps/1. These feed Tasks 3, 4, and 5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Wire validate_instance_only_avps/1 as the outermost guard in
do_create_class/4. A bound instance_only => true AVP now returns
{error, {instance_only_attribute, AttrNref}} before any nref
allocation or Mnesia writes occur.

Two new CT tests added to graphdb_class_SUITE (creation group):
  - create_class_3_rejects_instance_only_with_value
  - create_class_3_accepts_instance_only_unbound

72 tests, all passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
The PR text implied that slice B's AVP well-formedness check makes the
instance_only marker permanently unsettable through update_node_avps/2
and mutate/1. That restriction is a side effect of the slice-B grammar,
not a decided long-term contract — record it as an open investigation in
TASKS.md rather than an implicit design stance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF

@david-w-t david-w-t left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@david-w-t david-w-t merged commit 204ea9c into davidwt-com:main Jun 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant