@graphit/cli 0.2.380 → 0.2.384
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/bin/graphit +1 -1
- package/bin/graphit.ps1 +1 -1
- package/dist/commands/ds/sql-history.d.ts +29 -0
- package/dist/commands/ds/sql-history.js +69 -0
- package/dist/commands/ds/sql-history.js.map +1 -0
- package/dist/commands/ds.js +3 -0
- package/dist/commands/ds.js.map +1 -1
- package/dist/commands/kb-batch.js +1 -1
- package/dist/commands/kb-batch.js.map +1 -1
- package/dist/commands/kb.js +2 -21
- package/dist/commands/kb.js.map +1 -1
- package/dist/skill-guard.js +0 -2
- package/dist/skill-guard.js.map +1 -1
- package/hooks/hooks.json +12 -1
- package/package.json +1 -1
- package/scripts/fixtures/kb-cutover-project293.json +13 -0
- package/scripts/plugin-status/claude-install.mjs +215 -0
- package/scripts/plugin-status/cloud-refresh.mjs +97 -0
- package/scripts/plugin-status/common.mjs +107 -0
- package/scripts/plugin-status/hook-io.mjs +200 -0
- package/scripts/plugin-status.mjs +68 -505
- package/scripts/sync-plugin-marketplace.sh +18 -0
- package/scripts/verb-policy-source.json +49 -30
- package/scripts/verify-kb-cutover.mjs +134 -0
- package/skills/graphit/SKILL.md +7 -7
- package/skills/graphit/VERSION.json +1 -1
- package/skills/graphit/references/governance.md +2 -2
- package/skills/graphit/references/kb-actions.md +5 -5
- package/skills/graphit/references/kb-discovery.md +1 -1
- package/skills/graphit/references/metric-families.md +1 -1
- package/skills/graphit/references/onboarding.md +1 -1
- package/skills/graphit/references/page-load.md +38 -0
- package/skills/graphit/references/runtime.md +5 -5
- package/skills/graphit/references/semantic-authoring.md +5 -5
- package/skills/graphit/references/share.md +4 -4
- package/skills/graphit-build/SKILL.md +1 -1
- package/skills/graphit-explore/SKILL.md +1 -1
- package/skills/graphit-share/SKILL.md +5 -5
|
@@ -20,6 +20,10 @@ sync_marketplace() {
|
|
|
20
20
|
# Project #298: the copied hook manifest must not reference absent scripts.
|
|
21
21
|
cp cli/scripts/plugin-status.mjs cli/scripts/block-legacy-setup.mjs \
|
|
22
22
|
cli/scripts/show-query-result.mjs "$target/scripts/"
|
|
23
|
+
# Feature #1042: plugin-status.mjs imports its modules from this folder; verify
|
|
24
|
+
# follows each hook script's relative imports, so a missing module fails there.
|
|
25
|
+
mkdir -p "$target/scripts/plugin-status"
|
|
26
|
+
rsync -a --delete cli/scripts/plugin-status/ "$target/scripts/plugin-status/"
|
|
23
27
|
|
|
24
28
|
# Project #246: the public plugin ships wrappers that invoke the current CLI.
|
|
25
29
|
mkdir -p "$target/bin"
|
|
@@ -98,6 +102,20 @@ for (const groups of Object.values(config.hooks)) {
|
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
}
|
|
105
|
+
// Feature #1042: a hook script's relative imports ship with it, so check them too.
|
|
106
|
+
const importPattern = /(?:\bfrom\s+|\bimport\s*\(\s*|\bimport\s+)["'](\.{1,2}\/[^"']+)["']/g;
|
|
107
|
+
const queue = [...files];
|
|
108
|
+
while (queue.length > 0) {
|
|
109
|
+
const file = queue.shift();
|
|
110
|
+
if (!file.endsWith(".mjs") || !fs.existsSync(path.join("cli", file))) continue;
|
|
111
|
+
for (const match of fs.readFileSync(path.join("cli", file), "utf8").matchAll(importPattern)) {
|
|
112
|
+
const dependency = path.posix.normalize(path.posix.join(path.posix.dirname(file), match[1]));
|
|
113
|
+
if (!files.has(dependency)) {
|
|
114
|
+
files.add(dependency);
|
|
115
|
+
queue.push(dependency);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
101
119
|
let valid = true;
|
|
102
120
|
for (const file of files) {
|
|
103
121
|
const destination = path.join(target, file);
|
|
@@ -300,22 +300,6 @@
|
|
|
300
300
|
"requires_approval": true,
|
|
301
301
|
"silent_retry_exempt": false
|
|
302
302
|
},
|
|
303
|
-
"kb verify": {
|
|
304
|
-
"surface": "both",
|
|
305
|
-
"noun": "kb_write",
|
|
306
|
-
"is_read_only": false,
|
|
307
|
-
"mutation_class": "kb",
|
|
308
|
-
"requires_approval": true,
|
|
309
|
-
"silent_retry_exempt": false
|
|
310
|
-
},
|
|
311
|
-
"kb unverify": {
|
|
312
|
-
"surface": "both",
|
|
313
|
-
"noun": "kb_write",
|
|
314
|
-
"is_read_only": false,
|
|
315
|
-
"mutation_class": "kb",
|
|
316
|
-
"requires_approval": true,
|
|
317
|
-
"silent_retry_exempt": false
|
|
318
|
-
},
|
|
319
303
|
"kb column-visibility": {
|
|
320
304
|
"surface": "both",
|
|
321
305
|
"noun": "kb_write",
|
|
@@ -440,6 +424,13 @@
|
|
|
440
424
|
"requires_approval": false,
|
|
441
425
|
"silent_retry_exempt": false
|
|
442
426
|
},
|
|
427
|
+
"ds sql-history": {
|
|
428
|
+
"surface": "both",
|
|
429
|
+
"is_read_only": true,
|
|
430
|
+
"mutation_class": "none",
|
|
431
|
+
"requires_approval": false,
|
|
432
|
+
"silent_retry_exempt": false
|
|
433
|
+
},
|
|
443
434
|
"ds usage": {
|
|
444
435
|
"surface": "both",
|
|
445
436
|
"is_read_only": true,
|
|
@@ -514,32 +505,60 @@
|
|
|
514
505
|
"reason": "Not a working verb on either surface, and there is no UI action behind it (Issue #962). A source has no home of its own: it lives in the group of the semantic model bound to it, so `kb update semantic-model` with a new group is the move. The CLI registers `ds move` only to say so."
|
|
515
506
|
},
|
|
516
507
|
"dashboard folder spaces": {
|
|
517
|
-
"surface": "both",
|
|
518
|
-
"
|
|
508
|
+
"surface": "both",
|
|
509
|
+
"noun": "dashboard",
|
|
510
|
+
"is_read_only": true,
|
|
511
|
+
"mutation_class": "none",
|
|
512
|
+
"requires_approval": false,
|
|
513
|
+
"silent_retry_exempt": false
|
|
519
514
|
},
|
|
520
515
|
"dashboard folder list": {
|
|
521
|
-
"surface": "both",
|
|
522
|
-
"
|
|
516
|
+
"surface": "both",
|
|
517
|
+
"noun": "dashboard",
|
|
518
|
+
"is_read_only": true,
|
|
519
|
+
"mutation_class": "none",
|
|
520
|
+
"requires_approval": false,
|
|
521
|
+
"silent_retry_exempt": false
|
|
523
522
|
},
|
|
524
523
|
"dashboard folder get": {
|
|
525
|
-
"surface": "both",
|
|
526
|
-
"
|
|
524
|
+
"surface": "both",
|
|
525
|
+
"noun": "dashboard",
|
|
526
|
+
"is_read_only": true,
|
|
527
|
+
"mutation_class": "none",
|
|
528
|
+
"requires_approval": false,
|
|
529
|
+
"silent_retry_exempt": false
|
|
527
530
|
},
|
|
528
531
|
"dashboard folder create": {
|
|
529
|
-
"surface": "both",
|
|
530
|
-
"
|
|
532
|
+
"surface": "both",
|
|
533
|
+
"noun": "dashboard",
|
|
534
|
+
"is_read_only": false,
|
|
535
|
+
"mutation_class": "dashboard",
|
|
536
|
+
"requires_approval": true,
|
|
537
|
+
"silent_retry_exempt": true
|
|
531
538
|
},
|
|
532
539
|
"dashboard folder update": {
|
|
533
|
-
"surface": "both",
|
|
534
|
-
"
|
|
540
|
+
"surface": "both",
|
|
541
|
+
"noun": "dashboard",
|
|
542
|
+
"is_read_only": false,
|
|
543
|
+
"mutation_class": "dashboard",
|
|
544
|
+
"requires_approval": true,
|
|
545
|
+
"silent_retry_exempt": true
|
|
535
546
|
},
|
|
536
547
|
"dashboard folder delete": {
|
|
537
|
-
"surface": "both",
|
|
538
|
-
"
|
|
548
|
+
"surface": "both",
|
|
549
|
+
"noun": "dashboard",
|
|
550
|
+
"is_read_only": false,
|
|
551
|
+
"mutation_class": "dashboard",
|
|
552
|
+
"requires_approval": true,
|
|
553
|
+
"silent_retry_exempt": true
|
|
539
554
|
},
|
|
540
555
|
"dashboard move": {
|
|
541
|
-
"surface": "both",
|
|
542
|
-
"
|
|
556
|
+
"surface": "both",
|
|
557
|
+
"noun": "dashboard",
|
|
558
|
+
"is_read_only": false,
|
|
559
|
+
"mutation_class": "dashboard",
|
|
560
|
+
"requires_approval": true,
|
|
561
|
+
"silent_retry_exempt": true
|
|
543
562
|
},
|
|
544
563
|
"dashboard list": {
|
|
545
564
|
"surface": "both",
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Bounded local fixture proof. Uses the normal CLI client/auth without modifying credentials. */
|
|
3
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
export const FIXTURE_ORG = 'zz-test-293-kb-cutover';
|
|
9
|
+
const BASE = '/api/v1/cli/kb';
|
|
10
|
+
const RETIRED_KEYS = new Set(['verified', 'verified_by', 'verified_at', 'unverified']);
|
|
11
|
+
const safeId = value => typeof value === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(value);
|
|
12
|
+
const statusCode = error => Number.isInteger(error?.status) ? error.status : null;
|
|
13
|
+
const entityOf = response => {
|
|
14
|
+
if (response?.entity && typeof response.entity === 'object') return response.entity;
|
|
15
|
+
if (response?.rule && typeof response.rule === 'object') return response.rule;
|
|
16
|
+
return response;
|
|
17
|
+
};
|
|
18
|
+
function hasRetiredMetadata(entity) {
|
|
19
|
+
return Object.keys(entity ?? {}).some(k => RETIRED_KEYS.has(k)) ||
|
|
20
|
+
Object.keys(entity?.meta?.graphit ?? {}).some(k => RETIRED_KEYS.has(k));
|
|
21
|
+
}
|
|
22
|
+
function blocked(reason) { return {scope:'kb_authoring_and_retired_client_contract',status:'blocked',reason,created:[],checks:[],cleanup:[]}; }
|
|
23
|
+
|
|
24
|
+
export async function runKBVerification({api, orgId, apiBaseUrl, runId = randomBytes(5).toString('hex')}) {
|
|
25
|
+
if (orgId !== FIXTURE_ORG) return blocked('undesignated_fixture_org');
|
|
26
|
+
let url;
|
|
27
|
+
try { url = new URL(apiBaseUrl); } catch { return blocked('invalid_api_url'); }
|
|
28
|
+
if (url.protocol !== 'http:' || !['localhost','127.0.0.1','[::1]'].includes(url.hostname) || url.username || url.password) return blocked('local_api_required');
|
|
29
|
+
if (!/^[a-z0-9]{1,16}$/.test(runId)) return blocked('invalid_run_id');
|
|
30
|
+
|
|
31
|
+
const report = {...blocked(null),status:'failed',org_id:orgId,run_id:runId};
|
|
32
|
+
report.not_run = [
|
|
33
|
+
{check:'cached_query_and_constraint_enforcement',reason:'No approved source fixture or cloud storage/KMS path supplied.'},
|
|
34
|
+
{check:'source_activation_and_breaking_schema_acceptance',reason:'No approved source fixture supplied.'},
|
|
35
|
+
{check:'scoped_non_admin_weakening_and_delete',reason:'Current CLI identity only; no separate member session supplied.'},
|
|
36
|
+
{check:'physical_tombstone_inspection',reason:'Public CRUD proves live absence only; storage inspection belongs to the cut runner.'},
|
|
37
|
+
];
|
|
38
|
+
const owned = [];
|
|
39
|
+
report.unconfirmed_writes = [];
|
|
40
|
+
try {
|
|
41
|
+
const status = await api.get('/api/v1/cli/status');
|
|
42
|
+
if (status?.identity?.org_id !== orgId) return blocked('authenticated_org_mismatch');
|
|
43
|
+
if (!['owner','admin'].includes(status?.identity?.org_role)) return blocked('fixture_admin_required');
|
|
44
|
+
// A clean run only. Names with our prefix are not ownership evidence.
|
|
45
|
+
for (const noun of ['group','semantic-model','metric','rules','template']) {
|
|
46
|
+
const page = await api.get(`${BASE}/${noun}`);
|
|
47
|
+
if (!Array.isArray(page?.items) || page.total !== 0 || page.items.length || page.truncated || page.next_cursor) return blocked('fixture_not_empty');
|
|
48
|
+
}
|
|
49
|
+
report.checks.push({check:'authenticated_empty_visible_fixture',status:'passed'});
|
|
50
|
+
const fixtures = JSON.parse(await readFile(new URL('./fixtures/kb-cutover-project293.json',import.meta.url),'utf8'));
|
|
51
|
+
const names = Object.fromEntries(['group','semantic-model','metric'].map(noun => [noun,`zz_test_293_${noun.replaceAll('-','_')}_${runId}`]));
|
|
52
|
+
names.rule = `ZZ_TEST_293_RULE_${runId.toUpperCase()}`;
|
|
53
|
+
for (const noun of ['group','semantic-model','metric','rule']) {
|
|
54
|
+
const definition = structuredClone(fixtures[noun]);
|
|
55
|
+
definition.name = names[noun];
|
|
56
|
+
if (noun === 'metric' || noun === 'semantic-model') definition.group = names.group;
|
|
57
|
+
if (noun === 'rule') definition.apply_on = [{type:'model',name:names['semantic-model']}];
|
|
58
|
+
const body = noun === 'rule' ? definition : {definition};
|
|
59
|
+
let response;
|
|
60
|
+
try { response=await api.post(`${BASE}/${noun}`,body); }
|
|
61
|
+
catch(error) {
|
|
62
|
+
if(statusCode(error)===null || statusCode(error)>=500) report.unconfirmed_writes.push({noun,name:names[noun]});
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
if(response?.detail?.applied === true && safeId(response.detail.doc_id)) {
|
|
66
|
+
const artifact={noun,name:names[noun],id:response.detail.doc_id};
|
|
67
|
+
owned.push(artifact);report.created.push({...artifact});
|
|
68
|
+
throw {status:202};
|
|
69
|
+
}
|
|
70
|
+
const created = entityOf(response);
|
|
71
|
+
if (!safeId(created?.id) || created.name !== names[noun]) throw {status:null};
|
|
72
|
+
const artifact = {noun,name:created.name,id:created.id};
|
|
73
|
+
owned.push(artifact); report.created.push({...artifact});
|
|
74
|
+
if (hasRetiredMetadata(created)) throw {status:null};
|
|
75
|
+
const readPath = noun === 'rule' ? `${BASE}/rules/${encodeURIComponent(created.id)}` : `${BASE}/${noun}/${encodeURIComponent(created.name)}`;
|
|
76
|
+
const actual = entityOf(await api.get(readPath));
|
|
77
|
+
if (actual?.id !== created.id || actual?.name !== created.name || hasRetiredMetadata(actual)) throw {status:null};
|
|
78
|
+
report.checks.push({check:`save_and_read_${noun}`,status:'passed',id:created.id});
|
|
79
|
+
}
|
|
80
|
+
const refusalCases = [
|
|
81
|
+
{check:'old_cli_unverified_false',path:`${BASE}/metric`,body:{definition:{...fixtures.metric,name:`zz_test_293_rejected_${runId}`,group:names.group},unverified:false},codes:[422]},
|
|
82
|
+
{check:'old_nested_verification',path:`${BASE}/metric`,body:{definition:{...fixtures.metric,name:`zz_test_293_rejected_${runId}`,group:names.group,meta:{graphit:{verified:false}}}},codes:[400,422]},
|
|
83
|
+
{check:'manual_verification_action',path:`${BASE}/metric/${names.metric}/verify`,body:{verified:true},codes:[404,405]},
|
|
84
|
+
];
|
|
85
|
+
for (const scenario of refusalCases) {
|
|
86
|
+
let code = null;
|
|
87
|
+
try {
|
|
88
|
+
const accepted = entityOf(await api.post(scenario.path,scenario.body));
|
|
89
|
+
if (scenario.body.definition && safeId(accepted?.id) && accepted.name === scenario.body.definition.name) {
|
|
90
|
+
const artifact={noun:'metric',name:accepted.name,id:accepted.id};
|
|
91
|
+
owned.push(artifact);report.created.push({...artifact});
|
|
92
|
+
}
|
|
93
|
+
} catch (error) { code=statusCode(error); }
|
|
94
|
+
report.checks.push({check:scenario.check,status:scenario.codes.includes(code)?'passed':'failed',http_status:code});
|
|
95
|
+
if (!scenario.codes.includes(code)) throw {status:code};
|
|
96
|
+
}
|
|
97
|
+
report.status='passed';delete report.reason;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
report.status='failed';report.reason='scenario_failed';report.http_status=statusCode(error);
|
|
100
|
+
} finally {
|
|
101
|
+
for (const artifact of owned.reverse()) {
|
|
102
|
+
const readPath = artifact.noun === 'rule' ? `${BASE}/rules/${encodeURIComponent(artifact.id)}` : `${BASE}/${artifact.noun}/${encodeURIComponent(artifact.name)}`;
|
|
103
|
+
try {
|
|
104
|
+
const current = entityOf(await api.get(readPath));
|
|
105
|
+
if (current?.id !== artifact.id || current?.name !== artifact.name) throw {status:409};
|
|
106
|
+
await api.delete(`${BASE}/${artifact.noun}/${encodeURIComponent(artifact.name)}`);
|
|
107
|
+
let absent=false;
|
|
108
|
+
try { await api.get(readPath); } catch (error) { absent=statusCode(error)===404; }
|
|
109
|
+
if (!absent) throw {status:409};
|
|
110
|
+
report.cleanup.push({...artifact,status:'deleted'});
|
|
111
|
+
} catch (error) {
|
|
112
|
+
report.cleanup.push({...artifact,status:'refused_or_failed',http_status:statusCode(error)});
|
|
113
|
+
report.status='failed';report.reason='cleanup_incomplete';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return report;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function main() {
|
|
121
|
+
const args=process.argv.slice(2);
|
|
122
|
+
if (args.length!==4 || args[0]!=='--org-id' || args[2]!=='--output') throw new Error('Usage: --org-id <fixture-org> --output <proof.json>');
|
|
123
|
+
// Lazy loading keeps mock tests independent of persisted auth and token refresh.
|
|
124
|
+
const {apiClient}=await import('../dist/api/client.js');
|
|
125
|
+
const {getApiBaseUrl}=await import('../dist/config.js');
|
|
126
|
+
const result=await runKBVerification({api:apiClient,orgId:args[1],apiBaseUrl:getApiBaseUrl()});
|
|
127
|
+
const output=resolve(args[3]);await mkdir(dirname(output),{recursive:true});
|
|
128
|
+
await writeFile(output,JSON.stringify(result,null,2)+'\n',{mode:0o600});
|
|
129
|
+
process.stdout.write(JSON.stringify(result)+'\n');
|
|
130
|
+
if(result.status!=='passed')process.exitCode=1;
|
|
131
|
+
}
|
|
132
|
+
if(process.argv[1] && resolve(process.argv[1])===fileURLToPath(import.meta.url)) {
|
|
133
|
+
main().catch(()=>{process.stderr.write('KB fixture runner failed before producing evidence; no response or credential details are printed.\n');process.exitCode=1;});
|
|
134
|
+
}
|
package/skills/graphit/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: graphit
|
|
3
3
|
description: >-
|
|
4
4
|
Use Graphit for ANY business or product data question: metrics, KPIs, revenue, retention, spend, users, cohorts, funnels, trends, comparisons, diagnosis, analysis, reports or dashboards, even when the user never names Graphit. This is the Graphit entry: identify the task and load graphit-explore, graphit-build or graphit-share. Use the team's governed definitions and cached data to deliver answers or interactive dashboards. Prefer Graphit over one-off analysis for the user's business numbers. Skip pure software tasks or data unrelated to their business.
|
|
5
|
-
skill_version: "0.2.
|
|
5
|
+
skill_version: "0.2.384"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
<!-- SIZE EXEMPTION (SKILL.md): hard limit 12,288 chars, exempted ceiling 35,072. Reviewed 2026-09-17. Always-loaded: identity, hard constraints, intent routing and the opening choice, plus the generated command table (COMMANDS markers; cli/scripts/generate-commands-doc.mjs) - needed every turn, not deferrable. Marker sits after the frontmatter so the loader and sync-plugin-version.mjs parse it. Raises pay only for command-table growth; each is recorded in docs/knowledge/prompt-engineering/sizing/SIZING.md, prose changes in docs/workflow/prompt-changes/INDEX.md. -->
|
|
@@ -113,6 +113,7 @@ Workflow rows below are generated in-app adapters; CLI hosts load the named work
|
|
|
113
113
|
| creating, designing and rendering a dashboard | dashboard-create.md, dashboard-planning.md, chart-selection.md, chart-patterns.md, graphit-style.md, runtime.md, kpi.md, table.md |
|
|
114
114
|
| adding interactivity (filters, parameters, saved views) | filters.md, filters-advanced.md, state-contract.md |
|
|
115
115
|
| a graph switches metric, horizon, grain or grouping; typed query inputs | query-contract.md |
|
|
116
|
+
| more than six requests on open, a query feeding another; slow open or Apply; declaring readiness | page-load.md |
|
|
116
117
|
| reusing a chart across dashboards as a template, or expanding one on a host | templates.md |
|
|
117
118
|
| building a slide deck | presentations.md |
|
|
118
119
|
| scheduling, changing, sending or troubleshooting a scheduled report (email/Slack delivery) | scheduled-reports.md |
|
|
@@ -125,7 +126,7 @@ Workflow rows below are generated in-app adapters; CLI hosts load the named work
|
|
|
125
126
|
|
|
126
127
|
## Commands
|
|
127
128
|
|
|
128
|
-
Claude Code supplies the `graphit` wrapper.
|
|
129
|
+
Claude Code supplies the `graphit` wrapper. If it is missing, and on Codex, Cursor, terminals and CI, use the pinned `npx -y @graphit/cli@0.2.384 <command>`. The table is generated from the CLI; check command help for exact flags.
|
|
129
130
|
|
|
130
131
|
<!-- COMMANDS:START -->
|
|
131
132
|
|
|
@@ -154,14 +155,14 @@ _Generated by `npm run gen:commands`; do not hand-edit between the markers._
|
|
|
154
155
|
- `kb repo token mint` - Mint a display-once CI machine token (org admin); scope kb:verify or kb:apply - `--scope`
|
|
155
156
|
- `kb repo token list` - List CI machine tokens: metadata only, never the secret
|
|
156
157
|
- `kb repo token revoke <token-id>` - Revoke a CI machine token (org admin)
|
|
157
|
-
- `kb batch` - Apply many metric and semantic-model creates and updates as one Knowledge Base change (up to 50 per batch; 20 in-app). Items: {op: create, noun, definition
|
|
158
|
+
- `kb batch` - Apply many metric and semantic-model creates and updates as one Knowledge Base change (up to 50 per batch; 20 in-app). Items: {op: create, noun, definition} or {op: update, noun, name, patch}; each passes the same checks as kb create/kb update and reports its own result, and readers rebuild once for the whole batch instead of once per edit. Groups, rules and deletes use their own verbs - `--file --json --stop-on-error`
|
|
158
159
|
- `kb template list` - List chart templates without their HTML
|
|
159
160
|
- `kb template get <name>` - Fetch one chart template with its HTML. What expands in every adopting dashboard
|
|
160
161
|
- `kb template create` - Create a chart template from an HTML fragment. A fragment may carry <script> and <style> and {{param}} placeholders in markup, never data-graphit-id/-sql/-ds/-label/-vocab/-state attributes: the host entity owns the query - `--name --file --json --description --params`
|
|
161
162
|
- `kb template update <name>` - Update a chart template. A new fragment reaches every adopting dashboard on its next open - `--file --json --description --params`
|
|
162
163
|
- `kb template delete <name>` - Delete a chart template (requires --yes). Adopting hosts render a missing marker - `--yes`
|
|
163
|
-
- `kb create semantic-model` - Create a semantic model from a JSON definition (dbt shape: name, model, entities, dimensions, measures, defaults, group) - `--file --json
|
|
164
|
-
- `kb create metric` - Create a metric from a JSON definition (type: simple, ratio or derived, with type_params; advanced shapes remain unavailable). --family/--axis tag a concrete member of a metric family - `--file --json --family --axis
|
|
164
|
+
- `kb create semantic-model` - Create a semantic model from a JSON definition (dbt shape: name, model, entities, dimensions, measures, defaults, group) - `--file --json`
|
|
165
|
+
- `kb create metric` - Create a metric from a JSON definition (type: simple, ratio or derived, with type_params; advanced shapes remain unavailable). --family/--axis tag a concrete member of a metric family - `--file --json --family --axis`
|
|
165
166
|
- `kb create group` - Create a group (the domain analogue; admin only) - `--name --description --owner-email --access`
|
|
166
167
|
- `kb create rule` - Create a retained Graphit governance rule from JSON. Targets use model:, entity:, dimension:, metric: or group: identities - `--file --json`
|
|
167
168
|
- `kb update <noun> <name>` - Update an asset with a JSON patch. On semantic-model, a provided entities/dimensions/measures list replaces the stored list whole; explicit meta replaces author metadata whole - `--file --json`
|
|
@@ -175,8 +176,6 @@ _Generated by `npm run gen:commands`; do not hand-edit between the markers._
|
|
|
175
176
|
- `kb family <family>` - Expand a metric family; with --axis constraints, resolve to the one concrete member (ambiguity answers with the still-open axes) - `--axis`
|
|
176
177
|
- `kb explore <noun> <name>` - Traverse semantic reach. metric shows models, entities, dimensions and variants; semantic-model/group show bound metrics, families and rules
|
|
177
178
|
- `kb usage [type] [name]` - Reverse lookup: dashboards using a semantic metric/dimension or enforcing a rule. Facets supplied by position or flags AND together - `--metric --dimension --rule`
|
|
178
|
-
- `kb verify <noun> <name>` - Verify a Knowledge Base asset
|
|
179
|
-
- `kb unverify <noun> <name>` - Unverify a Knowledge Base asset
|
|
180
179
|
- `kb column-visibility <model> <column> <state>` - Set whether queries may read one physical column of a semantic model; hidden = masked as NULL everywhere (dashboards, exports, the AI). Unhiding a PII-detector hide takes the source's creator or an org admin, is recorded with who set it, and is for false positives only
|
|
181
180
|
|
|
182
181
|
**query**
|
|
@@ -190,6 +189,7 @@ _Generated by `npm run gen:commands`; do not hand-edit between the markers._
|
|
|
190
189
|
**ds** - Data source management
|
|
191
190
|
- `ds refresh-history <id>` - Show recent refresh runs for a data source with the Snowflake query id per run (status, time, rows, duration). Runs from before query-id capture - or a failure before any query ran - show 'not captured'. Read-only; no ds refresh-history delete. - `--limit`
|
|
192
191
|
- `ds usage [id]` - Show which canvas dashboards and graphs read a data source. With an id: each dashboard you can open and its graphs, plus a count of graphs on dashboards you cannot open. Without an id: dashboard_count and graph_count for every source you can read (0 = no graph uses it). A graph counts when its data-graphit-ds names the source, its governed metrics or dimensions come from the source's semantic model, or its SQL reads the source by name. Graphs composed only in JavaScript are not seen. Read-only; ds delete still re-checks metrics and rules.
|
|
192
|
+
- `ds sql-history <id>` - Show the Source SQL versions of a data source, newest first: when, by whom, from which channel (web, cli, agent, repo) and which columns each edit added (+), removed (-) or retyped (~). --show <n> prints version n's SQL. Keeps the last 50; history starts at the first edit after it shipped. Read-only - to reuse an old version, pass its SQL to ds edit-sql. - `--show`
|
|
193
193
|
- `ds move <id>` - Not a command anywhere: a source lives in its bound semantic model's group; kb update semantic-model moves it
|
|
194
194
|
- `ds delete <id>` - Delete one of YOUR OWN private data sources (requires --yes). Shared sources are deleted in the Sources Hub, where the cascade is visible. - `--yes`
|
|
195
195
|
- `ds re-upload <id>` - Replace an uploaded CSV/Excel source's contents in place - keeps its id, graph bindings, semantic model and history; never re-create with `ds create --file`. A changed column set needs --force - `--file --force`
|
|
@@ -16,7 +16,7 @@ The governed fragment path serves simple, ratio, and derived metrics. Cumulative
|
|
|
16
16
|
|
|
17
17
|
## Trust tiers
|
|
18
18
|
|
|
19
|
-
- **governed:**
|
|
19
|
+
- **governed:** valid accessible semantic references compiled through the gateway.
|
|
20
20
|
- **verified:** known safe stored query without semantic references.
|
|
21
21
|
- **ad hoc:** raw SQL at the frontier.
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@ Prefer governed. Never present ad-hoc SQL as the team's definition.
|
|
|
24
24
|
|
|
25
25
|
## Rules
|
|
26
26
|
|
|
27
|
-
Rules target model, entity, dimension, metric, or group identities.
|
|
27
|
+
Rules target model, entity, dimension, metric, or group identities. Saved constrained rules enforce according to their mode; body-only rules guide. Scope, source readiness and deprecation still apply. Modes and EXPLORE behavior remain server-owned.
|
|
28
28
|
|
|
29
29
|
The gateway runs before caches, injects constraints, verifies resolved SQL, and returns a transparency receipt. Do not claim a rule applied merely because it exists.
|
|
30
30
|
|
|
@@ -6,7 +6,7 @@ Load when an approved gap must be authored or an existing semantic asset changed
|
|
|
6
6
|
|
|
7
7
|
For a cached data source, first read its visible scanner-created semantic model and confirm `meta.graphit.data_source.ds_id` matches the source. Add approved entities, dimensions, or measures by updating that model. If no bound model is visible, follow the scan/verify flow in `data-sources.md`; creating a second model does not bind it.
|
|
8
8
|
|
|
9
|
-
Before writing, present the missing concept, proposed root, exact definition
|
|
9
|
+
Before writing, present the missing concept, proposed root, exact definition and group/access scope. Do not write until the user approves.
|
|
10
10
|
|
|
11
11
|
## Authoring contract
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ Before writing, present the missing concept, proposed root, exact definition, gr
|
|
|
16
16
|
- Entities, dimensions, and measures mutate only through semantic-model update.
|
|
17
17
|
- A supplied nested list replaces the stored list whole. Read first and include every sibling that must remain.
|
|
18
18
|
- Explicit `meta` replaces author metadata whole. Preserve family, axes, topics, and other author fields.
|
|
19
|
-
-
|
|
19
|
+
- Valid authorized saves are effective in their permitted scope. Do not send retired verification metadata or call manual verify/unverify actions.
|
|
20
20
|
|
|
21
21
|
When create is refused because a model already binds that physical table, read the visible model named in the refusal and propose the needed update. Do not retry with another name or scope. If the response names no readable model, report the refusal without guessing or exposing a hidden target.
|
|
22
22
|
|
|
@@ -36,15 +36,15 @@ Constraints keep their five semantics: required predicate, forbidden column, req
|
|
|
36
36
|
2. Preserve complete nested and metadata structures.
|
|
37
37
|
3. Apply the smallest patch.
|
|
38
38
|
4. Re-read immediately.
|
|
39
|
-
5.
|
|
39
|
+
5. Read back the saved definition and confirm its intended scope.
|
|
40
40
|
6. Inspect receipts; a degraded write may have landed and must not be retried blindly.
|
|
41
41
|
|
|
42
42
|
When one task creates or changes several metrics or semantic models, send them as one `graphit kb batch` instead of one `kb create` or `kb update` each. Every item gets the same checks and its own result, and dashboards rebuild once for the whole batch instead of once per edit, so they keep serving while you author. A single edit stays `kb update`. Steps 1-2 still apply to every item; a batch changes the transport, not the patch discipline.
|
|
43
43
|
|
|
44
|
-
- Shape: `{"operations": [...]}` or a bare array of `{"op": "update", "noun", "name", "patch"}` and `{"op": "create", "noun", "definition"
|
|
44
|
+
- Shape: `{"operations": [...]}` or a bare array of `{"op": "update", "noun", "name", "patch"}` and `{"op": "create", "noun", "definition"}` items. Nouns are `metric` and `semantic-model` only; groups, rules and deletes keep their own verbs. Up to 50 items per batch, 20 in-app.
|
|
45
45
|
- Order items so each validates against the ones before it: a semantic model before the metrics that use its measures, a metric before a derived metric over it. Use `--stop-on-error` when later items depend on earlier ones.
|
|
46
46
|
- Items are not all-or-nothing: earlier items stay applied when a later one fails. Read `results` per item, fix what each `error` names, and re-send only the `failed` and `not_attempted` items as a new batch. Never replay the whole batch. An `unknown` item may have landed; read it back with `kb get` before sending it again.
|
|
47
|
-
-
|
|
47
|
+
- Each successful item is saved with the same validation and authorization as a single update.
|
|
48
48
|
|
|
49
49
|
## Delete
|
|
50
50
|
|
|
@@ -49,4 +49,4 @@ Use `{{ Metric('revenue') }}`, `{{ Dimension('order__channel') }}`, and `{{ Meas
|
|
|
49
49
|
|
|
50
50
|
## Gap decision
|
|
51
51
|
|
|
52
|
-
Research first, then recommend. Show which existing definitions or dashboards already cover the request, what can be reused or extended, and what is still missing. Propose authoring only for that agreed gap, with formula, grain, binding, group, rule impact and
|
|
52
|
+
Research first, then recommend. Show which existing definitions or dashboards already cover the request, what can be reused or extended, and what is still missing. Propose authoring only for that agreed gap, with formula, grain, binding, group, rule impact and validation. Ask when a choice is unresolved; do not treat a request to investigate as permission to build.
|
|
@@ -10,7 +10,7 @@ Each member carries:
|
|
|
10
10
|
- `meta.graphit.family`
|
|
11
11
|
- axis key/value pairs in `meta.graphit.axes`
|
|
12
12
|
|
|
13
|
-
Create each concrete member with the family and repeatable axis options. Tree/search/family views collapse members; `list metric` remains flat.
|
|
13
|
+
Create each concrete member with the family and repeatable axis options. For several members, send one `graphit kb batch` (kb-actions.md) whose definitions carry the same metadata: `"meta": {"graphit": {"family": "arppu", "axes": {"horizon": "d7"}}}`. Tree/search/family views collapse members; `list metric` remains flat.
|
|
14
14
|
|
|
15
15
|
Use family expansion to inspect members. Supply known axes to resolve. If several candidates remain, show open axes and ask—never guess a governed metric.
|
|
16
16
|
|
|
@@ -56,7 +56,7 @@ Shape it for the question - grain, only the columns dashboards use, low cardinal
|
|
|
56
56
|
|
|
57
57
|
## 4. Create the KB assets
|
|
58
58
|
|
|
59
|
-
The scan supplies the bound model. Explore answers without authoring definitions; Private first Build uses it and keeps a private metric only on request. Share applies the readiness gate: show missing prerequisites and proposed definitions, obtain required approval, then
|
|
59
|
+
The scan supplies the bound model. Explore answers without authoring definitions; Private first Build uses it and keeps a private metric only on request. Share applies the readiness gate: show missing prerequisites and proposed definitions, obtain required approval, then save and read back via kb-structure.md and kb-actions.md. Onboarding does not override the selected workflow.
|
|
60
60
|
|
|
61
61
|
## 5. Offer a dashboard
|
|
62
62
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Page Load: Request Graph and Readiness
|
|
2
|
+
|
|
3
|
+
Load when a dashboard sends more than six requests on open (charts, option lists, bounds) or has a query feeding another (a rank feeding a series, bounds feeding a date window); when its open or Apply time is slow or measured; or when declaring readiness. `runtime.md` still owns the resolve API, the entity contract and the rate budget.
|
|
4
|
+
|
|
5
|
+
Every `graphit.resolve()` and chrome call (`cascade`, `dataBounds`, `rank`) pays a fixed server cost of up to about a second, and the SDK sends at most six at once, in call order. Load time is roughly that cost times the rounds a page waits through, so shape the request graph before tuning SQL.
|
|
6
|
+
|
|
7
|
+
## The request graph
|
|
8
|
+
|
|
9
|
+
- **Parallel unless the rows are needed.** Start independent queries together in one `Promise.all`. Await one query before another only when the later query's SQL or params use the earlier result. A chain of awaits whose later steps ignore earlier results turns one round into many.
|
|
10
|
+
- **Fold a dependency when you can.** A rank that only picks the top N for a series is usually one statement: rank in a CTE and join the series to it. Keep two steps when the first result is also shown.
|
|
11
|
+
- **Visible first.** Start above-the-fold charts and KPIs before option lists, bounds for secondary controls and anything below the fold; later calls wait behind earlier ones.
|
|
12
|
+
- **Hidden tabs wait** until first shown (`runtime.md`, "Declare statically, execute lazily").
|
|
13
|
+
- **One first load.** `graphit.state.subscribe` calls back immediately: guard the callback with a boot flag and call `refresh()` once when init ends (`state-contract.md`, "Restore order"). An unguarded callback plus a boot refresh runs every query twice.
|
|
14
|
+
- **Redraw, don't re-resolve.** Resize, highlight, legend, axis and zero-axis toggles change drawing only: keep the last result and redraw from it. Resolve again only when a filter, param or variant changes the query.
|
|
15
|
+
|
|
16
|
+
## Declaring readiness
|
|
17
|
+
|
|
18
|
+
Declare what "loaded" means so the page reports its open and Apply time. Readiness never delays drawing, and a missed acknowledgement only records a timeout after two minutes. Misuse does throw, so build `targets` from the same ids you pass tokens for.
|
|
19
|
+
|
|
20
|
+
1. At the start of the open and of each Apply, call `graphit.readiness.begin(kind, targets)`: `kind` is `'initial'` or `'apply'`, `targets` the `data-graphit-id`s this load paints (1-128, unique). A hidden tab's entities are not targets until it opens. Each Apply supersedes the open and the previous Apply. `begin` throws on another kind or a bad list, and `ready.target(id)` on an id missing from `targets`.
|
|
21
|
+
2. Pass `readiness: ready.target(id)` to each `graphit.resolve`, `dataBounds` or `rank` call for that target and to its `graphit.graph`, `table` or `kpi` render, which acknowledges the paint itself. After drawing custom DOM or options, call `ready.target(id).rendered()`. A `bind` keeps the token it was registered with, so it reports only that first load; give Apply targets through `resolve`.
|
|
22
|
+
3. Call `ready.stateReady()` once the filters and params this load uses are applied, and `ready.fail()` if the load fails.
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
function load(kind) {
|
|
26
|
+
const ready = graphit.readiness.begin(kind, ['spend-trend', 'spend-kpi']);
|
|
27
|
+
ready.stateReady(); // this page applies its state before load()
|
|
28
|
+
return Promise.all([
|
|
29
|
+
graphit.resolve({ target: '#spend-trend', readiness: ready.target('spend-trend') })
|
|
30
|
+
.then(r => graphit.graph('#spend-trend-chart', { type: 'line', data: r.data, x: 'day', y: 'spend',
|
|
31
|
+
readiness: ready.target('spend-trend') })),
|
|
32
|
+
graphit.resolve({ target: '#spend-kpi', readiness: ready.target('spend-kpi') })
|
|
33
|
+
.then(r => graphit.kpi('#spend-kpi-card', { value: r.data[0]?.spend ?? 0, readiness: ready.target('spend-kpi') })),
|
|
34
|
+
]).catch(() => ready.fail());
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Redraw-only toggles begin nothing. Never run a query just to measure readiness.
|
|
@@ -140,11 +140,11 @@ A resolve query following these shapes serves from a semantic cache in roughly 1
|
|
|
140
140
|
|
|
141
141
|
`graphit.resolve()` is rate-limited per user per dashboard: 360 requests a minute, 180 of them cold executions (a cache hit is not one). Design for that budget:
|
|
142
142
|
|
|
143
|
-
- **Single refresh function.** Put all queries in ONE `Promise.all` inside one `refresh()` so they share a time window. NEVER scatter `graphit.resolve()` across independent
|
|
144
|
-
- **Count queries per interaction.** 6 charts
|
|
145
|
-
- **Reuse trend data for KPIs.** If you already fetch a weekly time series, derive the KPI total and its sparkline from that result in JS
|
|
146
|
-
- **Avoid redundant refreshes.** If a filter affects only some charts, split into targeted refresh functions (`refreshKPIs()`, `refreshCharts()`).
|
|
147
|
-
- **No polling.** NEVER use `setInterval(refresh, ...)`. Data sources
|
|
143
|
+
- **Single refresh function.** Put all queries in ONE `Promise.all` inside one `refresh()` so they share a time window. NEVER scatter `graphit.resolve()` across independent handlers or timeouts - one user action becomes several bursts.
|
|
144
|
+
- **Count queries per interaction.** 6 charts cost 6 cold executions per filter change, about 30 changes a minute; 12 charts, about 15. With 10 or more charts and 3 or more filters, debounce filter changes (300ms).
|
|
145
|
+
- **Reuse trend data for KPIs.** If you already fetch a weekly time series, derive the KPI total and its sparkline from that result in JS, not a separate aggregate query. Anchor the extra graphs it feeds with `targetEntityIds` per the attribution rule above. Canonical KPI-row example: `kpi.md`.
|
|
146
|
+
- **Avoid redundant refreshes.** If a filter affects only some charts, split into targeted refresh functions (`refreshKPIs()`, `refreshCharts()`). Resize, highlight and axis toggles only redraw; no resolve (`page-load.md`).
|
|
147
|
+
- **No polling.** NEVER use `setInterval(refresh, ...)`. Data sources refresh on their own schedule; polling burns the whole budget.
|
|
148
148
|
|
|
149
149
|
## Helper index
|
|
150
150
|
|
|
@@ -22,7 +22,7 @@ Graphit extensions and assets:
|
|
|
22
22
|
- topics are curated Graphit metadata, deliberately not dbt `tags`
|
|
23
23
|
- rules are separate Graphit objects targeting semantic identities by name;
|
|
24
24
|
never embed them in dbt metadata
|
|
25
|
-
-
|
|
25
|
+
- provenance and data-source bindings are
|
|
26
26
|
server-owned; use dedicated actions instead of hand-authoring them
|
|
27
27
|
|
|
28
28
|
Do not expose or depend on storage collection names, revision fields, feature
|
|
@@ -76,7 +76,7 @@ Measure identity is group/model/measure, never a bare name. A metric's measure r
|
|
|
76
76
|
|
|
77
77
|
Explore authors no definitions. Private first Build creates a metric only on "keep": use the scanner model's exact private group (`kb-scope.md`), with no group-agreement or shared-reuse approval round. The comparisons below still apply. In Share, follow `kb-discovery.md`: agree the group, inspect assets/cross-group matches and present reuse-or-build. Before missing inputs, discover visible candidates in the target group/model. Summaries only nominate candidates; follow continuation metadata before declaring a gap. Ranked search or an incomplete page is not proof of absence.
|
|
78
78
|
|
|
79
|
-
Read each plausible metric's full definition and its reached semantic models. Compare the resolved model/source binding, grain and time dimension, measure expression and aggregation parameters, metric-level and per-input filters, units/scale,
|
|
79
|
+
Read each plausible metric's full definition and its reached semantic models. Compare the resolved model/source binding, grain and time dimension, measure expression and aggregation parameters, metric-level and per-input filters, units/scale, ownership, and applicable rules. Similar names or identical SQL alone are insufficient. Use the existing path resolution above; never inspect hidden definitions or copy a private definition into a shared scope to make it reusable.
|
|
80
80
|
|
|
81
81
|
| Finding | Action |
|
|
82
82
|
|---|---|
|
|
@@ -85,13 +85,13 @@ Read each plausible metric's full definition and its reached semantic models. Co
|
|
|
85
85
|
| Different grain, filters, scale, binding or applicable policy | Keep the definitions separate; ask if the intended business meaning is unclear |
|
|
86
86
|
| Repository-owned definition needs a change | Follow the repository authoring workflow; do not create a direct-write replacement to bypass ownership |
|
|
87
87
|
|
|
88
|
-
A ratio still references numerator/denominator metric objects. For example, a
|
|
88
|
+
A ratio still references numerator/denominator metric objects. For example, a saved total-matches metric can serve several ratios at the same grain; a country-filtered matches metric is not an interchangeable denominator for all countries. Being referenced or ending in `_num`/`_den` does not make an existing metric disposable.
|
|
89
89
|
|
|
90
|
-
After discovery, author only the approved missing prerequisites: group, data sources, semantic models with nested components, simple metrics, ratio/derived metrics, then rules after their targets exist. Execute one
|
|
90
|
+
After discovery, author only the approved missing prerequisites: group, data sources, semantic models with nested components, simple metrics, ratio/derived metrics, then rules after their targets exist. Execute in that order: metrics and semantic models as one ordered `graphit kb batch` (kb-actions.md); start any other item only after the current receipt is terminal.
|
|
91
91
|
|
|
92
92
|
## Verification
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
Valid authorized saves are effective within their access scope. Manual KB verification and draft status are retired; omit `unverified`, `verified`, `verified_by`, and `verified_at`. Source readiness, schema acceptance and SQL validation still apply.
|
|
95
95
|
|
|
96
96
|
## Final check
|
|
97
97
|
|
|
@@ -20,9 +20,9 @@ Use the **draft door** for shared dashboards and the **share plan** for private
|
|
|
20
20
|
|
|
21
21
|
## Shared-scope checks
|
|
22
22
|
|
|
23
|
-
Read kb-scope.md for effective permissions and exact placement, kb-discovery.md for staged reuse discovery, and semantic-authoring.md plus kb-actions.md for supported definitions, equivalence and
|
|
23
|
+
Read kb-scope.md for effective permissions and exact placement, kb-discovery.md for staged reuse discovery, and semantic-authoring.md plus kb-actions.md for supported definitions, equivalence and validation. Resolve the audience, lowercase group and uppercase policy key from current `status` and returned `domain_keys`; carry forward choices already made. Read access is the ceiling for writes, and a status result is advisory, never a grant.
|
|
24
24
|
|
|
25
|
-
**KB-readiness gate:** before work goes live for others, confirm the required models, nested components, metrics, groups and rules exist and
|
|
25
|
+
**KB-readiness gate:** before work goes live for others, confirm the required models, nested components, metrics, groups and rules exist, are valid and are readable in the intended scope. If a business measure is missing, present its gap and proposed governed definition for approval, then save and read back the approved prerequisites. An ad-hoc business measure can be unavailable to governed-only viewers; do not silently publish it as a reusable governed answer. Compare actual binding, grain, time dimension, aggregation, filters, units and policy, not just names or SQL. A same-named conflicting asset is not equivalent: explain the difference and resolve the consequential choice. A truly equivalent accessible asset should be reused.
|
|
26
26
|
|
|
27
27
|
Choose dashboard audience and folder through dashboard-create.md when sharing. Owners share their own; org admins/owners also any they can see. Org needs admin/owner; Team needs membership. When needed, explain Private/ORG/named scopes via kb-scope.md; keep dashboard audience separate.
|
|
28
28
|
|
|
@@ -37,9 +37,9 @@ Report 409 (another editor), 423 (locked) and 403 (view-only) with the returned
|
|
|
37
37
|
## Share plan: private to shared
|
|
38
38
|
|
|
39
39
|
1. With the requested audience and placement established and the content checked, attempt `dashboard share` on the same ID. A success needs audience and placement readback; a private-dependency refusal supplies `blockers` and `remediation_options`. Read sharing-recovery.md. Respect `blockers_truncated` and uncertainty; the visible list is not proof of a complete closure when eligibility could not be verified. For shapes b/c without a dashboard, inspect the selected assets and their visible dependencies directly; do not invent a share-preview endpoint.
|
|
40
|
-
2. For each returned private dependency, choose **reuse**, **move**, or **create**. Reuse an accessible shared equivalent only after inspecting its full definition. Move an approved model or metric with `kb update semantic-model` or `kb update metric` and the target `group`; the bound source's home follows its model,
|
|
40
|
+
2. For each returned private dependency, choose **reuse**, **move**, or **create**. Reuse an accessible shared equivalent only after inspecting its full definition. Move an approved model or metric with `kb update semantic-model` or `kb update metric` and the target `group`, several in one ordered `kb batch`; the bound source's home follows its model, no command moves a source alone. Create only a genuinely missing, supported definition through `kb create`, never a copy to evade ownership or a refusal. Include naming collisions and visibility impact.
|
|
41
41
|
3. Present the whole plan as **one structured ask**: exact assets, reuse comparisons, moves/new definitions, affected audience, group, rules and destination. Carry forward existing authorization; ask for the additional effects or consequential choices not yet approved. An authorization to share a dashboard alone does not silently authorize broadening every source's audience.
|
|
42
|
-
4. Apply the approved dependency order
|
|
42
|
+
4. Apply the approved dependency order. Read each item's terminal receipt and re-read the resulting definition/binding before the next step. When reusing a shared asset, rewrite each `data-graphit-*` attribute naming the replaced asset through `dashboard update-html`, preserving unrelated content. Run the readiness checks on the resulting references and pre-flight the canvas. `dashboard check` is not proof of sharing eligibility.
|
|
43
43
|
5. Retry the original `dashboard share` with the agreed space/team and `--folder-path`. Verify the same ID's audience through `dashboard list` and placement through the destination folder listing. For definitions/source-only work, read back the exact group, binding and requested configuration instead of claiming a dashboard was shared.
|
|
44
44
|
6. On intermediate failure, report what applied, what remains and the returned next step. Preserve successful work. Reconcile uncertain writes by reading state before retrying; do not repeat a non-retryable operation unchanged, silently fall back to another folder, or create a replacement dashboard/source.
|
|
45
45
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: graphit-build
|
|
3
3
|
description: >-
|
|
4
4
|
Author and verify Graphit dashboard content, private or shared, and build private reports, sources and saved metrics. Pair with graphit-share for shared dependencies, draft sessions and publication. Use graphit-explore for answers without artifacts.
|
|
5
|
-
skill_version: "0.2.
|
|
5
|
+
skill_version: "0.2.384"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Build: author and verify content
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: graphit-explore
|
|
3
3
|
description: >-
|
|
4
4
|
Answer, explain or diagnose business data using Graphit. Use after Graphit routing or for a direct Graphit question, including reads of shared dashboards. Does not authorize creating reusable definitions or sharing; use graphit-build to keep a private artifact and graphit-share for shared writes.
|
|
5
|
-
skill_version: "0.2.
|
|
5
|
+
skill_version: "0.2.384"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Explore: answer the question
|