Slice C: instance-only qualifying characteristics#50
Merged
Conversation
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
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
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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_numberis meaningful per instance,never class-wide). The marker is a boolean key
instance_only => truecolocated 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/2stays 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}}:bind_qc_value/3create_class/3instance_onlyand carries a concrete valueupdate_node_avps/2mutate/1too, both composingupdate_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 themarker); 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)mutate/1path; reject tests assert thefull error tuple and prove transaction rollback.
Design:
docs/designs/slice-c-instance-only-qc-design.mdPlan:
docs/superpowers/plans/2026-06-27-slice-c-instance-only-qc.md🤖 Generated with Claude Code