Skip to content

[spec] FieldSchema accepts a lookup/master_detail with no reference target, though its own TSDoc calls the key required #13632

Description

@claude

Measured while implementing #13222 part (2). Filed unassigned by a domain:engine dev seat; not a blocker for that card.

The gap

FieldSchema's own TSDoc for reference says, at packages/spec/src/data/field.zod.ts:1001-1003:

Used by lookup and master_detail field types to define cross-object references.
The reference property is required for these types

The schema does not enforce it. Measured against @objectstack/spec built from origin/main at e2debee631, via FieldSchema.safeParse:

fixtureverdict
{ name:'company_id', type:'lookup', reference:'company' }success: true
{ name:'company_id', type:'lookup' } — no referencesuccess: true
{ name:'company_id', type:'lookup', reference:'' }success: true
{ name:'p', type:'master_detail', reference:'company' }success: true
positive control — { type:'lookup', reference_to:'company' }success: false, unrecognized_keys

The control proves the harness really refuses things, so the three trues are real acceptances and not a dead parse.

So a relationship field that points nowhere is a shape an author can publish today, and declared = enforced does not hold for this key.

Why it is worth closing

  • A lookup with no target is not actionable downstream. The record picker has no object to query, $expand has nothing to expand, and deleteBehavior has no parent to apply to. Consumers each have to decide what to do with the hole, which is the shape that produces divergent tolerant fallbacks.
  • It reaches the physical schema. Since driver-mongodb indexes lookup joins off reference_to — a key the spec REFUSES — so no authored lookup field has ever been indexed on MongoDB #13222 part (2), driver-mongodb builds idx_FIELD_lookup for a lookup carrying reference and declines when it is absent or empty — a correct call, but it means the unenforced key now silently decides whether an index exists.
  • AI-authored metadata is the population most exposed. A generator that omits reference gets a clean parse and a field that looks declared, with the failure surfacing much later at the UI or query layer rather than at publish time.

Options, not a recommendation to adopt as-is

  1. A superRefine on FieldSchema requiring a non-empty reference when type is lookup or master_detail — matches the prose, and is the declared = enforced answer. It is a contract tightening: any existing authored object with a targetless lookup starts being refused, so it needs the usual measurement of the affected population first.
  2. Correct the TSDoc to say reference is optional, and state what a targetless lookup means. Cheaper, but keeps a key whose absence nothing checks.

Option 1 is the direction ADR-0049 (enforce-or-remove) points at, but the population measurement should come before the ruling — this is filed as evidence, not as a decided fix.

Reproduce

pnpm --filter @objectstack/spec build
node -e "const {FieldSchema}=require('./packages/spec/dist/data/index.js'); console.log(FieldSchema.safeParse({name:'company_id',type:'lookup'}).success)"

Related

#13222 (where this was measured) · ADR-0049 enforce-or-remove


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[spec] `FieldSchema` accepts a `lookup`/`master_detail` with no `reference` target, though its own TSDoc calls the key required · Issue #13632 · objectstack-ai/objectstack · GitHub
Skip to content

[spec] FieldSchema accepts a lookup/master_detail with no reference target, though its own TSDoc calls the key required #13632

Description

@claude

Measured while implementing #13222 part (2). Filed unassigned by a domain:engine dev seat; not a blocker for that card.

The gap

FieldSchema's own TSDoc for reference says, at packages/spec/src/data/field.zod.ts:1001-1003:

Used by lookup and master_detail field types to define cross-object references.
The reference property is required for these types

The schema does not enforce it. Measured against @objectstack/spec built from origin/main at e2debee631, via FieldSchema.safeParse:

fixtureverdict
{ name:'company_id', type:'lookup', reference:'company' }success: true
{ name:'company_id', type:'lookup' } — no referencesuccess: true
{ name:'company_id', type:'lookup', reference:'' }success: true
{ name:'p', type:'master_detail', reference:'company' }success: true
positive control — { type:'lookup', reference_to:'company' }success: false, unrecognized_keys

The control proves the harness really refuses things, so the three trues are real acceptances and not a dead parse.

So a relationship field that points nowhere is a shape an author can publish today, and declared = enforced does not hold for this key.

