Skip to content

feat: support combined anonymous instances and OP#13 schema traits validation#13

Merged
Artifizer merged 3 commits intoGlobalTypeSystem:mainfrom
GeraBart:feat/anonymous_instances_test_OP#6
Mar 6, 2026
Merged

feat: support combined anonymous instances and OP#13 schema traits validation#13
Artifizer merged 3 commits intoGlobalTypeSystem:mainfrom
GeraBart:feat/anonymous_instances_test_OP#6

Conversation

@GeraBart
Copy link
Copy Markdown
Contributor

@GeraBart GeraBart commented Mar 6, 2026

Summary by CodeRabbit

  • New Features

    • Added a unified entity validation method that returns annotated results indicating schema vs. instance.
    • Added entity-level trait validation to enforce trait presence and closed trait schemas.
  • Bug Fixes

    • Improved ID parsing to allow trailing UUID segments and better handle edge-case formats.
    • Strengthened trait and inheritance validation, including cyclic reference detection and stricter schema checks.
  • Chores

    • Bumped package version to 0.3.0.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

Updated submodule reference and package version; added UUID-tail handling in GTS ID parsing; implemented comprehensive trait- and inheritance-based schema validation in the store (with cycle detection and many helpers); added GTS.validateEntity and simplified server validate endpoint to call store.validateEntity.

Changes

Cohort / File(s) Summary
Submodule & Version
\.gts-spec, package.json
Submodule reference updated; package version bumped from 0.2.00.3.0. No behavioral changes from the submodule entry.
Store — trait & inheritance validation
src/store.ts
Big addition: trait-validation flow (validateSchemaTraits, validateEntityTraits, buildSchemaChain, collectTraitSchemas/Values, resolve refs, apply defaults, detectRefCycle, etc.), enhanced validateSchemaAgainstParent with cycle detection and trait checks, validateInstance accepts GTS ID or base ID and results include valid flag. New public method validateEntityTraits(entityId: string).
GTS ID parsing & types
src/gts.ts, src/types.ts
Add UUID tail detection and isUuidTail segment flag; per-segment hyphen rules; matching logic treats UUID tail specially. GtsIDSegment gains isUuidTail: boolean.
Public API & server usage
src/index.ts, src/server/server.ts
Added validateEntity(id: string): ValidationResult & { entity_type: string } on GTS; server validate endpoint simplified to a single call to store.validateEntity(id) and forwards unified result.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant Store
    participant Validator
    Client->>Server: POST /validateEntity(id)
    Server->>Store: validateEntity(id)
    alt id refers to schema
        Store->>Validator: validateSchemaAgainstParent(id)
        Store->>Validator: validateSchemaTraits(id)
        Validator-->>Store: ValidationResult (schema)
    else id refers to instance
        Store->>Validator: validateInstance(id) (resolve GTS id -> base id if needed)
        Validator-->>Store: ValidationResult (instance)
    end
    Store-->>Server: ValidationResult + entity_type
    Server-->>Client: 200 OK + ValidationResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through schemas with a UUID tail bright,
I gather traits gently by lamp-light,
A version bumped up, a submodule nudge,
Store checks the chains and refuses to trudge,
Hooray — the rabbit validates into the night!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: support for combined anonymous instances and OP#13 schema traits validation, which aligns with the substantive code additions in src/store.ts, src/gts.ts, and src/index.ts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

@GeraBart GeraBart marked this pull request as draft March 6, 2026 05:58
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/store.ts (1)

98-104: Avoid double parsing by using try-catch pattern.

Gts.isValidGtsID(gtsId) internally calls parseGtsID (see context snippet 1), and then line 100 parses again. This duplicates work. Consider restructuring to parse once:

♻️ Suggested refactor to avoid double parsing
-      let objId: string = gtsId;
-      if (Gts.isValidGtsID(gtsId)) {
-        const gid = Gts.parseGtsID(gtsId);
-        objId = gid.id;
-      }
+      let objId: string;
+      try {
+        const gid = Gts.parseGtsID(gtsId);
+        objId = gid.id;
+      } catch {
+        // Not a valid GTS ID format, use raw input for lookup
+        objId = gtsId;
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store.ts` around lines 98 - 104, The code currently calls Gts.parseGtsID
twice via Gts.isValidGtsID(gtsId) then again calling Gts.parseGtsID; change to
call Gts.parseGtsID once in a try-catch: attempt const gid =
Gts.parseGtsID(gtsId) and set objId = gid.id on success, and on failure fall
back to objId = gtsId (or handle the error case as before); update the block
around objId and the subsequent this.get(objId) to use the single parsed result
and remove the Gts.isValidGtsID check to avoid duplicate parsing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/store.ts`:
- Around line 98-104: The current lookup in src/store.ts lets arbitrary strings
bypass GTS validation by using objId = gtsId when Gts.isValidGtsID(gtsId) is
false; decide and implement one of two fixes: (A) if dual-format lookup is
intended, add an inline comment near the Gts.isValidGtsID / Gts.parseGtsID call
documenting that parseGtsID(gtsId).id returns the full GTS ID (not a short ID)
and that a raw string fallback is intentionally allowed for direct this.get
lookups, or (B) if validation is required, remove the conditional and enforce
validation by rejecting invalid IDs (e.g., throw or return an error) before
calling this.get; reference the symbols Gts.isValidGtsID, Gts.parseGtsID, objId,
and this.get when making the change.

---

Nitpick comments:
In `@src/store.ts`:
- Around line 98-104: The code currently calls Gts.parseGtsID twice via
Gts.isValidGtsID(gtsId) then again calling Gts.parseGtsID; change to call
Gts.parseGtsID once in a try-catch: attempt const gid = Gts.parseGtsID(gtsId)
and set objId = gid.id on success, and on failure fall back to objId = gtsId (or
handle the error case as before); update the block around objId and the
subsequent this.get(objId) to use the single parsed result and remove the
Gts.isValidGtsID check to avoid duplicate parsing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7afb0ca7-60d0-4706-8cc4-50a1e184717e

📥 Commits

Reviewing files that changed from the base of the PR and between 367386b and 5da2c0b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • .gts-spec
  • package.json
  • src/store.ts

Comment thread src/store.ts
@GeraBart GeraBart force-pushed the feat/anonymous_instances_test_OP#6 branch from 5da2c0b to 0033f35 Compare March 6, 2026 07:24
@GeraBart GeraBart changed the title Feat/anonymous instances test op#6 feat: support combined anonymous instances and OP#13 schema traits validation Mar 6, 2026
@GeraBart GeraBart marked this pull request as ready for review March 6, 2026 07:28
@GeraBart GeraBart marked this pull request as draft March 6, 2026 07:31
@GeraBart GeraBart force-pushed the feat/anonymous_instances_test_OP#6 branch from 0033f35 to 7606154 Compare March 6, 2026 07:39
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/gts.ts`:
- Around line 428-448: The UUID-tail branch currently treats the last token as
an anonymous UUID even when it's the only segment, causing "gts.<uuid>" to be
misclassified; update the conditional that checks "i === parts.length - 1 &&
!part.endsWith('~') && UUID_REGEX.test(part)" to also require an existing prior
segment (e.g., add "&& i > 0" or "&& gtsId.segments.length > 0") so a real
prefix is present before setting isUuidTail on the created GtsIDSegment; apply
the same change to the other identical UUID-tail block (the similar check around
the 466-470 area) to keep behavior consistent.

In `@src/store.ts`:
- Around line 1129-1133: The validateSchemaTraits(schemaId) call is placed after
the early-return for base schemas so base schemas skip OP#13; move or invoke
validateSchemaTraits (or call it conditionally before the early-return) so it
runs for schemas with no parent as well; update the validateEntity() /
validateSchema path to call validateSchemaTraits(schemaId) prior to the
base-schema early return (and keep validateEntityTraits() for completeness
checks) so malformed base trait schemas are validated across the inheritance
chain.
- Around line 1289-1296: The current early return in validateEntity() that
rejects entities when hasTraitValues is false wrongly rejects schemas whose
trait values are satisfied by defaults; instead of rejecting when hasTraitValues
is false, change the check to use the output of validateSchemaTraits() (or the
computed effective trait values it returns) — e.g., call or reuse
validateSchemaTraits() / its effectiveTraitValues result and only return the
error if required traits are still missing after defaults/materialization.
Update the logic around hasTraitValues and the error return (referencing
entityId, validateSchemaTraits(), and hasTraitValues) so defaults produced by
validateSchemaTraits() are considered valid rather than causing failure.
- Around line 1380-1389: The code in resolveTraitSchemaRefs silently discards
unresolved $ref when this.get(refId) returns undefined; update
resolveTraitSchemaRefs so that when refEntity is falsy (this.get(refId) returned
nothing) it does not drop the reference but instead surfaces the problem—e.g.,
throw a descriptive error or record an unresolved-ref failure that will cause
validation to fail (include refId and the parent id/context in the message).
Locate the block using this.get(refId), refEntity, resolveTraitSchemaRefs and
result, and replace the silent continue with logic that raises or propagates an
error for missing referenced entities so broken $ref entries do not become
looser schemas.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45183d10-4214-419d-a77d-c6ef3bb9a700

📥 Commits

Reviewing files that changed from the base of the PR and between 5da2c0b and 0033f35.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • package.json
  • src/gts.ts
  • src/index.ts
  • src/server/server.ts
  • src/store.ts
  • src/types.ts

Comment thread src/gts.ts
Comment thread src/store.ts
Comment thread src/store.ts
Comment thread src/store.ts
@GeraBart GeraBart force-pushed the feat/anonymous_instances_test_OP#6 branch from 7606154 to 3527ae5 Compare March 6, 2026 08:17
@GeraBart GeraBart marked this pull request as ready for review March 6, 2026 08:20
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/store.ts (2)

1353-1360: ⚠️ Potential issue | 🟠 Major

Don't reject trait schemas that are satisfied entirely by defaults.

This !hasTraitValues guard still fails schemas whose effective trait values are fully materialized by validateSchemaTraits(). Use the resolved trait-validation result here instead of requiring raw x-gts-traits to be present.

Proposed fix
-    // If trait schemas exist but no trait values, entity is incomplete
-    if (!hasTraitValues) {
-      return {
-        id: entityId,
-        ok: false,
-        error: 'Entity defines x-gts-traits-schema but no x-gts-traits values are provided',
-      };
-    }
+    const traitsResult = this.validateSchemaTraits(entityId);
+    if (!traitsResult.ok) {
+      return traitsResult;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store.ts` around lines 1353 - 1360, The guard that rejects entities when
!hasTraitValues should instead consider the resolved/validated trait result
returned by validateSchemaTraits(); modify the logic around hasTraitValues to
check the validation output (e.g., the result object returned by
validateSchemaTraits() or its "ok"/"satisfied" flag) and only return the error
for entityId when the validation result indicates the trait schema is not
satisfied, allowing cases where defaults produced a fully materialized result to
pass even if raw x-gts-traits is absent.

1449-1459: ⚠️ Potential issue | 🟠 Major

Unresolvable trait refs are still silently discarded.

If this.get(refId) misses here, the code just continues and drops the referenced constraints from the effective trait schema. That can turn a broken $ref into a passing validation.

Proposed fix
         const refEntity = this.get(refId);
-        if (refEntity && refEntity.content) {
-          const resolved = this.resolveTraitSchemaRefs(refEntity.content, visited, depth + 1);
-          // Merge resolved content into result
-          for (const [rk, rv] of Object.entries(resolved)) {
-            if (rk !== '$id' && rk !== '$$id' && rk !== '$schema' && rk !== '$$schema') {
-              result[rk] = rv;
-            }
-          }
-        }
+        if (!refEntity?.content) {
+          throw new Error(`Unresolvable GTS trait reference: ${refId}`);
+        }
+        const resolved = this.resolveTraitSchemaRefs(refEntity.content, visited, depth + 1);
+        for (const [rk, rv] of Object.entries(resolved)) {
+          if (rk !== '$id' && rk !== '$$id' && rk !== '$schema' && rk !== '$$schema') {
+            result[rk] = rv;
+          }
+        }
         continue;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/store.ts` around lines 1449 - 1459, When resolving $ref entries in
resolveTraitSchemaRefs, don't silently skip missing references: if
this.get(refId) returns falsy, throw a descriptive Error (or reject) that
includes the missing refId and current context (e.g., caller trait id / visited
stack) instead of continuing; update the block that currently reads refEntity =
this.get(refId) to detect a missing ref and raise that error so callers of
resolveTraitSchemaRefs (and any validation flow using it) fail fast rather than
dropping constraints.
src/gts.ts (1)

428-447: ⚠️ Potential issue | 🟠 Major

Require a real segment before treating the tail as a UUID.

Line 429 still lets gts.<uuid> parse as a valid ID: the first/only segment enters the UUID-tail branch, and the single-segment instance guard at Lines 467-470 is then bypassed. Gate this path on an existing prior segment.

Proposed fix
-      if (i === parts.length - 1 && !part.endsWith('~') && UUID_REGEX.test(part)) {
+      if (i > 0 && i === parts.length - 1 && !part.endsWith('~') && UUID_REGEX.test(part)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/gts.ts` around lines 428 - 447, The UUID-tail branch currently allows a
single-part ID like "gts.<uuid>" to be treated as a UUID tail; change the
condition that enters the UUID tail handling to require an existing prior
segment before accepting a UUID tail. Specifically, when checking "if (i ===
parts.length - 1 && !part.endsWith('~') && UUID_REGEX.test(part))" add a guard
that gtsId.segments (or parts.length) indicates there was at least one real
segment before the tail (e.g., require gtsId.segments.length > 0 or parts.length
> 1) so the UUID-tail handling (GtsIDSegment created with isUuidTail = true)
only runs when a prior segment exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/store.ts`:
- Around line 1517-1529: The detectRefCycle recursion shares the same visited
Set across sibling branches which can falsely mark separate branches as cyclic;
fix by cloning visited for each recursion branch: when recursing into a $ref
(refEntity.content) create a branchVisited = new Set(visited),
branchVisited.add(refId) and call this.detectRefCycle(originId,
refEntity.content, branchVisited, depth + 1); similarly, when iterating
content.allOf call this.detectRefCycle(originId, sub, new Set(visited), depth +
1) so each sibling branch gets its own visited set and cycles are only detected
along the actual path.

---

Duplicate comments:
In `@src/gts.ts`:
- Around line 428-447: The UUID-tail branch currently allows a single-part ID
like "gts.<uuid>" to be treated as a UUID tail; change the condition that enters
the UUID tail handling to require an existing prior segment before accepting a
UUID tail. Specifically, when checking "if (i === parts.length - 1 &&
!part.endsWith('~') && UUID_REGEX.test(part))" add a guard that gtsId.segments
(or parts.length) indicates there was at least one real segment before the tail
(e.g., require gtsId.segments.length > 0 or parts.length > 1) so the UUID-tail
handling (GtsIDSegment created with isUuidTail = true) only runs when a prior
segment exists.

In `@src/store.ts`:
- Around line 1353-1360: The guard that rejects entities when !hasTraitValues
should instead consider the resolved/validated trait result returned by
validateSchemaTraits(); modify the logic around hasTraitValues to check the
validation output (e.g., the result object returned by validateSchemaTraits() or
its "ok"/"satisfied" flag) and only return the error for entityId when the
validation result indicates the trait schema is not satisfied, allowing cases
where defaults produced a fully materialized result to pass even if raw
x-gts-traits is absent.
- Around line 1449-1459: When resolving $ref entries in resolveTraitSchemaRefs,
don't silently skip missing references: if this.get(refId) returns falsy, throw
a descriptive Error (or reject) that includes the missing refId and current
context (e.g., caller trait id / visited stack) instead of continuing; update
the block that currently reads refEntity = this.get(refId) to detect a missing
ref and raise that error so callers of resolveTraitSchemaRefs (and any
validation flow using it) fail fast rather than dropping constraints.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2e0355c-3df3-4680-89d5-c60ea3b51f9e

📥 Commits

Reviewing files that changed from the base of the PR and between 0033f35 and 3527ae5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • package.json
  • src/gts.ts
  • src/index.ts
  • src/server/server.ts
  • src/store.ts
  • src/types.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/types.ts
  • src/server/server.ts
  • src/index.ts

Comment thread src/store.ts
GeraBart added 2 commits March 6, 2026 18:06
…lidation

Signed-off-by: GeraBart <246844849+GeraBart@users.noreply.github.com>
Signed-off-by: GeraBart <246844849+GeraBart@users.noreply.github.com>
@GeraBart GeraBart force-pushed the feat/anonymous_instances_test_OP#6 branch from 3527ae5 to 74710d4 Compare March 6, 2026 10:06
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/gts.ts`:
- Line 18: Segments representing UUID tails must be skipped or handled
explicitly wherever version/identity fields are read: update code that inspects
segment fields to first check the isUuidTail flag and avoid treating its falsy
verMajor/verMinor or empty vendor/package/namespace/type as real values.
Concretely, in the logic that returns the "incomplete pattern" error (the
lastSeg check in the query handling where you test lastSeg.isType and
lastSeg.verMajor), add a guard to ensure you only apply that test when
!lastSeg.isUuidTail; in compatibility logic (the comparison code that directly
reads verMajor/verMinor) short-circuit or skip comparisons when seg.isUuidTail
is true; and in the cast/version-string builder (where you format
v{verMajor}.{verMinor}) detect isUuidTail and do not emit v0.0 but instead treat
it as a UUID-tail segment (skip or format as UUID). Apply these changes to all
uses of verMajor/verMinor/vendor/package/namespace/type to prevent
misclassification of UUID tail segments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a46c017c-1312-44c6-9365-af7366977bb2

📥 Commits

Reviewing files that changed from the base of the PR and between 3527ae5 and 74710d4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • package.json
  • src/gts.ts
  • src/index.ts
  • src/server/server.ts
  • src/store.ts
  • src/types.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/types.ts
  • package.json
  • src/index.ts

Comment thread src/gts.ts
@GeraBart GeraBart requested a review from Artifizer March 6, 2026 10:31
@Artifizer Artifizer merged commit 91724e8 into GlobalTypeSystem:main Mar 6, 2026
8 checks 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.

3 participants