@sbissoli/mcp-provenance 0.1.0

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/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # @sbissoli/mcp-provenance
2
+
3
+ Contrato de proveniência para servidores MCP: todo retorno de tool carrega **fonte,
4
+ endpoint, período, data de extração e licença**, com serialização determinística, em dois
5
+ modos — `concise` (padrão, piso legal) e `detailed` (bloco canônico completo).
6
+
7
+ > Mantido para o meu portfólio de servidores MCP. Uso por terceiros é bem-vindo, mas o
8
+ > roadmap segue as necessidades dos meus servidores.
9
+
10
+ Especificação completa: [`docs/contrato-proveniencia-v1.md`](docs/contrato-proveniencia-v1.md).
11
+
12
+ ## Uso
13
+
14
+ ```ts
15
+ import { createProvenanceContext } from "@sbissoli/mcp-provenance";
16
+
17
+ // 1. Uma vez, na inicialização do servidor:
18
+ const prov = createProvenanceContext({
19
+ metaNamespace: "com.sidneybissoli.senado", // chaves de _meta (reverse-DNS, estável)
20
+ locale: "pt-BR", // idioma do rodapé ("pt-BR" | "en" | LocaleSpec)
21
+ timezone: { offset: "-03:00", label: "horário de Brasília" }, // default: "utc"
22
+ defaultMode: "concise", // default: "concise"
23
+ });
24
+
25
+ // 2. Presets por fonte upstream (opcional):
26
+ const SENADO_LEGIS = {
27
+ source: "Senado Federal — Dados Abertos (Legislativo)",
28
+ citation: "Fonte: Senado Federal, Portal de Dados Abertos (Legislativo) — legis.senado.leg.br/dadosabertos.",
29
+ license: "Dados Abertos do Senado Federal — uso livre com atribuição da fonte.",
30
+ };
31
+
32
+ // 3. Em cada tool:
33
+ const p = prov.from(SENADO_LEGIS, {
34
+ source_url: `${baseUrl}/processo.json`,
35
+ retrieved_at: fetchedAt, // instante REAL da extração (preservado pelo cache)
36
+ data_vintage: "2025",
37
+ });
38
+ return prov.result(shapedData, p); // modo concise (default)
39
+ return prov.result(shapedData, p, { mode: "detailed" }); // bloco canônico completo
40
+ ```
41
+
42
+ `result()` emite os três canais: `structuredContent` (bloco + `attribution` RFC #711,
43
+ visível ao modelo), `_meta` namespaced (auditoria/UI, zero tokens) e rodapé de texto
44
+ compacto para clientes text-only.
45
+
46
+ ### Fonte estruturada (ex.: ILOSTAT/SDMX)
47
+
48
+ ```ts
49
+ const p = prov.build({
50
+ source: { name: "ILOSTAT", agency: "ILO", database: "ILOSTAT", endpoint: "https://sdmx.ilo.org/rest" },
51
+ dataset: { id: "DF_UNE_DEAP_SEX_AGE_RT", version: "1.0", name: "Unemployment rate by sex and age" },
52
+ dimension_key: { REF_AREA: "BRA", SEX: "SEX_F", TIME_PERIOD: "2024" },
53
+ data_vintage: "2026-06-15",
54
+ retrieved_at: fetchedAt,
55
+ source_url: canonicalRestUrl,
56
+ license: { id: "CC-BY-4.0", url: "https://creativecommons.org/licenses/by/4.0/", verified_at: "2026-08-04" },
57
+ citation: "International Labour Organization, ILOSTAT, https://ilostat.ilo.org/data/, accessed 2026-08-04.",
58
+ });
59
+ ```
60
+
61
+ ### Multi-fonte (segregação de licenças)
62
+
63
+ ```ts
64
+ // Um bloco POR FONTE; dados de cada fonte em estruturas separadas apontando para o seu bloco.
65
+ return prov.result({ ilostat: dadosIlo, uis: dadosUis }, [pIlo, pUis]);
66
+ ```
67
+
68
+ ### Recortes múltiplos de uma mesma fonte
69
+
70
+ ```ts
71
+ const p = prov.build({ ...base, field_sources: [
72
+ { fields: ["relatoria"], source_url: urlRelatoria, retrieved_at: fetchedAtRelatoria },
73
+ ]});
74
+ ```
75
+
76
+ ## Regras que a lib impõe (server-side, antes de responder)
77
+
78
+ - `license` com ao menos `id` ou `name` (piso legal);
79
+ - `derived: true` exige `derivation_note`;
80
+ - chaves em ordem fixa e ausência como `null` explícito (determinismo byte-a-byte por modo);
81
+ - timestamps ISO-8601 sem milissegundos, normalizados ao fuso configurado (datas puras
82
+ passam intactas — nunca inventa horário num vintage).
83
+
84
+ ## API
85
+
86
+ `createProvenanceContext(options)` → `{ build, from, render, footer, result, metaKeys }`.
87
+ Peças soltas também exportadas: `CanonicalProvenanceSchema`, `renderConcise`,
88
+ `renderDetailed`, `attributionList`, `provenanceFooter`, `toCanonicalIso`, locales
89
+ `ptBR`/`en`.
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Fábrica de contexto: o servidor cria UM contexto na inicialização (namespace de
3
+ * `_meta`, idioma, fuso, modo default) e as tools usam os helpers ligados a ele.
4
+ *
5
+ * Três canais para o mesmo envelope (estratégia herdada do senado-br-mcp):
6
+ * 1. `structuredContent.provenance` + `structuredContent.attribution` — canal
7
+ * PARSEÁVEL e visível ao modelo (para que ele cite fonte/período/extração).
8
+ * 2. `_meta` sob chaves namespaced — metadado out-of-band (auditoria/UI), não custa
9
+ * tokens do modelo; forward-compat com a Extensions Track de trust/attribution do
10
+ * MCP (RFC #711 → PR #1913).
11
+ * 3. Linha de fonte compacta anexada ao `content` textual, para clientes text-only.
12
+ *
13
+ * O bloco embutido nos canais 1 e 2 é a PROJEÇÃO do modo da resposta (concise por
14
+ * padrão — decisão 5); `attribution` é sempre a lista canônica de `source_url`.
15
+ */
16
+ import { type LocaleSpec } from "./locale.js";
17
+ import { type ConciseBlock, type DetailedBlock, type ProvenanceMode } from "./render.js";
18
+ import { type CanonicalProvenance, type ProvenanceInput } from "./schema.js";
19
+ import { type TimezoneSpec } from "./time.js";
20
+ export interface ProvenanceContextOptions {
21
+ /**
22
+ * Namespace reverse-DNS das chaves de `_meta` (ex.: "com.sidneybissoli.senado").
23
+ * Evita colisão com os namespaces reservados do MCP (`modelcontextprotocol.io/`,
24
+ * `mcp.*`) e com `openai/...`. Manter estável — consumidores leem por estas chaves.
25
+ */
26
+ metaNamespace: string;
27
+ /** Idioma do rodapé humano — "pt-BR", "en" ou um LocaleSpec customizado. */
28
+ locale?: string | LocaleSpec;
29
+ /** Fuso de serialização dos timestamps. Default: "utc". */
30
+ timezone?: TimezoneSpec;
31
+ /** Modo default das respostas. Default: "concise" (decisão 5). */
32
+ defaultMode?: ProvenanceMode;
33
+ }
34
+ /** Preset de fonte: campos fixos por fonte upstream; o restante vem por chamada. */
35
+ export type SourcePreset = Pick<ProvenanceInput, "source" | "citation" | "license"> & Partial<Pick<ProvenanceInput, "dataset" | "api_version" | "notices">>;
36
+ export interface ProvenanceResult {
37
+ content: Array<{
38
+ type: "text";
39
+ text: string;
40
+ }>;
41
+ structuredContent: Record<string, unknown> & {
42
+ provenance: ConciseBlock | DetailedBlock | Array<ConciseBlock | DetailedBlock>;
43
+ attribution: string[];
44
+ };
45
+ _meta: Record<string, unknown>;
46
+ }
47
+ export interface ProvenanceContext {
48
+ metaKeys: {
49
+ provenance: string;
50
+ attribution: string;
51
+ };
52
+ locale: LocaleSpec;
53
+ timezone: TimezoneSpec;
54
+ defaultMode: ProvenanceMode;
55
+ /** Valida e normaliza a entrada no modelo canônico (fuso aplicado a retrieved_at). */
56
+ build(input: ProvenanceInput): CanonicalProvenance;
57
+ /** `build` a partir de um preset de fonte + campos por chamada. */
58
+ from(preset: SourcePreset, perCall: Omit<ProvenanceInput, keyof SourcePreset> & Partial<SourcePreset>): CanonicalProvenance;
59
+ /** Projeção do bloco no modo pedido (default do contexto). */
60
+ render(p: CanonicalProvenance, mode?: ProvenanceMode): ConciseBlock | DetailedBlock;
61
+ /** Rodapé humano para os blocos, no idioma/fuso do contexto. */
62
+ footer(p: CanonicalProvenance | CanonicalProvenance[], mode?: ProvenanceMode): string;
63
+ /** Resultado MCP completo: os três canais. `data` deve ser um objeto. */
64
+ result(data: Record<string, unknown>, provenance: CanonicalProvenance | CanonicalProvenance[], opts?: {
65
+ mode?: ProvenanceMode;
66
+ }): ProvenanceResult;
67
+ }
68
+ export declare function createProvenanceContext(options: ProvenanceContextOptions): ProvenanceContext;
69
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAiC,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE7E,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,kEAAkE;IAClE,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED,oFAAoF;AACpF,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC,GACjF,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC;AAExE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAC3C,UAAU,EAAE,YAAY,GAAG,aAAa,GAAG,KAAK,CAAC,YAAY,GAAG,aAAa,CAAC,CAAC;QAC/E,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;IACF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,EAAE,cAAc,CAAC;IAC5B,sFAAsF;IACtF,KAAK,CAAC,KAAK,EAAE,eAAe,GAAG,mBAAmB,CAAC;IACnD,mEAAmE;IACnE,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,YAAY,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,mBAAmB,CAAC;IAC5H,8DAA8D;IAC9D,MAAM,CAAC,CAAC,EAAE,mBAAmB,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,YAAY,GAAG,aAAa,CAAC;IACpF,gEAAgE;IAChE,MAAM,CAAC,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC;IACtF,yEAAyE;IACzE,MAAM,CACJ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,UAAU,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,EACvD,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,cAAc,CAAA;KAAE,GAC/B,gBAAgB,CAAC;CACrB;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CA+D5F"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Fábrica de contexto: o servidor cria UM contexto na inicialização (namespace de
3
+ * `_meta`, idioma, fuso, modo default) e as tools usam os helpers ligados a ele.
4
+ *
5
+ * Três canais para o mesmo envelope (estratégia herdada do senado-br-mcp):
6
+ * 1. `structuredContent.provenance` + `structuredContent.attribution` — canal
7
+ * PARSEÁVEL e visível ao modelo (para que ele cite fonte/período/extração).
8
+ * 2. `_meta` sob chaves namespaced — metadado out-of-band (auditoria/UI), não custa
9
+ * tokens do modelo; forward-compat com a Extensions Track de trust/attribution do
10
+ * MCP (RFC #711 → PR #1913).
11
+ * 3. Linha de fonte compacta anexada ao `content` textual, para clientes text-only.
12
+ *
13
+ * O bloco embutido nos canais 1 e 2 é a PROJEÇÃO do modo da resposta (concise por
14
+ * padrão — decisão 5); `attribution` é sempre a lista canônica de `source_url`.
15
+ */
16
+ import { provenanceFooter } from "./footer.js";
17
+ import { resolveLocale } from "./locale.js";
18
+ import { attributionList, renderProvenance, } from "./render.js";
19
+ import { assertSemantics, CanonicalProvenanceSchema, expandInput, } from "./schema.js";
20
+ import { timezoneLabel, toCanonicalIso } from "./time.js";
21
+ export function createProvenanceContext(options) {
22
+ const locale = resolveLocale(options.locale ?? "pt-BR");
23
+ const timezone = options.timezone ?? "utc";
24
+ const defaultMode = options.defaultMode ?? "concise";
25
+ const ns = options.metaNamespace.replace(/\/+$/, "");
26
+ if (!ns)
27
+ throw new Error("metaNamespace é obrigatório (ex.: \"com.exemplo.meuservidor\")");
28
+ const metaKeys = { provenance: `${ns}/provenance`, attribution: `${ns}/attribution` };
29
+ const tzLabel = timezoneLabel(timezone);
30
+ function build(input) {
31
+ const retrievedAtIso = toCanonicalIso(input.retrieved_at ?? new Date(), timezone);
32
+ const expanded = expandInput(input, retrievedAtIso);
33
+ if (expanded.field_sources) {
34
+ expanded.field_sources = expanded.field_sources.map((fs) => fs.retrieved_at ? { ...fs, retrieved_at: toCanonicalIso(fs.retrieved_at, timezone) } : fs);
35
+ }
36
+ const parsed = CanonicalProvenanceSchema.parse(expanded);
37
+ assertSemantics(parsed);
38
+ return parsed;
39
+ }
40
+ function from(preset, perCall) {
41
+ return build({ ...preset, ...perCall });
42
+ }
43
+ function render(p, mode) {
44
+ return renderProvenance(p, mode ?? defaultMode);
45
+ }
46
+ function footer(p, mode) {
47
+ const blocks = Array.isArray(p) ? p : [p];
48
+ return provenanceFooter(blocks, { locale, tzLabel, mode: mode ?? defaultMode });
49
+ }
50
+ function result(data, provenance, opts) {
51
+ const mode = opts?.mode ?? defaultMode;
52
+ const blocks = Array.isArray(provenance) ? provenance : [provenance];
53
+ if (blocks.length === 0)
54
+ throw new Error("result() exige ao menos um bloco de proveniência");
55
+ const rendered = blocks.map((b) => renderProvenance(b, mode));
56
+ const provOut = Array.isArray(provenance) ? rendered : rendered[0];
57
+ const attribution = attributionList(blocks);
58
+ return {
59
+ content: [
60
+ { type: "text", text: JSON.stringify(data, null, 2) },
61
+ { type: "text", text: footer(blocks, mode) },
62
+ ],
63
+ structuredContent: { ...data, provenance: provOut, attribution },
64
+ _meta: {
65
+ [metaKeys.provenance]: provOut,
66
+ [metaKeys.attribution]: attribution,
67
+ },
68
+ };
69
+ }
70
+ return { metaKeys, locale, timezone, defaultMode, build, from, render, footer, result };
71
+ }
72
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Rodapé de fonte — o canal para clientes que só renderizam texto.
3
+ *
4
+ * Formato (uma dupla de linhas por fonte, respeitando a segregação por licença):
5
+ *
6
+ * ---
7
+ * Fonte: {nome} · {url} · dados de {vintage} · extraído em {timestamp humano}
8
+ * Licença: {licença}.
9
+ * A referência completa desta informação pode ser solicitada nesta própria conversa.
10
+ *
11
+ * O aviso final (requestNotice) aparece UMA vez, apenas no modo `concise` — no modo
12
+ * `detailed` a referência completa já está na própria resposta, e o aviso seria ruído.
13
+ */
14
+ import type { LocaleSpec } from "./locale.js";
15
+ import type { ProvenanceMode } from "./render.js";
16
+ import type { CanonicalProvenance } from "./schema.js";
17
+ export declare function provenanceFooter(blocks: CanonicalProvenance[], opts: {
18
+ locale: LocaleSpec;
19
+ tzLabel: string;
20
+ mode: ProvenanceMode;
21
+ }): string;
22
+ //# sourceMappingURL=footer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../src/footer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,mBAAmB,EAAE,EAC7B,IAAI,EAAE;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GAClE,MAAM,CAaR"}
package/dist/footer.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Rodapé de fonte — o canal para clientes que só renderizam texto.
3
+ *
4
+ * Formato (uma dupla de linhas por fonte, respeitando a segregação por licença):
5
+ *
6
+ * ---
7
+ * Fonte: {nome} · {url} · dados de {vintage} · extraído em {timestamp humano}
8
+ * Licença: {licença}.
9
+ * A referência completa desta informação pode ser solicitada nesta própria conversa.
10
+ *
11
+ * O aviso final (requestNotice) aparece UMA vez, apenas no modo `concise` — no modo
12
+ * `detailed` a referência completa já está na própria resposta, e o aviso seria ruído.
13
+ */
14
+ import { conciseLicense } from "./render.js";
15
+ export function provenanceFooter(blocks, opts) {
16
+ const { locale, tzLabel, mode } = opts;
17
+ const lines = ["---"];
18
+ for (const p of blocks) {
19
+ const parts = [`${locale.sourceLabel}: ${p.source.name}`, p.source_url];
20
+ if (p.data_vintage)
21
+ parts.push(`${locale.vintagePrefix} ${p.data_vintage}`);
22
+ parts.push(`${locale.retrievedPrefix} ${locale.formatTimestamp(p.retrieved_at, tzLabel)}`);
23
+ lines.push(parts.join(" · "));
24
+ const license = conciseLicense(p.license);
25
+ if (license)
26
+ lines.push(`${locale.licenseLabel}: ${license.endsWith(".") ? license : `${license}.`}`);
27
+ }
28
+ if (mode === "concise")
29
+ lines.push(locale.requestNotice);
30
+ return lines.join("\n");
31
+ }
32
+ //# sourceMappingURL=footer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../src/footer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,MAAM,UAAU,gBAAgB,CAC9B,MAA6B,EAC7B,IAAmE;IAEnE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IACvC,MAAM,KAAK,GAAa,CAAC,KAAK,CAAC,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,WAAW,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;QACxE,IAAI,CAAC,CAAC,YAAY;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,aAAa,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,eAAe,IAAI,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;QAC3F,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9B,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,EAAE,CAAC,CAAC;IACxG,CAAC;IACD,IAAI,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACzD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { CONTRACT_VERSION, CanonicalProvenanceSchema, DatasetSchema, FieldSourceSchema, LicenseSchema, ProvenanceContractError, SourceSchema, type CanonicalProvenance, type FieldSource, type ProvenanceInput, } from "./schema.js";
2
+ export { attributionList, conciseLicense, renderConcise, renderDetailed, renderProvenance, type ConciseBlock, type DetailedBlock, type ProvenanceMode, } from "./render.js";
3
+ export { en, locales, ptBR, resolveLocale, type LocaleSpec } from "./locale.js";
4
+ export { provenanceFooter } from "./footer.js";
5
+ export { parseOffsetMinutes, timezoneLabel, toCanonicalIso, type TimezoneSpec } from "./time.js";
6
+ export { createProvenanceContext, type ProvenanceContext, type ProvenanceContextOptions, type ProvenanceResult, type SourcePreset, } from "./context.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,uBAAuB,EACvB,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,eAAe,EACf,cAAc,EACd,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AACjG,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { CONTRACT_VERSION, CanonicalProvenanceSchema, DatasetSchema, FieldSourceSchema, LicenseSchema, ProvenanceContractError, SourceSchema, } from "./schema.js";
2
+ export { attributionList, conciseLicense, renderConcise, renderDetailed, renderProvenance, } from "./render.js";
3
+ export { en, locales, ptBR, resolveLocale } from "./locale.js";
4
+ export { provenanceFooter } from "./footer.js";
5
+ export { parseOffsetMinutes, timezoneLabel, toCanonicalIso } from "./time.js";
6
+ export { createProvenanceContext, } from "./context.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,uBAAuB,EACvB,YAAY,GAIb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,eAAe,EACf,cAAc,EACd,aAAa,EACb,cAAc,EACd,gBAAgB,GAIjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAmB,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,cAAc,EAAqB,MAAM,WAAW,CAAC;AACjG,OAAO,EACL,uBAAuB,GAKxB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Princípio de linguagem (decisão 5 da Fase 0): o rodapé fala com o LEITOR, o schema
3
+ * fala com o agente. O aviso no corpo da resposta é em linguagem simples, registro
4
+ * formal/institucional (público: pesquisadores, jornalistas, órgãos públicos), no
5
+ * idioma do servidor, sem jargão técnico ("proveniência", "response_format", inglês
6
+ * voltado ao usuário final) e sem coloquialidade. O aviso deixa explícito que a
7
+ * referência completa se solicita AQUI MESMO, na conversa — não em portal, e-mail ou
8
+ * outro canal.
9
+ *
10
+ * A redação pt-BR do aviso é a fixada na decisão 5; não alterar sem decisão nova.
11
+ */
12
+ export interface LocaleSpec {
13
+ id: string;
14
+ /** "Fonte" / "Source" */
15
+ sourceLabel: string;
16
+ /** Prefixo do vintage: "dados de" / "data as of" */
17
+ vintagePrefix: string;
18
+ /** Prefixo da extração: "extraído em" / "retrieved on" */
19
+ retrievedPrefix: string;
20
+ /** "Licença" / "License" */
21
+ licenseLabel: string;
22
+ /** Aviso ao leitor de que a referência completa pode ser pedida na própria conversa. */
23
+ requestNotice: string;
24
+ /** Versão humana de um timestamp canônico ISO-8601 (datas puras sem hora). */
25
+ formatTimestamp(iso: string, tzLabel: string): string;
26
+ }
27
+ export declare const ptBR: LocaleSpec;
28
+ export declare const en: LocaleSpec;
29
+ export declare const locales: Record<string, LocaleSpec>;
30
+ /** Resolve um id de locale embutido ou aceita um LocaleSpec customizado. */
31
+ export declare function resolveLocale(locale: string | LocaleSpec): LocaleSpec;
32
+ //# sourceMappingURL=locale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../src/locale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;IACxB,4BAA4B;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,aAAa,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACvD;AAID,eAAO,MAAM,IAAI,EAAE,UAalB,CAAC;AAEF,eAAO,MAAM,EAAE,EAAE,UAahB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAyB,CAAC;AAEzE,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,CAOrE"}
package/dist/locale.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Princípio de linguagem (decisão 5 da Fase 0): o rodapé fala com o LEITOR, o schema
3
+ * fala com o agente. O aviso no corpo da resposta é em linguagem simples, registro
4
+ * formal/institucional (público: pesquisadores, jornalistas, órgãos públicos), no
5
+ * idioma do servidor, sem jargão técnico ("proveniência", "response_format", inglês
6
+ * voltado ao usuário final) e sem coloquialidade. O aviso deixa explícito que a
7
+ * referência completa se solicita AQUI MESMO, na conversa — não em portal, e-mail ou
8
+ * outro canal.
9
+ *
10
+ * A redação pt-BR do aviso é a fixada na decisão 5; não alterar sem decisão nova.
11
+ */
12
+ const ISO_RE = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?/;
13
+ export const ptBR = {
14
+ id: "pt-BR",
15
+ sourceLabel: "Fonte",
16
+ vintagePrefix: "dados de",
17
+ retrievedPrefix: "extraído em",
18
+ licenseLabel: "Licença",
19
+ requestNotice: "A referência completa desta informação pode ser solicitada nesta própria conversa.",
20
+ formatTimestamp(iso, tzLabel) {
21
+ const m = iso.match(ISO_RE);
22
+ if (!m)
23
+ return iso;
24
+ const [, y, mo, d, hh, mm] = m;
25
+ return hh !== undefined ? `${d}/${mo}/${y} às ${hh}:${mm} (${tzLabel})` : `${d}/${mo}/${y}`;
26
+ },
27
+ };
28
+ export const en = {
29
+ id: "en",
30
+ sourceLabel: "Source",
31
+ vintagePrefix: "data as of",
32
+ retrievedPrefix: "retrieved on",
33
+ licenseLabel: "License",
34
+ requestNotice: "The complete reference for this information can be requested here, in this same conversation.",
35
+ formatTimestamp(iso, tzLabel) {
36
+ const m = iso.match(ISO_RE);
37
+ if (!m)
38
+ return iso;
39
+ const [, y, mo, d, hh, mm] = m;
40
+ return hh !== undefined ? `${y}-${mo}-${d}, ${hh}:${mm} (${tzLabel})` : `${y}-${mo}-${d}`;
41
+ },
42
+ };
43
+ export const locales = { "pt-BR": ptBR, en };
44
+ /** Resolve um id de locale embutido ou aceita um LocaleSpec customizado. */
45
+ export function resolveLocale(locale) {
46
+ if (typeof locale !== "string")
47
+ return locale;
48
+ const found = locales[locale];
49
+ if (!found) {
50
+ throw new Error(`Locale desconhecido: "${locale}" (embutidos: ${Object.keys(locales).join(", ")}); passe um LocaleSpec customizado`);
51
+ }
52
+ return found;
53
+ }
54
+ //# sourceMappingURL=locale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.js","sourceRoot":"","sources":["../src/locale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAkBH,MAAM,MAAM,GAAG,+CAA+C,CAAC;AAE/D,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,OAAO;IACX,WAAW,EAAE,OAAO;IACpB,aAAa,EAAE,UAAU;IACzB,eAAe,EAAE,aAAa;IAC9B,YAAY,EAAE,SAAS;IACvB,aAAa,EAAE,oFAAoF;IACnG,eAAe,CAAC,GAAG,EAAE,OAAO;QAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QACnB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;IAC9F,CAAC;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,EAAE,GAAe;IAC5B,EAAE,EAAE,IAAI;IACR,WAAW,EAAE,QAAQ;IACrB,aAAa,EAAE,YAAY;IAC3B,eAAe,EAAE,cAAc;IAC/B,YAAY,EAAE,SAAS;IACvB,aAAa,EAAE,+FAA+F;IAC9G,eAAe,CAAC,GAAG,EAAE,OAAO;QAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QACnB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;IAC5F,CAAC;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAA+B,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAEzE,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,MAA2B;IACvD,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,iBAAiB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACvI,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Projeções por modo do bloco de proveniência (decisão 5 da Fase 0).
3
+ *
4
+ * - `concise` (padrão): piso legal + citação mínima — fonte, URL canônica, vintage,
5
+ * data de extração, citação/atribuição, licença. Seis chaves, sempre presentes,
6
+ * `null` explícito quando desconhecido.
7
+ * - `detailed`: bloco canônico completo do contrato v1.0, todas as chaves em ordem
8
+ * fixa, ausência = `null` explícito.
9
+ *
10
+ * DETERMINISMO: os objetos são construídos literalmente em ordem fixa de chaves —
11
+ * `JSON.stringify` preserva a ordem de inserção, logo a mesma consulta sobre a mesma
12
+ * versão dos dados produz bloco byte-idêntico exceto pelos campos de timestamp
13
+ * (`retrieved_at`, citação que embute data). Não reordenar campos: a ordem é contrato.
14
+ */
15
+ import type { CanonicalProvenance, FieldSource } from "./schema.js";
16
+ export type ProvenanceMode = "concise" | "detailed";
17
+ /** Projeção concise — chaves e ordem fazem parte do contrato. */
18
+ export interface ConciseBlock {
19
+ source: string;
20
+ source_url: string;
21
+ data_vintage: string | null;
22
+ retrieved_at: string;
23
+ citation: string;
24
+ license: string | null;
25
+ }
26
+ /** Rótulo curto da licença para o modo concise: `id` quando há, senão `name`. */
27
+ export declare function conciseLicense(license: CanonicalProvenance["license"]): string | null;
28
+ export declare function renderConcise(p: CanonicalProvenance): ConciseBlock;
29
+ /** Projeção detailed — o bloco canônico completo, ordem fixa, nulls explícitos. */
30
+ export interface DetailedBlock {
31
+ contract_version: string;
32
+ source: {
33
+ name: string;
34
+ agency: string | null;
35
+ database: string | null;
36
+ endpoint: string | null;
37
+ };
38
+ dataset: {
39
+ id: string | null;
40
+ version: string | null;
41
+ name: string | null;
42
+ };
43
+ dimension_key: Record<string, string> | null;
44
+ data_vintage: string | null;
45
+ retrieved_at: string;
46
+ source_url: string;
47
+ api_version: string | null;
48
+ license: {
49
+ id: string | null;
50
+ name: string | null;
51
+ url: string | null;
52
+ terms_url: string | null;
53
+ verified_at: string | null;
54
+ };
55
+ citation: string;
56
+ notices: string[];
57
+ derived: boolean;
58
+ derivation_note: string | null;
59
+ served_from_cache: boolean | null;
60
+ field_sources: FieldSource[] | null;
61
+ }
62
+ export declare function renderDetailed(p: CanonicalProvenance): DetailedBlock;
63
+ export declare function renderProvenance(p: CanonicalProvenance, mode: ProvenanceMode): ConciseBlock | DetailedBlock;
64
+ /**
65
+ * Lista canônica de fontes no formato da RFC `attribution` do MCP
66
+ * (modelcontextprotocol#711): todas as `source_url` distintas da resposta, incluindo
67
+ * as de `field_sources`, na ordem de primeira aparição.
68
+ */
69
+ export declare function attributionList(blocks: CanonicalProvenance[]): string[];
70
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,UAAU,CAAC;AAEpD,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,CAErF;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,mBAAmB,GAAG,YAAY,CASlE;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAClG,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC5E,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC7C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;QAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5B,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;CACrC;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,mBAAmB,GAAG,aAAa,CAqCpE;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,mBAAmB,EAAE,IAAI,EAAE,cAAc,GAAG,YAAY,GAAG,aAAa,CAE3G;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,EAAE,CAMvE"}
package/dist/render.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Projeções por modo do bloco de proveniência (decisão 5 da Fase 0).
3
+ *
4
+ * - `concise` (padrão): piso legal + citação mínima — fonte, URL canônica, vintage,
5
+ * data de extração, citação/atribuição, licença. Seis chaves, sempre presentes,
6
+ * `null` explícito quando desconhecido.
7
+ * - `detailed`: bloco canônico completo do contrato v1.0, todas as chaves em ordem
8
+ * fixa, ausência = `null` explícito.
9
+ *
10
+ * DETERMINISMO: os objetos são construídos literalmente em ordem fixa de chaves —
11
+ * `JSON.stringify` preserva a ordem de inserção, logo a mesma consulta sobre a mesma
12
+ * versão dos dados produz bloco byte-idêntico exceto pelos campos de timestamp
13
+ * (`retrieved_at`, citação que embute data). Não reordenar campos: a ordem é contrato.
14
+ */
15
+ /** Rótulo curto da licença para o modo concise: `id` quando há, senão `name`. */
16
+ export function conciseLicense(license) {
17
+ return license.id ?? license.name;
18
+ }
19
+ export function renderConcise(p) {
20
+ return {
21
+ source: p.source.name,
22
+ source_url: p.source_url,
23
+ data_vintage: p.data_vintage,
24
+ retrieved_at: p.retrieved_at,
25
+ citation: p.citation,
26
+ license: conciseLicense(p.license),
27
+ };
28
+ }
29
+ export function renderDetailed(p) {
30
+ return {
31
+ contract_version: p.contract_version,
32
+ source: {
33
+ name: p.source.name,
34
+ agency: p.source.agency,
35
+ database: p.source.database,
36
+ endpoint: p.source.endpoint,
37
+ },
38
+ dataset: { id: p.dataset.id, version: p.dataset.version, name: p.dataset.name },
39
+ dimension_key: p.dimension_key,
40
+ data_vintage: p.data_vintage,
41
+ retrieved_at: p.retrieved_at,
42
+ source_url: p.source_url,
43
+ api_version: p.api_version,
44
+ license: {
45
+ id: p.license.id,
46
+ name: p.license.name,
47
+ url: p.license.url,
48
+ terms_url: p.license.terms_url,
49
+ verified_at: p.license.verified_at,
50
+ },
51
+ citation: p.citation,
52
+ notices: [...p.notices],
53
+ derived: p.derived,
54
+ derivation_note: p.derivation_note,
55
+ served_from_cache: p.served_from_cache,
56
+ field_sources: p.field_sources
57
+ ? p.field_sources.map((fs) => ({
58
+ fields: [...fs.fields],
59
+ source_url: fs.source_url,
60
+ dataset_id: fs.dataset_id,
61
+ data_vintage: fs.data_vintage,
62
+ retrieved_at: fs.retrieved_at,
63
+ }))
64
+ : null,
65
+ };
66
+ }
67
+ export function renderProvenance(p, mode) {
68
+ return mode === "concise" ? renderConcise(p) : renderDetailed(p);
69
+ }
70
+ /**
71
+ * Lista canônica de fontes no formato da RFC `attribution` do MCP
72
+ * (modelcontextprotocol#711): todas as `source_url` distintas da resposta, incluindo
73
+ * as de `field_sources`, na ordem de primeira aparição.
74
+ */
75
+ export function attributionList(blocks) {
76
+ const urls = [];
77
+ for (const p of blocks) {
78
+ urls.push(p.source_url, ...(p.field_sources?.map((fs) => fs.source_url) ?? []));
79
+ }
80
+ return [...new Set(urls)];
81
+ }
82
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Modelo canônico de proveniência — contrato v1.0 do portfólio.
3
+ *
4
+ * Generalização de duas linhagens em produção/rascunho:
5
+ * - envelope nível-1 do senado-br-mcp-cloudflare (`src/utils/provenance.ts`) — vira a
6
+ * projeção `concise`;
7
+ * - bloco canônico v0.1 do ilostat (`docs/03-contrato-proveniencia.md`) — vira a
8
+ * projeção `detailed`.
9
+ *
10
+ * O servidor constrói UM modelo canônico por fonte (validado aqui, server-side); a
11
+ * projeção por modo acontece em `render.ts`. Regra de segregação: um bloco refere-se a
12
+ * exatamente UMA fonte — resposta multi-fonte carrega um bloco por fonte, e os dados de
13
+ * cada fonte ficam em estruturas separadas apontando para seu bloco (não contaminar
14
+ * licenças distintas, ex.: CC BY vs. CC BY-SA).
15
+ */
16
+ import { z } from "zod";
17
+ /** Versão do contrato de proveniência implementado por esta lib. */
18
+ export declare const CONTRACT_VERSION = "1.0";
19
+ /** Identidade da fonte. `name` é o nome humano oficial; os demais refinam quando existem. */
20
+ export declare const SourceSchema: z.ZodObject<{
21
+ name: z.ZodString;
22
+ agency: z.ZodDefault<z.ZodNullable<z.ZodString>>;
23
+ database: z.ZodDefault<z.ZodNullable<z.ZodString>>;
24
+ endpoint: z.ZodDefault<z.ZodNullable<z.ZodString>>;
25
+ }, z.core.$strip>;
26
+ /** Identidade do conjunto de dados dentro da fonte (dataflow SDMX, tabela, série…). */
27
+ export declare const DatasetSchema: z.ZodObject<{
28
+ id: z.ZodDefault<z.ZodNullable<z.ZodString>>;
29
+ version: z.ZodDefault<z.ZodNullable<z.ZodString>>;
30
+ name: z.ZodDefault<z.ZodNullable<z.ZodString>>;
31
+ }, z.core.$strip>;
32
+ /** Regime legal do dado. Ao menos `id` ou `name` deve estar presente (piso legal). */
33
+ export declare const LicenseSchema: z.ZodObject<{
34
+ id: z.ZodDefault<z.ZodNullable<z.ZodString>>;
35
+ name: z.ZodDefault<z.ZodNullable<z.ZodString>>;
36
+ url: z.ZodDefault<z.ZodNullable<z.ZodString>>;
37
+ terms_url: z.ZodDefault<z.ZodNullable<z.ZodString>>;
38
+ verified_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
39
+ }, z.core.$strip>;
40
+ /** Sub-fonte por campo — para respostas que fundem recortes/endpoints numa única estrutura. */
41
+ export declare const FieldSourceSchema: z.ZodObject<{
42
+ fields: z.ZodArray<z.ZodString>;
43
+ source_url: z.ZodString;
44
+ dataset_id: z.ZodDefault<z.ZodNullable<z.ZodString>>;
45
+ data_vintage: z.ZodDefault<z.ZodNullable<z.ZodString>>;
46
+ retrieved_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
47
+ }, z.core.$strip>;
48
+ export type FieldSource = z.infer<typeof FieldSourceSchema>;
49
+ /**
50
+ * Modelo canônico completo (pós-validação). `retrieved_at` deve ser o instante real da
51
+ * extração no upstream — preservado pela camada de cache do servidor —, nunca o momento
52
+ * do build/deploy; respostas servidas de cache mantêm o `retrieved_at` do fetch original
53
+ * (é a data de extração juridicamente relevante) e podem marcar `served_from_cache`.
54
+ */
55
+ export declare const CanonicalProvenanceSchema: z.ZodObject<{
56
+ contract_version: z.ZodDefault<z.ZodLiteral<"1.0">>;
57
+ source: z.ZodObject<{
58
+ name: z.ZodString;
59
+ agency: z.ZodDefault<z.ZodNullable<z.ZodString>>;
60
+ database: z.ZodDefault<z.ZodNullable<z.ZodString>>;
61
+ endpoint: z.ZodDefault<z.ZodNullable<z.ZodString>>;
62
+ }, z.core.$strip>;
63
+ dataset: z.ZodDefault<z.ZodObject<{
64
+ id: z.ZodDefault<z.ZodNullable<z.ZodString>>;
65
+ version: z.ZodDefault<z.ZodNullable<z.ZodString>>;
66
+ name: z.ZodDefault<z.ZodNullable<z.ZodString>>;
67
+ }, z.core.$strip>>;
68
+ dimension_key: z.ZodDefault<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>>;
69
+ data_vintage: z.ZodDefault<z.ZodNullable<z.ZodString>>;
70
+ retrieved_at: z.ZodString;
71
+ source_url: z.ZodString;
72
+ api_version: z.ZodDefault<z.ZodNullable<z.ZodString>>;
73
+ license: z.ZodObject<{
74
+ id: z.ZodDefault<z.ZodNullable<z.ZodString>>;
75
+ name: z.ZodDefault<z.ZodNullable<z.ZodString>>;
76
+ url: z.ZodDefault<z.ZodNullable<z.ZodString>>;
77
+ terms_url: z.ZodDefault<z.ZodNullable<z.ZodString>>;
78
+ verified_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
79
+ }, z.core.$strip>;
80
+ citation: z.ZodString;
81
+ notices: z.ZodDefault<z.ZodArray<z.ZodString>>;
82
+ derived: z.ZodDefault<z.ZodBoolean>;
83
+ derivation_note: z.ZodDefault<z.ZodNullable<z.ZodString>>;
84
+ served_from_cache: z.ZodDefault<z.ZodNullable<z.ZodBoolean>>;
85
+ field_sources: z.ZodDefault<z.ZodNullable<z.ZodArray<z.ZodObject<{
86
+ fields: z.ZodArray<z.ZodString>;
87
+ source_url: z.ZodString;
88
+ dataset_id: z.ZodDefault<z.ZodNullable<z.ZodString>>;
89
+ data_vintage: z.ZodDefault<z.ZodNullable<z.ZodString>>;
90
+ retrieved_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
91
+ }, z.core.$strip>>>>;
92
+ }, z.core.$strip>;
93
+ export type CanonicalProvenance = z.infer<typeof CanonicalProvenanceSchema>;
94
+ /** Entrada aceita pelos builders: atalhos de string para source/dataset/license. */
95
+ export interface ProvenanceInput {
96
+ source: string | z.input<typeof SourceSchema>;
97
+ source_url: string;
98
+ citation: string;
99
+ license: string | z.input<typeof LicenseSchema>;
100
+ dataset?: string | z.input<typeof DatasetSchema> | null;
101
+ dimension_key?: Record<string, string> | null;
102
+ data_vintage?: string | null;
103
+ /** Default: instante da chamada — aceitável só para catálogos estáticos sem extração upstream. */
104
+ retrieved_at?: string | Date;
105
+ api_version?: string | null;
106
+ notices?: string[];
107
+ derived?: boolean;
108
+ derivation_note?: string | null;
109
+ served_from_cache?: boolean | null;
110
+ field_sources?: Array<{
111
+ fields: string[];
112
+ source_url: string;
113
+ dataset_id?: string | null;
114
+ data_vintage?: string | null;
115
+ retrieved_at?: string | null;
116
+ }> | null;
117
+ }
118
+ /** Erro de contrato: builder recebeu entrada que viola o schema ou as regras semânticas. */
119
+ export declare class ProvenanceContractError extends Error {
120
+ constructor(message: string);
121
+ }
122
+ /**
123
+ * Expande os atalhos de string da entrada para os objetos canônicos. `retrievedAtIso` é
124
+ * o instante já normalizado para o fuso do contexto (a normalização vive no builder,
125
+ * que conhece o `TimezoneSpec`).
126
+ */
127
+ export declare function expandInput(input: ProvenanceInput, retrievedAtIso: string): z.input<typeof CanonicalProvenanceSchema>;
128
+ /** Regras semânticas que o zod não cobre por campo isolado. */
129
+ export declare function assertSemantics(p: CanonicalProvenance): void;
130
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAItC,6FAA6F;AAC7F,eAAO,MAAM,YAAY;;;;;iBAKvB,CAAC;AAEH,uFAAuF;AACvF,eAAO,MAAM,aAAa;;;;iBAIxB,CAAC;AAEH,sFAAsF;AACtF,eAAO,MAAM,aAAa;;;;;;iBAUtB,CAAC;AAEL,+FAA+F;AAC/F,eAAO,MAAM,iBAAiB;;;;;;iBAM5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+BpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,oFAAoF;AACpF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,GAAG,IAAI,CAAC;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9C,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,kGAAkG;IAClG,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iBAAiB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,aAAa,CAAC,EAAE,KAAK,CAAC;QACpB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,CAAC,GAAG,IAAI,CAAC;CACX;AAED,4FAA4F;AAC5F,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EACtB,cAAc,EAAE,MAAM,GACrB,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAa3C;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAI5D"}
package/dist/schema.js ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Modelo canônico de proveniência — contrato v1.0 do portfólio.
3
+ *
4
+ * Generalização de duas linhagens em produção/rascunho:
5
+ * - envelope nível-1 do senado-br-mcp-cloudflare (`src/utils/provenance.ts`) — vira a
6
+ * projeção `concise`;
7
+ * - bloco canônico v0.1 do ilostat (`docs/03-contrato-proveniencia.md`) — vira a
8
+ * projeção `detailed`.
9
+ *
10
+ * O servidor constrói UM modelo canônico por fonte (validado aqui, server-side); a
11
+ * projeção por modo acontece em `render.ts`. Regra de segregação: um bloco refere-se a
12
+ * exatamente UMA fonte — resposta multi-fonte carrega um bloco por fonte, e os dados de
13
+ * cada fonte ficam em estruturas separadas apontando para seu bloco (não contaminar
14
+ * licenças distintas, ex.: CC BY vs. CC BY-SA).
15
+ */
16
+ import { z } from "zod";
17
+ /** Versão do contrato de proveniência implementado por esta lib. */
18
+ export const CONTRACT_VERSION = "1.0";
19
+ const nullableString = z.string().min(1).nullable().default(null);
20
+ /** Identidade da fonte. `name` é o nome humano oficial; os demais refinam quando existem. */
21
+ export const SourceSchema = z.object({
22
+ name: z.string().min(1).describe('Nome oficial da fonte (ex.: "Senado Federal — Dados Abertos (Legislativo)")'),
23
+ agency: nullableString.describe('Órgão/agência (ex.: "ILO", "Senado Federal")'),
24
+ database: nullableString.describe('Base/sistema dentro do órgão (ex.: "ILOSTAT")'),
25
+ endpoint: nullableString.describe("Endpoint-base efetivamente consultado"),
26
+ });
27
+ /** Identidade do conjunto de dados dentro da fonte (dataflow SDMX, tabela, série…). */
28
+ export const DatasetSchema = z.object({
29
+ id: nullableString.describe("Identificador do conjunto (dataflow, código da matéria, série, tabela)"),
30
+ version: nullableString.describe("Versão do conjunto reportada pela fonte"),
31
+ name: nullableString.describe("Nome humano do conjunto, do metadado da fonte"),
32
+ });
33
+ /** Regime legal do dado. Ao menos `id` ou `name` deve estar presente (piso legal). */
34
+ export const LicenseSchema = z
35
+ .object({
36
+ id: nullableString.describe('Identificador SPDX-like (ex.: "CC-BY-4.0")'),
37
+ name: nullableString.describe("Nome/descrição da licença quando não há identificador formal"),
38
+ url: nullableString.describe("URL do texto da licença"),
39
+ terms_url: nullableString.describe("URL dos termos de uso da fonte"),
40
+ verified_at: nullableString.describe("Data (ISO-8601) da última verificação verbatim da licença"),
41
+ })
42
+ .refine((l) => l.id !== null || l.name !== null, {
43
+ message: "license exige ao menos `id` ou `name` (piso legal do contrato)",
44
+ });
45
+ /** Sub-fonte por campo — para respostas que fundem recortes/endpoints numa única estrutura. */
46
+ export const FieldSourceSchema = z.object({
47
+ fields: z.array(z.string().min(1)).min(1).describe("Campos do payload atribuídos a esta sub-fonte"),
48
+ source_url: z.string().min(1).describe("URL canônica da sub-fonte que originou estes campos"),
49
+ dataset_id: nullableString.describe("Identificador do conjunto da sub-fonte"),
50
+ data_vintage: nullableString.describe("Vintage/competência da sub-fonte"),
51
+ retrieved_at: nullableString.describe("ISO-8601 da extração desta sub-fonte no upstream"),
52
+ });
53
+ /**
54
+ * Modelo canônico completo (pós-validação). `retrieved_at` deve ser o instante real da
55
+ * extração no upstream — preservado pela camada de cache do servidor —, nunca o momento
56
+ * do build/deploy; respostas servidas de cache mantêm o `retrieved_at` do fetch original
57
+ * (é a data de extração juridicamente relevante) e podem marcar `served_from_cache`.
58
+ */
59
+ export const CanonicalProvenanceSchema = z.object({
60
+ contract_version: z.literal(CONTRACT_VERSION).default(CONTRACT_VERSION),
61
+ source: SourceSchema,
62
+ dataset: DatasetSchema.default({ id: null, version: null, name: null }),
63
+ dimension_key: z
64
+ .record(z.string(), z.string())
65
+ .nullable()
66
+ .default(null)
67
+ .describe("Chave dimensional da consulta (ordem = ordem das dimensões na fonte), quando aplicável"),
68
+ data_vintage: nullableString.describe("Vintage/competência do dado segundo a fonte; null se a fonte não expõe"),
69
+ retrieved_at: z.string().min(1).describe("ISO-8601 do momento da extração no upstream (não do build/deploy)"),
70
+ source_url: z.string().min(1).describe("URL canônica que reproduz a consulta na fonte"),
71
+ api_version: nullableString.describe("Versão do endpoint upstream, se exposta"),
72
+ license: LicenseSchema,
73
+ citation: z.string().min(1).describe("String de citação/atribuição pronta para uso (texto humano)"),
74
+ notices: z
75
+ .array(z.string().min(1))
76
+ .default([])
77
+ .describe("Disclaimers/avisos que acompanham o dado na origem, reproduzidos verbatim; [] se não houver"),
78
+ derived: z.boolean().default(false).describe("true se o servidor transformou além de filtrar/paginar/reserializar"),
79
+ derivation_note: nullableString.describe("Obrigatório se derived=true: o que foi feito"),
80
+ served_from_cache: z
81
+ .boolean()
82
+ .nullable()
83
+ .default(null)
84
+ .describe("true/false quando o servidor distingue cache de fetch; null quando não distingue"),
85
+ field_sources: z
86
+ .array(FieldSourceSchema)
87
+ .nullable()
88
+ .default(null)
89
+ .describe("Proveniência por-campo: presente só quando a resposta funde múltiplos recortes upstream"),
90
+ });
91
+ /** Erro de contrato: builder recebeu entrada que viola o schema ou as regras semânticas. */
92
+ export class ProvenanceContractError extends Error {
93
+ constructor(message) {
94
+ super(message);
95
+ this.name = "ProvenanceContractError";
96
+ }
97
+ }
98
+ /**
99
+ * Expande os atalhos de string da entrada para os objetos canônicos. `retrievedAtIso` é
100
+ * o instante já normalizado para o fuso do contexto (a normalização vive no builder,
101
+ * que conhece o `TimezoneSpec`).
102
+ */
103
+ export function expandInput(input, retrievedAtIso) {
104
+ const source = typeof input.source === "string" ? { name: input.source } : input.source;
105
+ const license = typeof input.license === "string" ? { name: input.license } : input.license;
106
+ const dataset = input.dataset == null ? undefined : typeof input.dataset === "string" ? { id: input.dataset } : input.dataset;
107
+ const { source: _s, license: _l, dataset: _d, retrieved_at: _r, ...rest } = input;
108
+ return {
109
+ ...rest,
110
+ source,
111
+ license,
112
+ ...(dataset !== undefined ? { dataset } : {}),
113
+ retrieved_at: retrievedAtIso,
114
+ };
115
+ }
116
+ /** Regras semânticas que o zod não cobre por campo isolado. */
117
+ export function assertSemantics(p) {
118
+ if (p.derived && p.derivation_note === null) {
119
+ throw new ProvenanceContractError("derived=true exige derivation_note não-nulo (§4 do contrato)");
120
+ }
121
+ }
122
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
package/dist/time.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Normalização de timestamps do contrato de proveniência.
3
+ *
4
+ * Todo `retrieved_at` (e afins) é serializado em ISO-8601 SEM milissegundos, num fuso
5
+ * fixo escolhido pelo servidor — o objetivo é a REPRESENTAÇÃO estável (determinismo
6
+ * byte-a-byte), nunca alterar o instante. Origem do requisito: no senado-br-mcp,
7
+ * timestamps em UTC faziam respostas noturnas citarem a data do dia seguinte para o
8
+ * leitor brasileiro; a solução (offset explícito -03:00) é aqui generalizada para
9
+ * qualquer offset fixo. O fuso do IP do requisitante não serve: em conectores MCP
10
+ * remotos o IP visto é o do backend do provedor de IA, não o da pessoa.
11
+ */
12
+ /** Fuso de serialização: "utc" ou offset fixo explícito (ex.: { offset: "-03:00" }). */
13
+ export type TimezoneSpec = "utc" | {
14
+ offset: string;
15
+ label?: string;
16
+ };
17
+ /** Converte "-03:00" em minutos (-180). Lança em formato inválido. */
18
+ export declare function parseOffsetMinutes(offset: string): number;
19
+ /** Rótulo humano do fuso, para o rodapé (ex.: "horário de Brasília", "UTC"). */
20
+ export declare function timezoneLabel(tz: TimezoneSpec): string;
21
+ /**
22
+ * Converte um instante para ISO-8601 canônico no fuso configurado, sem milissegundos
23
+ * (ex.: "2026-07-14T21:23:45-03:00"; em UTC, sufixo "Z"). Datas puras ("2026-06-28")
24
+ * e strings não-parseáveis passam inalteradas — nunca corrompe um vintage nem
25
+ * inventa horário onde não há.
26
+ */
27
+ export declare function toCanonicalIso(value: string | Date, tz: TimezoneSpec): string;
28
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,wFAAwF;AACxF,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAKtE,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,CAGtD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,YAAY,GAAG,MAAM,CAQ7E"}
package/dist/time.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Normalização de timestamps do contrato de proveniência.
3
+ *
4
+ * Todo `retrieved_at` (e afins) é serializado em ISO-8601 SEM milissegundos, num fuso
5
+ * fixo escolhido pelo servidor — o objetivo é a REPRESENTAÇÃO estável (determinismo
6
+ * byte-a-byte), nunca alterar o instante. Origem do requisito: no senado-br-mcp,
7
+ * timestamps em UTC faziam respostas noturnas citarem a data do dia seguinte para o
8
+ * leitor brasileiro; a solução (offset explícito -03:00) é aqui generalizada para
9
+ * qualquer offset fixo. O fuso do IP do requisitante não serve: em conectores MCP
10
+ * remotos o IP visto é o do backend do provedor de IA, não o da pessoa.
11
+ */
12
+ const OFFSET_RE = /^([+-])(\d{2}):(\d{2})$/;
13
+ const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
14
+ /** Converte "-03:00" em minutos (-180). Lança em formato inválido. */
15
+ export function parseOffsetMinutes(offset) {
16
+ const m = offset.match(OFFSET_RE);
17
+ if (!m)
18
+ throw new Error(`Offset de fuso inválido: "${offset}" (esperado ±HH:MM)`);
19
+ const sign = m[1] === "-" ? -1 : 1;
20
+ return sign * (Number(m[2]) * 60 + Number(m[3]));
21
+ }
22
+ /** Rótulo humano do fuso, para o rodapé (ex.: "horário de Brasília", "UTC"). */
23
+ export function timezoneLabel(tz) {
24
+ if (tz === "utc")
25
+ return "UTC";
26
+ return tz.label ?? `UTC${tz.offset}`;
27
+ }
28
+ /**
29
+ * Converte um instante para ISO-8601 canônico no fuso configurado, sem milissegundos
30
+ * (ex.: "2026-07-14T21:23:45-03:00"; em UTC, sufixo "Z"). Datas puras ("2026-06-28")
31
+ * e strings não-parseáveis passam inalteradas — nunca corrompe um vintage nem
32
+ * inventa horário onde não há.
33
+ */
34
+ export function toCanonicalIso(value, tz) {
35
+ if (typeof value === "string" && DATE_ONLY_RE.test(value))
36
+ return value;
37
+ const d = value instanceof Date ? value : new Date(value);
38
+ if (Number.isNaN(d.getTime()))
39
+ return String(value);
40
+ if (tz === "utc")
41
+ return d.toISOString().replace(/\.\d{3}Z$/, "Z");
42
+ const offsetMs = parseOffsetMinutes(tz.offset) * 60_000;
43
+ const wall = new Date(d.getTime() + offsetMs).toISOString();
44
+ return wall.replace(/\.\d{3}Z$/, tz.offset);
45
+ }
46
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,SAAS,GAAG,yBAAyB,CAAC;AAC5C,MAAM,YAAY,GAAG,qBAAqB,CAAC;AAE3C,sEAAsE;AACtE,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,MAAM,qBAAqB,CAAC,CAAC;IAClF,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,aAAa,CAAC,EAAgB;IAC5C,IAAI,EAAE,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAC/B,OAAO,EAAE,CAAC,KAAK,IAAI,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAoB,EAAE,EAAgB;IACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxE,MAAM,CAAC,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACpD,IAAI,EAAE,KAAK,KAAK;QAAE,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACnE,MAAM,QAAQ,GAAG,kBAAkB,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IACxD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5D,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,135 @@
1
+ # Contrato de proveniência do portfólio — v1.0
2
+
3
+ | Campo | Valor |
4
+ |:--|:--|
5
+ | Status | VIGENTE — implementado por `@sbissoli/mcp-provenance` |
6
+ | Origem | Promoção do contrato v0.1 do ilostat (`ilostat/docs/03-contrato-proveniencia.md`) + envelope nível-1 do senado-br-mcp-cloudflare (`src/utils/provenance.ts`) |
7
+ | Escopo | Toda resposta de toda ferramenta de todo servidor do portfólio (adoção nas Fases 1–4) |
8
+ | Decisões de base | Decisão 5 da Fase 0 (modos `concise`/`detailed`, princípio de linguagem) |
9
+
10
+ ## 1. Princípio
11
+
12
+ Todo dado retornado por um servidor carrega um **bloco de proveniência determinístico**:
13
+ a mesma consulta, sobre a mesma versão dos dados, produz bloco byte-idêntico exceto pelos
14
+ campos de timestamp. O bloco é o *wedge* do produto — é o que torna cada número citável,
15
+ auditável e reproduzível.
16
+
17
+ **Determinismo:** serialização canônica — chaves em ordem fixa (a ordem de construção nos
18
+ `render*` da lib é contrato), ausência é `null` explícito, timestamps sem milissegundos em
19
+ fuso fixo configurado por servidor. Os únicos campos que variam entre execuções idênticas
20
+ são `retrieved_at` e citação que embuta data. O determinismo vale **dentro de cada modo**.
21
+
22
+ **Segregação:** um bloco refere-se a exatamente **uma** fonte. Resposta que combine fontes
23
+ com regimes legais distintos (ex.: CC BY e CC BY-SA) carrega um bloco por fonte, e os
24
+ dados de cada fonte ficam em estruturas separadas, cada qual apontando para seu bloco —
25
+ não contaminar a saída de uma licença com as obrigações da outra. A lib aceita
26
+ `CanonicalProvenance[]` em `result()`/`footer()`; a separação das estruturas de dados é
27
+ responsabilidade do servidor. Para respostas de UMA fonte que fundem **recortes/endpoints**
28
+ diferentes, usar `field_sources` (granularidade por campo), não blocos múltiplos.
29
+
30
+ ## 2. Os dois modos (decisão 5)
31
+
32
+ - **`concise` (padrão)** — piso legal + citação mínima. Exatamente 6 chaves, nesta ordem:
33
+ `source`, `source_url`, `data_vintage`, `retrieved_at`, `citation`, `license`
34
+ (rótulo curto: `license.id` quando existe, senão `license.name`).
35
+ - **`detailed`** — bloco canônico completo (§3). Evals de completude (camada 2) rodam em
36
+ `detailed` para exercitar os dois caminhos.
37
+
38
+ O parâmetro técnico que expõe o modo na tool (nome, descrição) é decisão por-servidor e
39
+ vive na descrição da tool — nunca no texto voltado ao leitor.
40
+
41
+ ## 3. Bloco canônico (modo `detailed`)
42
+
43
+ Ordem fixa de chaves; ausência = `null`:
44
+
45
+ ```jsonc
46
+ {
47
+ "contract_version": "1.0",
48
+ "source": { "name": "...", "agency": null, "database": null, "endpoint": null },
49
+ "dataset": { "id": null, "version": null, "name": null },
50
+ "dimension_key": null, // objeto {DIM: valor} na ordem das dimensões da fonte
51
+ "data_vintage": null, // última atualização segundo a fonte; null se não expõe
52
+ "retrieved_at": "...", // ISO-8601 no fuso do servidor — instante REAL da extração
53
+ "source_url": "...", // URL canônica que reproduz a consulta
54
+ "api_version": null,
55
+ "license": { "id": null, "name": null, "url": null, "terms_url": null, "verified_at": null },
56
+ "citation": "...", // string de citação/atribuição pronta para uso
57
+ "notices": [], // avisos da origem, verbatim
58
+ "derived": false,
59
+ "derivation_note": null, // obrigatório se derived=true
60
+ "served_from_cache": null, // true/false quando o servidor distingue; null quando não
61
+ "field_sources": null // [{fields, source_url, dataset_id, data_vintage, retrieved_at}]
62
+ }
63
+ ```
64
+
65
+ Invariantes validados server-side (zod + `assertSemantics`), antes de responder:
66
+ `license` exige ao menos `id` ou `name`; `derived=true` exige `derivation_note`.
67
+
68
+ ### Semântica de `retrieved_at`
69
+
70
+ Instante real da ida ao upstream — preservado pela camada de cache do servidor —, nunca o
71
+ momento do build/deploy. Respostas servidas de cache mantêm o `retrieved_at` do fetch
72
+ original (é a data de extração juridicamente relevante) e podem marcar
73
+ `served_from_cache: true`. O default `new Date()` do builder só é aceitável para
74
+ catálogos estáticos mantidos em código.
75
+
76
+ ### Semântica de `derived`
77
+
78
+ - `false`: dado bruto, apenas filtrado/paginado/reserializado — atribuição basta.
79
+ - `true`: qualquer transformação de valor (agregação, taxa calculada, interpolação,
80
+ harmonização). Exige `derivation_note`. Licenças ShareAlike (ex.: UIS CC BY-SA)
81
+ propagam-se ao derivado — refletir em `license` do bloco.
82
+ - **Caso de fronteira** (conversão de unidade/arredondamento conta como derivação?):
83
+ decisão **por-servidor**, registrada nos docs do servidor; a lib suporta ambas.
84
+
85
+ ## 4. Mapeamento obrigação legal → campo
86
+
87
+ | Obrigação típica | Campo que a satisfaz |
88
+ |:--|:--|
89
+ | "credit must be given to …" | `citation` |
90
+ | Reproduzir avisos/disclaimers da origem | `notices` |
91
+ | Marcar obra derivada | `derived` + `derivation_note` |
92
+ | URL completa + data de extração (ex.: UIS) | `citation` (embute ambos) + `source_url` + `retrieved_at` |
93
+ | Registrar licença vigente a cada fetch | `license.verified_at` + log persistente do servidor (fora da lib) |
94
+
95
+ ## 5. Três canais de emissão (`result()`)
96
+
97
+ 1. `structuredContent.provenance` (projeção do modo) + `structuredContent.attribution`
98
+ (lista canônica de `source_url` distintas, alinhada à RFC `attribution` do MCP,
99
+ modelcontextprotocol#711) — canal parseável, visível ao modelo.
100
+ 2. `_meta` sob chaves namespaced (`{namespace}/provenance`, `{namespace}/attribution`,
101
+ namespace reverse-DNS por servidor) — out-of-band, auditoria/UI, zero tokens do modelo.
102
+ 3. Rodapé de texto compacto no `content` — clientes text-only. Medição no senado: embutir
103
+ proveniência também no JSON textual custava ~3.8× mais tokens sem benefício.
104
+
105
+ ## 6. Princípio de linguagem (rodapé)
106
+
107
+ O rodapé fala com o **leitor**; o schema fala com o agente. Registro formal/institucional,
108
+ idioma do servidor, sem jargão técnico nem coloquialidade. Redações fixadas (v1.0):
109
+
110
+ - **pt-BR**: "A referência completa desta informação pode ser solicitada nesta própria
111
+ conversa." (redação da decisão 5, fixada)
112
+ - **en**: "The complete reference for this information can be requested here, in this
113
+ same conversation." (fixada nesta sessão)
114
+
115
+ O aviso aparece uma única vez por resposta, apenas no modo `concise` — em `detailed` a
116
+ referência completa já está na resposta. Outros idiomas: passar um `LocaleSpec` próprio.
117
+
118
+ ## 7. Mapeamento dos predecessores → v1.0
119
+
120
+ | Predecessor | Campo antigo | v1.0 |
121
+ |:--|:--|:--|
122
+ | senado nível-1 | `source` (string) | `source.name` |
123
+ | senado nível-1 | `dataset_id` | `dataset.id` |
124
+ | senado nível-1 | `reference_period` | `data_vintage` |
125
+ | senado nível-1 | `citation` | `citation` |
126
+ | senado nível-1 | `license` (string) | `license.name` |
127
+ | senado nível-1 | `field_sources[].reference_period` | `field_sources[].data_vintage` |
128
+ | ilostat v0.1 | `dataflow` (`agency_id`/`dataflow_id`/`version`/`name`) | `dataset` (`id` = dataflow_id; agency já está em `source.agency`) |
129
+ | ilostat v0.1 | `attribution` (string) | `citation` (o nome `attribution` fica reservado à lista de URLs da RFC #711) |
130
+ | ilostat v0.1 | `license.license_verified_at` | `license.verified_at` |
131
+
132
+ Questões que o v0.1 do ilostat deixava em aberto e o v1.0 resolve: verbosidade → modos da
133
+ decisão 5; `served_from_cache` → campo nullable padrão. Permanecem por-servidor: caso de
134
+ fronteira de `derived` (§3) e confirmações de spike (IDs de dataflow, exposição de
135
+ `data_vintage` pela fonte).
@@ -0,0 +1,59 @@
1
+ # Diagnóstico de adoção — senado-br-mcp-cloudflare × contrato v1.0
2
+
3
+ | Campo | Valor |
4
+ |:--|:--|
5
+ | Data | 2026-08-07 (sessão de adoção Fase 1 no senado) |
6
+ | Veredito | **DIVERGENTE — adoção adiada, pendente de decisão do decisor** |
7
+ | Envelope real | `senado-br-mcp-cloudflare/src/utils/provenance.ts` (nível-1, em produção nas 67 tools) |
8
+ | Contrato | `contrato-proveniencia-v1.md` + `@sbissoli/mcp-provenance` (schema/render) |
9
+
10
+ ## Diff — envelope do senado × projeções v1.0
11
+
12
+ O envelope do senado é **plano** e anterior aos modos `concise`/`detailed`. Não é
13
+ byte-igual a nenhuma das duas projeções:
14
+
15
+ | Envelope senado (atual) | v1.0 `concise` | v1.0 `detailed` |
16
+ |:--|:--|:--|
17
+ | `source` (string) | `source` (string = `source.name`) — igual | `source` vira **objeto** `{name, agency, database, endpoint}` |
18
+ | `source_url` | igual | igual (posição diferente na ordem fixa) |
19
+ | `dataset_id?` (omitido quando ausente) | **não existe** (cai fora do bloco) | `dataset.id` (objeto `{id, version, name}`, nulls explícitos) |
20
+ | `reference_period?` | **renomeado** `data_vintage`, `null` explícito | idem |
21
+ | `retrieved_at` | igual | igual |
22
+ | `citation` | igual | igual |
23
+ | `license?` (string, omitida quando ausente) | `license` (rótulo curto, `null` explícito) | `license` vira **objeto** `{id, name, url, terms_url, verified_at}` |
24
+ | `api_version?` | **não existe** | `api_version` (null explícito) |
25
+ | `field_sources?[].reference_period` | **não existe** | `field_sources[].data_vintage` (+ nulls explícitos) |
26
+ | — | — | campos novos: `contract_version`, `dimension_key`, `notices`, `derived`, `derivation_note`, `served_from_cache` |
27
+
28
+ Diferenças transversais:
29
+
30
+ 1. **Renome de chave lida pelo modelo**: `reference_period` → `data_vintage` (e dentro de
31
+ `field_sources`). Muda a resposta de todas as 67 tools.
32
+ 2. **Ausência**: senado omite campos opcionais; v1.0 exige `null` explícito em ordem fixa
33
+ de chaves (determinismo é contrato).
34
+ 3. **Sem modos**: o senado emite um único envelope; v1.0 define `concise` (6 chaves) ×
35
+ `detailed` (bloco canônico) com parâmetro por-servidor.
36
+ 4. **`license` string × objeto**: no `concise` o rótulo curto coincide com a string atual
37
+ do senado, mas no canônico é objeto com piso legal (`id` ou `name`).
38
+ 5. **Rodapé**: v1.0 fixa a redação "A referência completa desta informação pode ser
39
+ solicitada nesta própria conversa." (decisão 5); o rodapé atual do senado é
40
+ `Fonte: X · url · extraído em … · competência …` — também mudaria.
41
+
42
+ ## Consequência
43
+
44
+ A adoção **não é troca de import**: altera chaves/shape do `structuredContent.provenance`,
45
+ do espelho em `_meta` e do rodapé de texto de **todas as 67 tools** — exatamente a classe
46
+ de mudança que o harness de evals e o widget do ChatGPT App leem. Conforme o plano da
47
+ sessão (ADOCAO_SENADO_PROMPT_SESSAO.md, escopo 3): não adotar em silêncio.
48
+
49
+ ## Decisão pendente (decisor)
50
+
51
+ - **Opção A**: release menor dedicado do senado (bump de versão + nota no README) que
52
+ migra o envelope ao v1.0 (modo `concise` como padrão preserva o "peso" atual da
53
+ resposta; `reference_period`→`data_vintage` é a única quebra visível no modo padrão,
54
+ além dos nulls explícitos e da perda de `dataset_id`/`api_version` no bloco padrão).
55
+ - **Opção B**: adiar para a fase do congresso-br (primeiro servidor novo a nascer já no
56
+ v1.0), mantendo o senado no envelope nível-1 até lá.
57
+
58
+ A adoção de `@sbissoli/mcp-stats` e `@sbissoli/mcp-evals` no senado **não depende** desta
59
+ decisão (concluída em 2026-08-07).
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@sbissoli/mcp-provenance",
3
+ "version": "0.1.0",
4
+ "description": "Contrato de proveniência para servidores MCP: todo retorno de tool carrega fonte, endpoint, período, data de extração e licença, em dois modos (concise/detailed), com serialização determinística",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "docs",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run"
23
+ },
24
+ "dependencies": {
25
+ "zod": "^4.0.0"
26
+ },
27
+ "keywords": [
28
+ "mcp",
29
+ "provenance",
30
+ "data-provenance",
31
+ "open-data",
32
+ "attribution"
33
+ ],
34
+ "author": "Sidney Bissoli <sbissoli76@gmail.com>",
35
+ "license": "MIT"
36
+ }