Why it is worth closing

  • A lookup with no target is not actionable downstream. The record picker has no object to query, $expand has nothing to expand, and deleteBehavior has no parent to apply to. Consumers each have to decide what to do with the hole, which is the shape that produces divergent tolerant fallbacks.
  • It reaches the physical schema. Since driver-mongodb indexes lookup joins off reference_to — a key the spec REFUSES — so no authored lookup field has ever been indexed on MongoDB #13222 part (2), driver-mongodb builds idx_FIELD_lookup for a lookup carrying reference and declines when it is absent or empty — a correct call, but it means the unenforced key now silently decides whether an index exists.
  • AI-authored metadata is the population most exposed. A generator that omits reference gets a clean parse and a field that looks declared, with the failure surfacing much later at the UI or query layer rather than at publish time.

Options, not a recommendation to adopt as-is

  1. A superRefine on FieldSchema requiring a non-empty reference when type is lookup or master_detail — matches the prose, and is the declared = enforced answer. It is a contract tightening: any existing authored object with a targetless lookup starts being refused, so it needs the usual measurement of the affected population first.
  2. Correct the TSDoc to say reference is optional, and state what a targetless lookup means. Cheaper, but keeps a key whose absence nothing checks.

Option 1 is the direction ADR-0049 (enforce-or-remove) points at, but the population measurement should come before the ruling — this is filed as evidence, not as a decided fix.

Reproduce

pnpm --filter @objectstack/spec build
node -e "const {FieldSchema}=require('./packages/spec/dist/data/index.js'); console.log(FieldSchema.safeParse({name:'company_id',type:'lookup'}).success)"

Related

#13222 (where this was measured) · ADR-0049 enforce-or-remove


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [spec] `FieldSchema` accepts a `lookup`/`master_detail` with no `reference` target, though its own TSDoc calls the key required · Issue #13632 · objectstack-ai/objectstack · GitHub
Skip to content

[spec] FieldSchema accepts a lookup/master_detail with no reference target, though its own TSDoc calls the key required #13632

Description

@claude

Measured while implementing #13222 part (2). Filed unassigned by a domain:engine dev seat; not a blocker for that card.

The gap

FieldSchema's own TSDoc for reference says, at packages/spec/src/data/field.zod.ts:1001-1003:

Used by lookup and master_detail field types to define cross-object references.
The reference property is required for these types

The schema does not enforce it. Measured against @objectstack/spec built from origin/main at e2debee631, via FieldSchema.safeParse:

fixtureverdict
{ name:'company_id', type:'lookup', reference:'company' }success: true
{ name:'company_id', type:'lookup' } — no referencesuccess: true
{ name:'company_id', type:'lookup', reference:'' }success: true
{ name:'p', type:'master_detail', reference:'company' }success: true
positive control — { type:'lookup', reference_to:'company' }success: false, unrecognized_keys

The control proves the harness really refuses things, so the three trues are real acceptances and not a dead parse.

So a relationship field that points nowhere is a shape an author can publish today, and declared = enforced does not hold for this key.

Why it is worth closing

  • A lookup with no target is not actionable downstream. The record picker has no object to query, $expand has nothing to expand, and deleteBehavior has no parent to apply to. Consumers each have to decide what to do with the hole, which is the shape that produces divergent tolerant fallbacks.
  • It reaches the physical schema. Since driver-mongodb indexes lookup joins off reference_to — a key the spec REFUSES — so no authored lookup field has ever been indexed on MongoDB #13222 part (2), driver-mongodb builds idx_FIELD_lookup for a lookup carrying reference and declines when it is absent or empty — a correct call, but it means the unenforced key now silently decides whether an index exists.
  • AI-authored metadata is the population most exposed. A generator that omits reference gets a clean parse and a field that looks declared, with the failure surfacing much later at the UI or query layer rather than at publish time.

Options, not a recommendation to adopt as-is

  1. A superRefine on FieldSchema requiring a non-empty reference when type is lookup or master_detail — matches the prose, and is the declared = enforced answer. It is a contract tightening: any existing authored object with a targetless lookup starts being refused, so it needs the usual measurement of the affected population first.
  2. Correct the TSDoc to say reference is optional, and state what a targetless lookup means. Cheaper, but keeps a key whose absence nothing checks.

