Files
ucwm/docs/sprint_001.md
bill b758d7ea60 Sprint 1: project skeleton, type system, and all architecture specs
- src/types.hpp: complete UCWM type system in C++20 — 19 enums, 11 facet
  data types, all core structs (CanonicalObject, Constraint, Facet,
  GateSignal, WorldState, etc.) with full JSON round-trip serialization
- src/main.cpp: smoke test — constructs apple-problem WorldState by hand,
  serializes to JSON
- tests/test_types.cpp: 19 tests, 123 assertions, all passing
- CMakeLists.txt: CMake + CPM build with nlohmann/json, spdlog, Catch2
- schemas/: JSON Schema contracts for all UCWM data types
- gates/, specialists/, resolver/, synthesis/: language-agnostic interface
  contracts and domain specs for all pipeline layers
- docs/: architecture, vocabulary, decision matrices, roadmap (6 phases,
  28 sprints), sprint_001, implementation_constraints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:09:55 -07:00

6.7 KiB

Sprint 1 — Skeleton

Goal

The C++ project compiles cleanly. All core UCWM types are defined as C++ structs. A WorldState can be constructed in code and serialized to JSON. No logic runs yet — this sprint is purely about getting the type system right before anything else is built on top of it.

If the types are wrong here, everything built on them will be wrong. Take the time to get them right.


Deliverables

File Description
CMakeLists.txt Build system. CPM dependencies. Targets for main and tests.
src/types.hpp All core UCWM structs: CanonicalObject, all Facet types, Constraint, GateSignal, RoutingDecision, WorldState, Provenance, SpecialistError.
src/main.cpp Smoke test: constructs a minimal WorldState for the apple problem by hand, serializes to JSON, prints debug output.
tests/test_types.cpp Unit tests: construct each type, verify field access, verify JSON round-trip.

Acceptance criteria

  • cmake -B build && cmake --build build succeeds with zero errors and zero warnings
  • ./build/ucwm runs and prints a JSON-serialized WorldState to stdout
  • ./build/tests runs and all type tests pass
  • Every field from schemas/objects.json, schemas/facets.json, schemas/constraints.json, schemas/routing.json, schemas/provenance.json, and schemas/world_state.json has a corresponding C++ field
  • No raw owning pointers — use std::optional, std::variant, std::vector, std::unordered_map
  • All string IDs typed as std::string (upgrade to a newtype wrapper in a later sprint if needed)

Out of scope

  • Any gate logic
  • Any specialist logic
  • Any resolver logic
  • Any synthesis logic
  • JSON Schema validation (structs mirror the schemas, but no runtime validation yet)
  • Neural models of any kind
  • Performance optimization

Dependencies (pulled via CPM)

CPMAddPackage("gh:nlohmann/json@3.11.3")
CPMAddPackage("gh:gabime/spdlog@1.13.0")
CPMAddPackage("gh:catchorg/Catch2@3.5.4")

Type design notes

Use std::variant for sum types

Object kind, facet kind, constraint status, object status — these are closed enumerations in the schema. Use enum class for simple enums, std::variant when the type carries different data per variant.

enum class ObjectKind {
    Entity, Event, Concept, Proposition,
    RelationInstance, ProcedureStep, CodeObject,
    Quantity, Claim, State
};

enum class ObjectStatus { Proposed, Active, Merged, Split, Invalidated };
enum class ConstraintStrength { Hard, Soft, Probabilistic, Defeasible };
enum class ConstraintStatus { Unresolved, Resolved, Contradicted, Suspended };
enum class Polarity { Positive, Negative };

Use std::optional for nullable fields

Schema fields that are not in required should be std::optional<T> in C++. Never use raw nulls or sentinel values.

std::optional<SurfaceSpan>    surface_span;
std::optional<std::string>    canonical_label;
std::optional<std::string>    merged_into;
std::optional<float>          probability;   // only when strength=Probabilistic

Facets as a variant

All facet types share a base set of fields, then add domain-specific fields. Represent as a std::variant or a struct with a std::variant payload.

struct TemporalFacetData {
    std::optional<std::string> time_point;
    std::optional<std::string> interval_start;
    std::optional<std::string> interval_end;
    bool is_recurring = false;
    std::optional<std::string> recurrence_pattern;
    std::vector<std::string>   temporal_order_refs;
};

// ... one struct per facet kind ...

using FacetData = std::variant<
    TemporalFacetData,
    SpatialFacetData,
    CausalFacetData,
    LogicalFacetData,
    OwnershipFacetData,
    QuantityFacetData,
    SyntacticFacetData,
    SocialFacetData,
    PlanningFacetData
>;

struct Facet {
    std::string             facet_id;
    std::string             object_ref;
    float                   confidence;
    std::string             source_module;
    std::vector<std::string> provenance_refs;
    FacetData               data;             // kind is implicit in the variant index
};

WorldState as maps

struct WorldState {
    std::string                                    state_id;
    WorldStateStage                                stage;
    std::optional<std::string>                     input_text;
    std::unordered_map<std::string, CanonicalObject> objects;
    std::unordered_map<std::string, Facet>           facets;
    std::unordered_map<std::string, Constraint>      constraints;
    std::unordered_map<std::string, Provenance>      provenance;
    std::optional<RoutingDecision>                   routing_decision;
    std::vector<GateSignal>                          gate_signals;
    std::vector<ResolutionEntry>                     resolution_log;
    std::vector<ContradictionRecord>                 open_contradictions;
    std::optional<std::string>                       synthesis_answer;
    std::optional<float>                             synthesis_confidence;
    std::vector<std::string>                         derivation_trace;
};

JSON serialization

Use nlohmann/json's NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE macro or manual to_json/from_json overloads. Every type needs both directions for the debug trace and for loading fixtures.

// Example pattern for each type:
void to_json(nlohmann::json& j, const CanonicalObject& o);
void from_json(const nlohmann::json& j, CanonicalObject& o);

File layout after Sprint 1

ucwm/
  CMakeLists.txt
  cmake/
    CPM.cmake
  src/
    types.hpp          ← all structs and enums
    main.cpp           ← smoke test
  tests/
    test_types.cpp     ← type construction + JSON round-trip tests

Smoke test behavior (main.cpp)

main.cpp should:

  1. Construct the post-proposal WorldState from examples/word_problem_001.json by hand (hardcoded, not parsed — parsing the fixture comes in Sprint 2)
  2. Serialize it to JSON using nlohmann/json
  3. Print the JSON to stdout
  4. Print a simple debug header showing object count, constraint count

Expected output:

=== UCWM Smoke Test ===
WorldState stage: post_proposal
Objects:    7
Facets:     0
Constraints: 0

[full JSON follows]

Definition of done

Sprint 1 is done when:

  1. All acceptance criteria checkboxes are checked
  2. A second person (or the same person on a fresh read) can understand every field in types.hpp without referring back to the schemas
  3. The JSON output of main.cpp matches the structure of examples/word_problem_001.json for the post-proposal stage

Next sprint preview

Sprint 2 builds the keyword gates and rule-based object proposal on top of these types. None of that can start until types.hpp is locked.