Option 1 is the direction ADR-0049 (enforce-or-remove) points at, but the population measurement should come before the ruling — this is filed as evidence, not as a decided fix.

Reproduce

pnpm --filter @objectstack/spec build
node -e "const {FieldSchema}=require('./packages/spec/dist/data/index.js'); console.log(FieldSchema.safeParse({name:'company_id',type:'lookup'}).success)"

Related

#13222 (where this was measured) · ADR-0049 enforce-or-remove


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [spec] `FieldSchema` accepts a `lookup`/`master_detail` with no `reference` target, though its own TSDoc calls the key required · Issue #13632 · objectstack-ai/objectstack · GitHub
Skip to content

[spec] FieldSchema accepts a lookup/master_detail with no reference target, though its own TSDoc calls the key required #13632

Description

@claude

Measured while implementing #13222 part (2). Filed unassigned by a domain:engine dev seat; not a blocker for that card.

The gap

FieldSchema's own TSDoc for reference says, at packages/spec/src/data/field.zod.ts:1001-1003:

Used by lookup and master_detail field types to define cross-object references.
The reference property is required for these types

The schema does not enforce it. Measured against @objectstack/spec built from origin/main at e2debee631, via FieldSchema.safeParse:

fixtureverdict
{ name:'company_id', type:'lookup', reference:'company' }success: true
{ name:'company_id', type:'lookup' } — no referencesuccess: true
{ name:'company_id', type:'lookup', reference:'' }success: true
{ name:'p', type:'master_detail', reference:'company' }success: true
positive control — { type:'lookup', reference_to:'company' }success: false, unrecognized_keys

The control proves the harness really refuses things, so the three trues are real acceptances and not a dead parse.

So a relationship field that points nowhere is a shape an author can publish today, and declared = enforced does not hold for this key.

Why it is worth closing

  • A lookup with no target is not actionable downstream. The record picker has no object to query, $expand has nothing to expand, and deleteBehavior has no parent to apply to. Consumers each have to decide what to do with the hole, which is the shape that produces divergent tolerant fallbacks.
  • It reaches the physical schema. Since driver-mongodb indexes lookup joins off reference_to — a key the spec REFUSES — so no authored lookup field has ever been indexed on MongoDB #13222 part (2), driver-mongodb builds idx_FIELD_lookup for a lookup carrying reference and declines when it is absent or empty — a correct call, but it means the unenforced key now silently decides whether an index exists.
  • AI-authored metadata is the population most exposed. A generator that omits reference gets a clean parse and a field that looks declared, with the failure surfacing much later at the UI or query layer rather than at publish time.

Options, not a recommendation to adopt as-is

  1. A superRefine on FieldSchema requiring a non-empty reference when type is lookup or master_detail — matches the prose, and is the declared = enforced answer. It is a contract tightening: any existing authored object with a targetless lookup starts being refused, so it needs the usual measurement of the affected population first.
  2. Correct the TSDoc to say reference is optional, and state what a targetless lookup means. Cheaper, but keeps a key whose absence nothing checks.

Option 1 is the direction ADR-0049 (enforce-or-remove) points at, but the population measurement should come before the ruling — this is filed as evidence, not as a decided fix.

Reproduce

pnpm --filter @objectstack/spec build
node -e "const {FieldSchema}=require('./packages/spec/dist/data/index.js'); console.log(FieldSchema.safeParse({name:'company_id',type:'lookup'}).success)"

Related

#13222 (where this was measured) · ADR-0049 enforce-or-remove


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [spec] `FieldSchema` accepts a `lookup`/`master_detail` with no `reference` target, though its own TSDoc calls the key required · Issue #13632 · objectstack-ai/objectstack · GitHub
Skip to content

[spec] FieldSchema accepts a lookup/master_detail with no reference target, though its own TSDoc calls the key required #13632

Description

@claude

Measured while implementing #13222 part (2). Filed unassigned by a domain:engine dev seat; not a blocker for that card.

The gap

FieldSchema's own TSDoc for reference says, at packages/spec/src/data/field.zod.ts:1001-1003:

Used by lookup and master_detail field types to define cross-object references.
The reference property is required for these types

The schema does not enforce it. Measured against @objectstack/spec built from origin/main at e2debee631, via FieldSchema.safeParse:

fixtureverdict
{ name:'company_id', type:'lookup', reference:'company' }success: true
{ name:'company_id', type:'lookup' } — no referencesuccess: true
{ name:'company_id', type:'lookup', reference:'' }success: true
{ name:'p', type:'master_detail', reference:'company' }success: true
positive control — { type:'lookup', reference_to:'company' }success: false, unrecognized_keys

The control proves the harness really refuses things, so the three trues are real acceptances and not a dead parse.

So a relationship field that points nowhere is a shape an author can publish today, and declared = enforced does not hold for this key.

Why it is worth closing

  • A lookup with no target is not actionable downstream. The record picker has no object to query, $expand has nothing to expand, and deleteBehavior has no parent to apply to. Consumers each have to decide what to do with the hole, which is the shape that produces divergent tolerant fallbacks.
  • It reaches the physical schema. Since driver-mongodb indexes lookup joins off reference_to — a key the spec REFUSES — so no authored lookup field has ever been indexed on MongoDB #13222 part (2), driver-mongodb builds idx_FIELD_lookup for a lookup carrying reference and declines when it is absent or empty — a correct call, but it means the unenforced key now silently decides whether an index exists.
  • AI-authored metadata is the population most exposed. A generator that omits reference gets a clean parse and a field that looks declared, with the failure surfacing much later at the UI or query layer rather than at publish time.

Options, not a recommendation to adopt as-is

  1. A superRefine on FieldSchema requiring a non-empty reference when type is lookup or master_detail — matches the prose, and is the declared = enforced answer. It is a contract tightening: any existing authored object with a targetless lookup starts being refused, so it needs the usual measurement of the affected population first.
  2. Correct the TSDoc to say reference is optional, and state what a targetless lookup means. Cheaper, but keeps a key whose absence nothing checks.

Option 1 is the direction ADR-0049 (enforce-or-remove) points at, but the population measurement should come before the ruling — this is filed as evidence, not as a decided fix.

Reproduce

pnpm --filter @objectstack/spec build
node -e "const {FieldSchema}=require('./packages/spec/dist/data/index.js'); console.log(FieldSchema.safeParse({name:'company_id',type:'lookup'}).success)"

Related

#13222 (where this was measured) · ADR-0049 enforce-or-remove


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [spec] `FieldSchema` accepts a `lookup`/`master_detail` with no `reference` target, though its own TSDoc calls the key required · Issue #13632 · objectstack-ai/objectstack · GitHub
Skip to content

[spec] FieldSchema accepts a lookup/master_detail with no reference target, though its own TSDoc calls the key required #13632

Description

@claude

Measured while implementing #13222 part (2). Filed unassigned by a domain:engine dev seat; not a blocker for that card.

The gap

FieldSchema's own TSDoc for reference says, at packages/spec/src/data/field.zod.ts:1001-1003:

Used by lookup and master_detail field types to define cross-object references.
The reference property is required for these types

The schema does not enforce it. Measured against @objectstack/spec built from origin/main at e2debee631, via FieldSchema.safeParse:

fixtureverdict
{ name:'company_id', type:'lookup', reference:'company' }success: true
{ name:'company_id', type:'lookup' } — no referencesuccess: true
{ name:'company_id', type:'lookup', reference:'' }success: true
{ name:'p', type:'master_detail', reference:'company' }success: true
positive control — { type:'lookup', reference_to:'company' }success: false, unrecognized_keys

The control proves the harness really refuses things, so the three trues are real acceptances and not a dead parse.

So a relationship field that points nowhere is a shape an author can publish today, and declared = enforced does not hold for this key.

Why it is worth closing

  • A lookup with no target is not actionable downstream. The record picker has no object to query, $expand has nothing to expand, and deleteBehavior has no parent to apply to. Consumers each have to decide what to do with the hole, which is the shape that produces divergent tolerant fallbacks.
  • It reaches the physical schema. Since driver-mongodb indexes lookup joins off reference_to — a key the spec REFUSES — so no authored lookup field has ever been indexed on MongoDB #13222 part (2), driver-mongodb builds idx_FIELD_lookup for a lookup carrying reference and declines when it is absent or empty — a correct call, but it means the unenforced key now silently decides whether an index exists.
  • AI-authored metadata is the population most exposed. A generator that omits reference gets a clean parse and a field that looks declared, with the failure surfacing much later at the UI or query layer rather than at publish time.

Options, not a recommendation to adopt as-is

  1. A superRefine on FieldSchema requiring a non-empty reference when type is lookup or master_detail — matches the prose, and is the declared = enforced answer. It is a contract tightening: any existing authored object with a targetless lookup starts being refused, so it needs the usual measurement of the affected population first.
  2. Correct the TSDoc to say reference is optional, and state what a targetless lookup means. Cheaper, but keeps a key whose absence nothing checks.

Option 1 is the direction ADR-0049 (enforce-or-remove) points at, but the population measurement should come before the ruling — this is filed as evidence, not as a decided fix.

Reproduce

pnpm --filter @objectstack/spec build
node -e "const {FieldSchema}=require('./packages/spec/dist/data/index.js'); console.log(FieldSchema.safeParse({name:'company_id',type:'lookup'}).success)"

Related

#13222 (where this was measured) · ADR-0049 enforce-or-remove


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); [spec] `FieldSchema` accepts a `lookup`/`master_detail` with no `reference` target, though its own TSDoc calls the key required · Issue #13632 · objectstack-ai/objectstack · GitHub
Skip to content

[spec] FieldSchema accepts a lookup/master_detail with no reference target, though its own TSDoc calls the key required #13632

Description

@claude

Measured while implementing #13222 part (2). Filed unassigned by a domain:engine dev seat; not a blocker for that card.

The gap

FieldSchema's own TSDoc for reference says, at packages/spec/src/data/field.zod.ts:1001-1003:

Used by lookup and master_detail field types to define cross-object references.
The reference property is required for these types

The schema does not enforce it. Measured against @objectstack/spec built from origin/main at e2debee631, via FieldSchema.safeParse:

fixtureverdict
{ name:'company_id', type:'lookup', reference:'company' }success: true
{ name:'company_id', type:'lookup' } — no referencesuccess: true
{ name:'company_id', type:'lookup', reference:'' }success: true
{ name:'p', type:'master_detail', reference:'company' }success: true
positive control — { type:'lookup', reference_to:'company' }success: false, unrecognized_keys

The control proves the harness really refuses things, so the three trues are real acceptances and not a dead parse.

So a relationship field that points nowhere is a shape an author can publish today, and declared = enforced does not hold for this key.

Why it is worth closing

  • A lookup with no target is not actionable downstream. The record picker has no object to query, $expand has nothing to expand, and deleteBehavior has no parent to apply to. Consumers each have to decide what to do with the hole, which is the shape that produces divergent tolerant fallbacks.
  • It reaches the physical schema. Since driver-mongodb indexes lookup joins off reference_to — a key the spec REFUSES — so no authored lookup field has ever been indexed on MongoDB #13222 part (2), driver-mongodb builds idx_FIELD_lookup for a lookup carrying reference and declines when it is absent or empty — a correct call, but it means the unenforced key now silently decides whether an index exists.
  • AI-authored metadata is the population most exposed. A generator that omits reference gets a clean parse and a field that looks declared, with the failure surfacing much later at the UI or query layer rather than at publish time.

Options, not a recommendation to adopt as-is

  1. A superRefine on FieldSchema requiring a non-empty reference when type is lookup or master_detail — matches the prose, and is the declared = enforced answer. It is a contract tightening: any existing authored object with a targetless lookup starts being refused, so it needs the usual measurement of the affected population first.
  2. Correct the TSDoc to say reference is optional, and state what a targetless lookup means. Cheaper, but keeps a key whose absence nothing checks.

Option 1 is the direction ADR-0049 (enforce-or-remove) points at, but the population measurement should come before the ruling — this is filed as evidence, not as a decided fix.

Reproduce

pnpm --filter @objectstack/spec build
node -e "const {FieldSchema}=require('./packages/spec/dist/data/index.js'); console.log(FieldSchema.safeParse({name:'company_id',type:'lookup'}).success)"

Related

#13222 (where this was measured) · ADR-0049 enforce-or-remove


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions