#ifndef CONECTORES_OPERACIONAL_H
#define CONECTORES_OPERACIONAL_H

#define _XOPEN_SOURCE 700
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#define GC_BASE "/var/www/html/gitconectores"
#define GC_RECEBIDOS "/var/www/html/gitconectores/gitconectoresrecebidos"
#define GC_SISC_PADRAO "/var/www/html/sisc/siscore"
#define GC_TESTESIS_PADRAO "/var/www/html/sisc/testesis"
#define GC_SELOS_DIR "/var/www/html/gitconectores/secretos"
#define GC_SELOS_CHAVE "/var/www/html/gitconectores/secretos/chave-selos.key"
#define GC_SELO_VERSAO "sisc-selo-v1"

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

static void sb_add(char *buf, size_t tam, const char *fmt, ...) {
    if (!buf || tam == 0) return;
    size_t n = strlen(buf);
    if (n >= tam - 1) return;
    va_list ap; va_start(ap, fmt);
    vsnprintf(buf + n, tam - n, fmt, ap);
    va_end(ap);
}

static int ends_with(const char *s, const char *suf) {
    if (!s || !suf) return 0;
    size_t a = strlen(s), b = strlen(suf);
    return a >= b && strcmp(s + a - b, suf) == 0;
}

static int starts_with(const char *s, const char *pre) {
    return s && pre && strncmp(s, pre, strlen(pre)) == 0;
}

static int is_file_p(const char *p) { struct stat st; return p && stat(p, &st) == 0 && S_ISREG(st.st_mode); }
static int is_dir_p(const char *p) { struct stat st; return p && stat(p, &st) == 0 && S_ISDIR(st.st_mode); }
static int is_exec_p(const char *p) { return is_file_p(p) && access(p, X_OK) == 0; }

static int mkdir_p(const char *dir, mode_t mode) {
    char tmp[PATH_MAX];
    if (!dir || !*dir) return -1;
    snprintf(tmp, sizeof(tmp), "%s", dir);
    size_t len = strlen(tmp);
    if (len == 0) return -1;
    if (tmp[len - 1] == '/') tmp[len - 1] = '\0';
    for (char *p = tmp + 1; *p; p++) {
        if (*p == '/') {
            *p = '\0';
            if (mkdir(tmp, mode) != 0 && errno != EEXIST) return -1;
            *p = '/';
        }
    }
    if (mkdir(tmp, mode) != 0 && errno != EEXIST) return -1;
    return 0;
}

static void dirname_of(const char *path, char *out, size_t tam) {
    snprintf(out, tam, "%s", path);
    char *s = strrchr(out, '/');
    if (s) *s = '\0'; else snprintf(out, tam, ".");
}

static void shell_quote(const char *s, char *out, size_t tam) {
    size_t j = 0;
    if (tam == 0) return;
    out[j++] = '\'';
    for (size_t i = 0; s && s[i] && j + 5 < tam; i++) {
        if (s[i] == '\'') { memcpy(out + j, "'\\''", 4); j += 4; }
        else out[j++] = s[i];
    }
    if (j + 1 < tam) out[j++] = '\'';
    out[j] = '\0';
}

static int run_cmd(const char *cmd) { return system(cmd); }

static int capture_cmd(const char *cmd, char *out, size_t tam) {
    if (tam) out[0] = '\0';
    FILE *fp = popen(cmd, "r");
    if (!fp) return -1;
    size_t n = 0;
    int c;
    while ((c = fgetc(fp)) != EOF && n + 1 < tam) out[n++] = (char)c;
    if (tam) out[n] = '\0';
    int rc = pclose(fp);
    return rc;
}

static char *read_file_alloc(const char *path, size_t *sz) {
    FILE *fp = fopen(path, "rb");
    if (!fp) return NULL;
    if (fseek(fp, 0, SEEK_END) != 0) { fclose(fp); return NULL; }
    long n = ftell(fp);
    if (n < 0) { fclose(fp); return NULL; }
    rewind(fp);
    char *buf = malloc((size_t)n + 1);
    if (!buf) { fclose(fp); return NULL; }
    size_t r = fread(buf, 1, (size_t)n, fp);
    fclose(fp);
    if (r != (size_t)n) { free(buf); return NULL; }
    buf[r] = '\0';
    if (sz) *sz = r;
    return buf;
}

static int write_text_file(const char *path, const char *txt, mode_t mode) {
    char dir[PATH_MAX]; dirname_of(path, dir, sizeof(dir));
    if (mkdir_p(dir, 0775) != 0) return -1;
    char tmp[PATH_MAX]; snprintf(tmp, sizeof(tmp), "%s.tmp.%ld.%ld", path, (long)time(NULL), (long)getpid());
#ifndef O_NOFOLLOW
#define O_NOFOLLOW 0
#endif
    int fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
    if (fd < 0) return -1;
    FILE *fp = fdopen(fd, "wb");
    if (!fp) { close(fd); unlink(tmp); return -1; }
    if (txt && fwrite(txt, 1, strlen(txt), fp) != strlen(txt)) { fclose(fp); unlink(tmp); return -1; }
    if (fclose(fp) != 0) { unlink(tmp); return -1; }
    chmod(tmp, mode);
    if (rename(tmp, path) != 0) { unlink(tmp); return -1; }
    return 0;
}

static int copy_file(const char *src, const char *dst, mode_t mode) {
    FILE *a = fopen(src, "rb"); if (!a) return -1;
    char dir[PATH_MAX]; dirname_of(dst, dir, sizeof(dir));
    if (mkdir_p(dir, 0775) != 0) { fclose(a); return -1; }
    FILE *b = fopen(dst, "wb"); if (!b) { fclose(a); return -1; }
    char buf[65536]; size_t n;
    while ((n = fread(buf, 1, sizeof(buf), a)) > 0) if (fwrite(buf, 1, n, b) != n) { fclose(a); fclose(b); return -1; }
    int ok = ferror(a) ? -1 : 0;
    fclose(a); if (fclose(b) != 0) ok = -1;
    chmod(dst, mode);
    return ok;
}

static int safe_rel(const char *p) {
    if (!p || !*p || p[0] == '/') return 0;
    char tmp[PATH_MAX]; snprintf(tmp, sizeof(tmp), "%s", p);
    char *save = NULL;
    for (char *t = strtok_r(tmp, "/\\", &save); t; t = strtok_r(NULL, "/\\", &save)) {
        if (strcmp(t, "..") == 0) return 0;
    }
    return 1;
}

static int scan_extraido_seguro(const char *dir, const char *rel, char *log, size_t logsz) {
    DIR *d = opendir(dir); if (!d) { sb_add(log, logsz, "nao foi possivel abrir extraido: %s\n", rel && *rel ? rel : "."); return -1; }
    int erro = 0; struct dirent *e;
    while ((e = readdir(d))) {
        if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
        char p[PATH_MAX], r[PATH_MAX]; snprintf(p, sizeof(p), "%s/%s", dir, e->d_name);
        snprintf(r, sizeof(r), "%s%s%s", rel && *rel ? rel : "", rel && *rel ? "/" : "", e->d_name);
        struct stat st;
        if (lstat(p, &st) != 0) { sb_add(log, logsz, "lstat falhou no extraido: %s\n", r); erro = 1; continue; }
        if (S_ISLNK(st.st_mode)) { sb_add(log, logsz, "link simbolico proibido no pacote: %s\n", r); erro = 1; continue; }
        if (S_ISDIR(st.st_mode)) { if (scan_extraido_seguro(p, r, log, logsz) != 0) erro = 1; continue; }
        if (!S_ISREG(st.st_mode)) { sb_add(log, logsz, "entrada nao regular proibida no pacote: %s\n", r); erro = 1; continue; }
    }
    closedir(d);
    return erro ? -1 : 0;
}

static int nome_conector_valido(const char *n) {
    if (!n || !starts_with(n, "conector-") || strlen(n) <= 9) return 0;
    for (const char *p = n; *p; p++) if (!(islower((unsigned char)*p) || isdigit((unsigned char)*p) || *p == '-')) return 0;
    return 1;
}

static int pacote_ext(const char *p) {
    return ends_with(p, ".tar.gz") || ends_with(p, ".tgz") || ends_with(p, ".zip");
}

static void marker_path(const char *pacote, const char *tipo, char *out, size_t tam) {
    snprintf(out, tam, "%s.%s.json", pacote, tipo);
}

static void sha256_file(const char *arquivo, char *out, size_t tam) {
    char q[PATH_MAX * 2], cmd[PATH_MAX * 3], cap[512];
    shell_quote(arquivo, q, sizeof(q));
    snprintf(cmd, sizeof(cmd), "sha256sum %s 2>/dev/null", q);
    if (capture_cmd(cmd, cap, sizeof(cap)) == 0 || cap[0]) {
        char *sp = strpbrk(cap, " \t\r\n"); if (sp) *sp = '\0';
        snprintf(out, tam, "%s", cap);
    } else if (tam) out[0] = '\0';
}

static int listar_validar_pacote(const char *pacote, char *log, size_t logsz) {
    char qp[PATH_MAX * 2], cmd[PATH_MAX * 3]; shell_quote(pacote, qp, sizeof(qp));
    if (ends_with(pacote, ".zip")) snprintf(cmd, sizeof(cmd), "unzip -Z1 %s 2>/dev/null", qp);
    else snprintf(cmd, sizeof(cmd), "tar -tzf %s 2>/dev/null", qp);
    FILE *fp = popen(cmd, "r");
    if (!fp) { sb_add(log, logsz, "nao foi possivel listar pacote\n"); return -1; }
    char line[PATH_MAX]; int n = 0, erro = 0;
    while (fgets(line, sizeof(line), fp)) {
        n++;
        line[strcspn(line, "\r\n")] = '\0';
        if (!safe_rel(line)) { sb_add(log, logsz, "caminho inseguro no pacote: %s\n", line); erro = 1; }
    }
    int rc = pclose(fp);
    if (rc != 0 || n == 0) { sb_add(log, logsz, "pacote vazio ou invalido\n"); erro = 1; }
    return erro ? -1 : 0;
}

static int extrair_pacote(const char *pacote, const char *dest, char *log, size_t logsz) {
    if (listar_validar_pacote(pacote, log, logsz) != 0) return -1;
    if (mkdir_p(dest, 0770) != 0) { sb_add(log, logsz, "falha criando tmp %s\n", dest); return -1; }
    char qp[PATH_MAX * 2], qd[PATH_MAX * 2], cmd[PATH_MAX * 5];
    shell_quote(pacote, qp, sizeof(qp)); shell_quote(dest, qd, sizeof(qd));
    if (ends_with(pacote, ".zip")) snprintf(cmd, sizeof(cmd), "unzip -q %s -d %s", qp, qd);
    else snprintf(cmd, sizeof(cmd), "tar -xzf %s -C %s", qp, qd);
    int rc = run_cmd(cmd);
    if (rc != 0) { sb_add(log, logsz, "extracao falhou\n"); return -1; }
    if (scan_extraido_seguro(dest, "", log, logsz) != 0) return -1;
    return 0;
}

static void rm_rf(const char *dir) {
    if (!dir || !starts_with(dir, GC_BASE "/tmp/")) return;
    char q[PATH_MAX * 2], cmd[PATH_MAX * 3]; shell_quote(dir, q, sizeof(q));
    snprintf(cmd, sizeof(cmd), "rm -rf -- %s", q);
    run_cmd(cmd);
}

static int json_get_string(const char *txt, const char *key, char *out, size_t tam) {
    if (tam) out[0] = '\0';
    char pat[128]; snprintf(pat, sizeof(pat), "\"%s\"", key);
    const char *p = strstr(txt, pat); if (!p) return 0;
    p += strlen(pat);
    while (*p && isspace((unsigned char)*p)) p++;
    if (*p != ':') return 0;
    p++;
    while (*p && isspace((unsigned char)*p)) p++;
    if (*p != '"') return 0;
    p++;
    size_t j = 0; int esc = 0;
    while (*p) {
        if (esc) { if (j + 1 < tam) out[j++] = *p; esc = 0; }
        else if (*p == '\\') esc = 1;
        else if (*p == '"') { if (tam) out[j] = '\0'; return 1; }
        else { if (j + 1 < tam) out[j++] = *p; }
        p++;
    }
    return 0;
}

static int contains_key_bool_true(const char *txt, const char *key) {
    char pat[128]; snprintf(pat, sizeof(pat), "\"%s\"", key);
    const char *p = strstr(txt, pat); if (!p) return 0;
    p += strlen(pat);
    while (*p && isspace((unsigned char)*p)) p++;
    if (*p != ':') return 0;
    p++;
    while (*p && isspace((unsigned char)*p)) p++;
    return strncmp(p, "true", 4) == 0;
}

static int canonical_path(const char *path, char *out, size_t tam) {
    if (!path || !*path || !out || tam == 0) return -1;
    char real[PATH_MAX];
    if (realpath(path, real)) return snprintf(out, tam, "%s", real) < (int)tam ? 0 : -1;
    return snprintf(out, tam, "%s", path) < (int)tam ? 0 : -1;
}

static void gc_json_escape_copy(const char *in, char *out, size_t tam) {
    if (!out || tam == 0) return;
    size_t j = 0;
    for (const unsigned char *p = (const unsigned char *)(in ? in : ""); *p && j + 1 < tam; p++) {
        if (*p == '"' || *p == '\\') {
            if (j + 2 >= tam) break;
            out[j++] = '\\'; out[j++] = (char)*p;
        } else if (*p == '\n') {
            if (j + 2 >= tam) break;
            out[j++] = '\\'; out[j++] = 'n';
        } else if (*p == '\r') {
            if (j + 2 >= tam) break;
            out[j++] = '\\'; out[j++] = 'r';
        } else if (*p == '\t') {
            if (j + 2 >= tam) break;
            out[j++] = '\\'; out[j++] = 't';
        } else if (*p < 0x20) {
            if (j + 6 >= tam) break;
            int n = snprintf(out + j, tam - j, "\\u%04x", *p);
            if (n < 0) break;
            j += (size_t)n;
        } else {
            out[j++] = (char)*p;
        }
    }
    out[j] = '\0';
}

typedef struct {
    uint8_t data[64];
    uint32_t datalen;
    uint64_t bitlen;
    uint32_t state[8];
} GcSha256Ctx;

#define GC_SHA256_ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32 - (b))))
#define GC_SHA256_CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
#define GC_SHA256_MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define GC_SHA256_EP0(x) (GC_SHA256_ROTRIGHT((x),2) ^ GC_SHA256_ROTRIGHT((x),13) ^ GC_SHA256_ROTRIGHT((x),22))
#define GC_SHA256_EP1(x) (GC_SHA256_ROTRIGHT((x),6) ^ GC_SHA256_ROTRIGHT((x),11) ^ GC_SHA256_ROTRIGHT((x),25))
#define GC_SHA256_SIG0(x) (GC_SHA256_ROTRIGHT((x),7) ^ GC_SHA256_ROTRIGHT((x),18) ^ ((x) >> 3))
#define GC_SHA256_SIG1(x) (GC_SHA256_ROTRIGHT((x),17) ^ GC_SHA256_ROTRIGHT((x),19) ^ ((x) >> 10))

static const uint32_t gc_sha256_k[64] = {
    0x428a2f98U,0x71374491U,0xb5c0fbcfU,0xe9b5dba5U,0x3956c25bU,0x59f111f1U,0x923f82a4U,0xab1c5ed5U,
    0xd807aa98U,0x12835b01U,0x243185beU,0x550c7dc3U,0x72be5d74U,0x80deb1feU,0x9bdc06a7U,0xc19bf174U,
    0xe49b69c1U,0xefbe4786U,0x0fc19dc6U,0x240ca1ccU,0x2de92c6fU,0x4a7484aaU,0x5cb0a9dcU,0x76f988daU,
    0x983e5152U,0xa831c66dU,0xb00327c8U,0xbf597fc7U,0xc6e00bf3U,0xd5a79147U,0x06ca6351U,0x14292967U,
    0x27b70a85U,0x2e1b2138U,0x4d2c6dfcU,0x53380d13U,0x650a7354U,0x766a0abbU,0x81c2c92eU,0x92722c85U,
    0xa2bfe8a1U,0xa81a664bU,0xc24b8b70U,0xc76c51a3U,0xd192e819U,0xd6990624U,0xf40e3585U,0x106aa070U,
    0x19a4c116U,0x1e376c08U,0x2748774cU,0x34b0bcb5U,0x391c0cb3U,0x4ed8aa4aU,0x5b9cca4fU,0x682e6ff3U,
    0x748f82eeU,0x78a5636fU,0x84c87814U,0x8cc70208U,0x90befffaU,0xa4506cebU,0xbef9a3f7U,0xc67178f2U
};

static void gc_sha256_transform(GcSha256Ctx *ctx, const uint8_t data[]) {
    uint32_t a,b,c,d,e,f,g,h,i,j,t1,t2,m[64];
    for (i = 0, j = 0; i < 16; ++i, j += 4)
        m[i] = ((uint32_t)data[j] << 24) | ((uint32_t)data[j + 1] << 16) | ((uint32_t)data[j + 2] << 8) | ((uint32_t)data[j + 3]);
    for (; i < 64; ++i)
        m[i] = GC_SHA256_SIG1(m[i - 2]) + m[i - 7] + GC_SHA256_SIG0(m[i - 15]) + m[i - 16];

    a = ctx->state[0]; b = ctx->state[1]; c = ctx->state[2]; d = ctx->state[3];
    e = ctx->state[4]; f = ctx->state[5]; g = ctx->state[6]; h = ctx->state[7];

    for (i = 0; i < 64; ++i) {
        t1 = h + GC_SHA256_EP1(e) + GC_SHA256_CH(e,f,g) + gc_sha256_k[i] + m[i];
        t2 = GC_SHA256_EP0(a) + GC_SHA256_MAJ(a,b,c);
        h = g; g = f; f = e; e = d + t1; d = c; c = b; b = a; a = t1 + t2;
    }

    ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d;
    ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h;
}

static void gc_sha256_init(GcSha256Ctx *ctx) {
    ctx->datalen = 0;
    ctx->bitlen = 0;
    ctx->state[0] = 0x6a09e667U; ctx->state[1] = 0xbb67ae85U; ctx->state[2] = 0x3c6ef372U; ctx->state[3] = 0xa54ff53aU;
    ctx->state[4] = 0x510e527fU; ctx->state[5] = 0x9b05688cU; ctx->state[6] = 0x1f83d9abU; ctx->state[7] = 0x5be0cd19U;
}

static void gc_sha256_update(GcSha256Ctx *ctx, const uint8_t data[], size_t len) {
    for (size_t i = 0; i < len; ++i) {
        ctx->data[ctx->datalen++] = data[i];
        if (ctx->datalen == 64) {
            gc_sha256_transform(ctx, ctx->data);
            ctx->bitlen += 512;
            ctx->datalen = 0;
        }
    }
}

static void gc_sha256_final(GcSha256Ctx *ctx, uint8_t hash[]) {
    uint32_t i = ctx->datalen;
    if (ctx->datalen < 56) {
        ctx->data[i++] = 0x80;
        while (i < 56) ctx->data[i++] = 0x00;
    } else {
        ctx->data[i++] = 0x80;
        while (i < 64) ctx->data[i++] = 0x00;
        gc_sha256_transform(ctx, ctx->data);
        memset(ctx->data, 0, 56);
    }
    ctx->bitlen += (uint64_t)ctx->datalen * 8;
    ctx->data[63] = (uint8_t)(ctx->bitlen);
    ctx->data[62] = (uint8_t)(ctx->bitlen >> 8);
    ctx->data[61] = (uint8_t)(ctx->bitlen >> 16);
    ctx->data[60] = (uint8_t)(ctx->bitlen >> 24);
    ctx->data[59] = (uint8_t)(ctx->bitlen >> 32);
    ctx->data[58] = (uint8_t)(ctx->bitlen >> 40);
    ctx->data[57] = (uint8_t)(ctx->bitlen >> 48);
    ctx->data[56] = (uint8_t)(ctx->bitlen >> 56);
    gc_sha256_transform(ctx, ctx->data);
    for (i = 0; i < 4; ++i) {
        hash[i]      = (uint8_t)((ctx->state[0] >> (24 - i * 8)) & 0x000000ffU);
        hash[i + 4]  = (uint8_t)((ctx->state[1] >> (24 - i * 8)) & 0x000000ffU);
        hash[i + 8]  = (uint8_t)((ctx->state[2] >> (24 - i * 8)) & 0x000000ffU);
        hash[i + 12] = (uint8_t)((ctx->state[3] >> (24 - i * 8)) & 0x000000ffU);
        hash[i + 16] = (uint8_t)((ctx->state[4] >> (24 - i * 8)) & 0x000000ffU);
        hash[i + 20] = (uint8_t)((ctx->state[5] >> (24 - i * 8)) & 0x000000ffU);
        hash[i + 24] = (uint8_t)((ctx->state[6] >> (24 - i * 8)) & 0x000000ffU);
        hash[i + 28] = (uint8_t)((ctx->state[7] >> (24 - i * 8)) & 0x000000ffU);
    }
}

static void gc_hmac_sha256(const uint8_t *key, size_t keylen, const uint8_t *msg, size_t msglen, uint8_t out[32]) {
    uint8_t kopad[64], kipad[64], khash[32];
    if (keylen > 64) {
        GcSha256Ctx kctx;
        gc_sha256_init(&kctx);
        gc_sha256_update(&kctx, key, keylen);
        gc_sha256_final(&kctx, khash);
        key = khash;
        keylen = 32;
    }
    memset(kopad, 0x5c, sizeof(kopad));
    memset(kipad, 0x36, sizeof(kipad));
    for (size_t i = 0; i < keylen; i++) { kopad[i] ^= key[i]; kipad[i] ^= key[i]; }

    uint8_t inner[32];
    GcSha256Ctx ctx;
    gc_sha256_init(&ctx);
    gc_sha256_update(&ctx, kipad, sizeof(kipad));
    gc_sha256_update(&ctx, msg, msglen);
    gc_sha256_final(&ctx, inner);

    gc_sha256_init(&ctx);
    gc_sha256_update(&ctx, kopad, sizeof(kopad));
    gc_sha256_update(&ctx, inner, sizeof(inner));
    gc_sha256_final(&ctx, out);
}

static void gc_hex32(const uint8_t in[32], char out[65]) {
    static const char hexd[] = "0123456789abcdef";
    for (int i = 0; i < 32; i++) { out[i * 2] = hexd[in[i] >> 4]; out[i * 2 + 1] = hexd[in[i] & 15]; }
    out[64] = '\0';
}

static char *gc_trim_ws_inplace(char *s) {
    if (!s) return s;
    while (*s && isspace((unsigned char)*s)) s++;
    char *end = s + strlen(s);
    while (end > s && isspace((unsigned char)end[-1])) *--end = '\0';
    return s;
}

static int gc_gerar_chave_selo(char *log, size_t logsz) {
    if (mkdir_p(GC_SELOS_DIR, 0700) != 0) { sb_add(log, logsz, "nao foi possivel criar diretorio de selos\n"); return -1; }
    chmod(GC_SELOS_DIR, 0700);
    uint8_t rnd[32];
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0) { sb_add(log, logsz, "nao foi possivel abrir /dev/urandom para chave de selo\n"); return -1; }
    size_t off = 0;
    while (off < sizeof(rnd)) {
        ssize_t n = read(fd, rnd + off, sizeof(rnd) - off);
        if (n <= 0) { close(fd); sb_add(log, logsz, "falha lendo aleatoriedade para chave de selo\n"); return -1; }
        off += (size_t)n;
    }
    close(fd);
    char hex[66];
    static const char hexd[] = "0123456789abcdef";
    for (int i = 0; i < 32; i++) { hex[i * 2] = hexd[rnd[i] >> 4]; hex[i * 2 + 1] = hexd[rnd[i] & 15]; }
    hex[64] = '\n'; hex[65] = '\0';
    int out = open(GC_SELOS_CHAVE, O_WRONLY | O_CREAT | O_EXCL, 0600);
    if (out < 0 && errno == EEXIST) return 0;
    if (out < 0) { sb_add(log, logsz, "nao foi possivel gravar chave de selo\n"); return -1; }
    ssize_t wr = write(out, hex, 65);
    if (close(out) != 0 || wr != 65) { sb_add(log, logsz, "falha escrevendo chave de selo\n"); return -1; }
    chmod(GC_SELOS_CHAVE, 0600);
    return 0;
}

static int gc_obter_chave_selo(uint8_t *key, size_t *keylen, char *log, size_t logsz) {
    if (!is_file_p(GC_SELOS_CHAVE)) {
        if (gc_gerar_chave_selo(log, logsz) != 0) return -1;
    }
    chmod(GC_SELOS_DIR, 0700);
    chmod(GC_SELOS_CHAVE, 0600);
    size_t sz = 0;
    char *txt = read_file_alloc(GC_SELOS_CHAVE, &sz);
    if (!txt) { sb_add(log, logsz, "nao foi possivel ler chave de selo\n"); return -1; }
    char *t = gc_trim_ws_inplace(txt);
    size_t n = strlen(t);
    if (n < 32 || n > 512) { free(txt); sb_add(log, logsz, "chave de selo invalida\n"); return -1; }
    memcpy(key, t, n);
    *keylen = n;
    free(txt);
    return 0;
}

static int gc_selo_payload(char *out, size_t tam, const char *status, const char *pacote, const char *sha,
                           const char *conector, const char *emitido, const char *emissor,
                           const char *ambiente, const char *resultado) {
    int n = snprintf(out, tam,
        GC_SELO_VERSAO "\n"
        "status=%s\n"
        "pacote=%s\n"
        "sha256=%s\n"
        "conector=%s\n"
        "emitidoEm=%s\n"
        "emissor=%s\n"
        "ambiente=%s\n"
        "resultado=%s\n",
        status ? status : "", pacote ? pacote : "", sha ? sha : "", conector ? conector : "",
        emitido ? emitido : "", emissor ? emissor : "", ambiente ? ambiente : "", resultado ? resultado : "");
    return (n >= 0 && (size_t)n < tam) ? 0 : -1;
}

static int gc_assinar_payload_selo(const char *payload, char assinatura_hex[65], char *log, size_t logsz) {
    uint8_t key[512]; size_t keylen = 0;
    if (gc_obter_chave_selo(key, &keylen, log, logsz) != 0) return -1;
    uint8_t digest[32];
    gc_hmac_sha256(key, keylen, (const uint8_t *)payload, strlen(payload), digest);
    gc_hex32(digest, assinatura_hex);
    memset(key, 0, sizeof(key));
    return 0;
}

static int gc_str_eq_const(const char *a, const char *b) {
    size_t la = strlen(a ? a : ""), lb = strlen(b ? b : ""), n = la > lb ? la : lb;
    unsigned char diff = (unsigned char)(la ^ lb);
    for (size_t i = 0; i < n; i++) {
        unsigned char ca = i < la ? (unsigned char)a[i] : 0;
        unsigned char cb = i < lb ? (unsigned char)b[i] : 0;
        diff |= (unsigned char)(ca ^ cb);
    }
    return diff == 0;
}

static int gc_emitir_selo_pacote(const char *pacote_arg, const char *status, const char *conector,
                                 const char *ambiente, const char *resultado, const char *emissor,
                                 char *log, size_t logsz) {
    char pacote[PATH_MAX];
    if (canonical_path(pacote_arg, pacote, sizeof(pacote)) != 0) { sb_add(log, logsz, "caminho de pacote invalido para selo\n"); return -1; }
    char sha[128] = ""; sha256_file(pacote, sha, sizeof(sha));
    if (strlen(sha) != 64) { sb_add(log, logsz, "sha256 indisponivel para selo: %s\n", pacote); return -1; }
    char emitido[32]; snprintf(emitido, sizeof(emitido), "%ld", (long)time(NULL));
    const char *em = emissor && *emissor ? emissor : "gitconectores";
    const char *amb = ambiente && *ambiente ? ambiente : "servidor";
    const char *res = resultado && *resultado ? resultado : "ok";
    char payload[8192];
    if (gc_selo_payload(payload, sizeof(payload), status, pacote, sha, conector, emitido, em, amb, res) != 0) { sb_add(log, logsz, "payload de selo excedeu limite\n"); return -1; }
    char sighex[65];
    if (gc_assinar_payload_selo(payload, sighex, log, logsz) != 0) return -1;
    char assinatura[96]; snprintf(assinatura, sizeof(assinatura), GC_SELO_VERSAO ":%s", sighex);

    char epac[PATH_MAX * 2], econ[512], eamb[512], eres[2048], eem[512], eass[128], esha[160], eemi[64], estat[128];
    gc_json_escape_copy(pacote, epac, sizeof(epac)); gc_json_escape_copy(conector, econ, sizeof(econ));
    gc_json_escape_copy(amb, eamb, sizeof(eamb)); gc_json_escape_copy(res, eres, sizeof(eres));
    gc_json_escape_copy(em, eem, sizeof(eem)); gc_json_escape_copy(assinatura, eass, sizeof(eass));
    gc_json_escape_copy(sha, esha, sizeof(esha)); gc_json_escape_copy(emitido, eemi, sizeof(eemi)); gc_json_escape_copy(status, estat, sizeof(estat));

    char marca[PATH_MAX]; marker_path(pacote, status, marca, sizeof(marca));
    char json[32768];
    int n = snprintf(json, sizeof(json),
        "{\n"
        "  \"status\": \"%s\",\n"
        "  \"versaoSelo\": \"" GC_SELO_VERSAO "\",\n"
        "  \"pacote\": \"%s\",\n"
        "  \"sha256\": \"%s\",\n"
        "  \"conector\": \"%s\",\n"
        "  \"ambiente\": \"%s\",\n"
        "  \"resultado\": \"%s\",\n"
        "  \"emitidoEm\": \"%s\",\n"
        "  \"emissor\": \"%s\",\n"
        "  \"assinaturaAlgoritmo\": \"HMAC-SHA256\",\n"
        "  \"assinaturaCampos\": \"versao/status/pacote/sha256/conector/emitidoEm/emissor/ambiente/resultado\",\n"
        "  \"assinatura\": \"%s\"\n"
        "}\n",
        estat, epac, esha, econ, eamb, eres, eemi, eem, eass);
    if (n < 0 || (size_t)n >= sizeof(json)) { sb_add(log, logsz, "JSON de selo excedeu limite\n"); return -1; }
    if (write_text_file(marca, json, 0444) != 0) { sb_add(log, logsz, "falha gravando selo: %s\n", marca); return -1; }
    chmod(marca, 0444);
    sb_add(log, logsz, "selo emitido: %s\n", marca);
    return 0;
}

static int gc_validar_selo_pacote(const char *pacote_arg, const char *status_esperado, char *conector_out, size_t conector_tam, char *log, size_t logsz) {
    char pacote[PATH_MAX];
    if (canonical_path(pacote_arg, pacote, sizeof(pacote)) != 0) { sb_add(log, logsz, "caminho de pacote invalido para validar selo\n"); return -1; }
    char marca[PATH_MAX]; marker_path(pacote, status_esperado, marca, sizeof(marca));
    char *txt = read_file_alloc(marca, NULL);
    if (!txt) { sb_add(log, logsz, "selo ausente: %s\n", marca); return -1; }
    char status[128] = "", pacote_selo[PATH_MAX] = "", sha_selo[128] = "", conector[256] = "";
    char emitido[64] = "", emissor[256] = "", ambiente[256] = "", resultado[1024] = "", assinatura[128] = "";
    int ok = 1;
    if (!json_get_string(txt, "status", status, sizeof(status))) ok = 0;
    if (!json_get_string(txt, "pacote", pacote_selo, sizeof(pacote_selo))) ok = 0;
    if (!json_get_string(txt, "sha256", sha_selo, sizeof(sha_selo))) ok = 0;
    if (!json_get_string(txt, "conector", conector, sizeof(conector))) ok = 0;
    if (!json_get_string(txt, "emitidoEm", emitido, sizeof(emitido))) ok = 0;
    if (!json_get_string(txt, "emissor", emissor, sizeof(emissor))) ok = 0;
    if (!json_get_string(txt, "ambiente", ambiente, sizeof(ambiente))) ok = 0;
    if (!json_get_string(txt, "resultado", resultado, sizeof(resultado))) ok = 0;
    if (!json_get_string(txt, "assinatura", assinatura, sizeof(assinatura))) ok = 0;
    free(txt);
    if (!ok) { sb_add(log, logsz, "selo incompleto: %s\n", marca); return -1; }
    if (strcmp(status, status_esperado) != 0) { sb_add(log, logsz, "selo com status incorreto: %s\n", marca); return -1; }
    if (strcmp(pacote_selo, pacote) != 0) { sb_add(log, logsz, "selo aponta para outro pacote: %s\n", marca); return -1; }
    char sha_atual[128] = ""; sha256_file(pacote, sha_atual, sizeof(sha_atual));
    if (strcmp(sha_selo, sha_atual) != 0) { sb_add(log, logsz, "sha256 do pacote nao confere com selo: %s\n", pacote); return -1; }
    char payload[8192];
    if (gc_selo_payload(payload, sizeof(payload), status, pacote_selo, sha_selo, conector, emitido, emissor, ambiente, resultado) != 0) { sb_add(log, logsz, "payload de validacao de selo excedeu limite\n"); return -1; }
    char sighex[65];
    if (gc_assinar_payload_selo(payload, sighex, log, logsz) != 0) return -1;
    char esperado[96]; snprintf(esperado, sizeof(esperado), GC_SELO_VERSAO ":%s", sighex);
    if (!gc_str_eq_const(assinatura, esperado)) { sb_add(log, logsz, "assinatura de selo invalida: %s\n", marca); return -1; }
    if (conector_out && conector_tam) snprintf(conector_out, conector_tam, "%s", conector);
    return 0;
}

static int path_join(char *out, size_t tam, const char *a, const char *b) {
    return snprintf(out, tam, "%s/%s", a, b) < (int)tam ? 0 : -1;
}

static int find_project_root(const char *tmp, char *out, size_t tam) {
    char p1[PATH_MAX], p2[PATH_MAX], p3[PATH_MAX];
    snprintf(p1, sizeof(p1), "%s/conectores", tmp); snprintf(p2, sizeof(p2), "%s/web-api", tmp); snprintf(p3, sizeof(p3), "%s/siscconectores/web-api", tmp);
    if (is_dir_p(p1) && (is_dir_p(p2) || is_dir_p(p3))) { snprintf(out, tam, "%s", tmp); return 0; }
    DIR *d = opendir(tmp); if (!d) return -1;
    struct dirent *e;
    while ((e = readdir(d))) {
        if (e->d_name[0] == '.') continue;
        char sub[PATH_MAX]; path_join(sub, sizeof(sub), tmp, e->d_name);
        if (!is_dir_p(sub)) continue;
        snprintf(p1, sizeof(p1), "%s/conectores", sub); snprintf(p2, sizeof(p2), "%s/web-api", sub); snprintf(p3, sizeof(p3), "%s/siscconectores/web-api", sub);
        if (is_dir_p(p1) && (is_dir_p(p2) || is_dir_p(p3))) { snprintf(out, tam, "%s", sub); closedir(d); return 0; }
    }
    closedir(d);
    return -1;
}

static int scan_secretos(const char *dir, const char *rel, char *log, size_t logsz) {
    DIR *d = opendir(dir); if (!d) return 0;
    struct dirent *e; int erro = 0;
    while ((e = readdir(d))) {
        if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
        char p[PATH_MAX], r[PATH_MAX]; path_join(p, sizeof(p), dir, e->d_name);
        snprintf(r, sizeof(r), "%s%s%s", rel, rel[0] ? "/" : "", e->d_name);
        if (is_dir_p(p)) erro |= scan_secretos(p, r, log, logsz);
        else if ((starts_with(r, "secretos/") || starts_with(r, "siscconectores/secretos/")) && !ends_with(r, ".sample.json")) { sb_add(log, logsz, "arquivo secreto real proibido: %s\n", r); erro = 1; }
    }
    closedir(d); return erro;
}

static int identify_connector(const char *root, char *nome, size_t ntam, char *manifesto, size_t mtam, char *log, size_t logsz) {
    char cdir[PATH_MAX]; snprintf(cdir, sizeof(cdir), "%s/conectores", root);
    DIR *d = opendir(cdir); if (!d) { sb_add(log, logsz, "diretorio conectores/ ausente\n"); return -1; }
    int count = 0; struct dirent *e; char found[256] = "";
    while ((e = readdir(d))) {
        if (e->d_name[0] == '.') continue;
        char sub[PATH_MAX], man[PATH_MAX]; path_join(sub, sizeof(sub), cdir, e->d_name);
        if (!is_dir_p(sub)) continue;
        snprintf(man, sizeof(man), "%s/%s.json", sub, e->d_name);
        if (is_file_p(man)) { count++; snprintf(found, sizeof(found), "%s", e->d_name); snprintf(manifesto, mtam, "%s", man); }
    }
    closedir(d);
    if (count != 1) { sb_add(log, logsz, "esperado exatamente um conector; encontrados=%d\n", count); return -1; }
    if (!nome_conector_valido(found)) { sb_add(log, logsz, "nome de conector invalido: %s\n", found); return -1; }
    snprintf(nome, ntam, "%s", found); return 0;
}

static int count_substr_ci(const char *s, const char *needle) {
    if (!s || !needle || !*needle) return 0;
    int count = 0;
    size_t n = strlen(needle);
    for (const char *p = s; *p; p++) {
        size_t i = 0;
        while (needle[i] && p[i] && tolower((unsigned char)p[i]) == tolower((unsigned char)needle[i])) i++;
        if (i == n) { count++; p += n - 1; }
    }
    return count;
}

static int contains_ci(const char *s, const char *needle) {
    return count_substr_ci(s, needle) > 0;
}

static void front_dir_from_nome(const char *nome, char *out, size_t tam) {
    if (!out || tam == 0) return;
    size_t j = 0;
    for (size_t i = 0; nome && nome[i] && j + 1 < tam; i++) out[j++] = nome[i] == '-' ? '_' : nome[i];
    out[j] = '\0';
}

static int validar_manual_usuario_qualidade_c(const char *html, const char *manual_rel, const char *nome, char *log, size_t logsz) {
    int erros = 0;
    if (!html || !*html) { sb_add(log, logsz, "manual do usuario vazio: %s\n", manual_rel); return 1; }
    if (strlen(html) < 6000) { sb_add(log, logsz, "manual do usuario curto demais; use padrao conector-modelo com identificacao, tabelas, exemplos, saida, credenciais, erros, limites, seguranca de uso e boas praticas: %s\n", manual_rel); erros++; }
    if (!contains_ci(html, "<html") || !contains_ci(html, "</html>")) { sb_add(log, logsz, "manual do usuario deve ser HTML completo\n"); erros++; }
    if (!contains_ci(html, "<style")) { sb_add(log, logsz, "manual do usuario deve conter CSS proprio de leitura\n"); erros++; }
    if (count_substr_ci(html, "<table") < 2) { sb_add(log, logsz, "manual do usuario deve usar tabelas para operacoes/campos/erros\n"); erros++; }
    if (count_substr_ci(html, "<pre") < 3) { sb_add(log, logsz, "manual do usuario deve conter ao menos tres exemplos em <pre><code>\n"); erros++; }
    char destino[320]; snprintf(destino, sizeof(destino), "conector__%s", nome ? nome : "");
    if (!contains_ci(html, nome ? nome : "") || !contains_ci(html, destino)) { sb_add(log, logsz, "manual do usuario deve identificar conector e destino SISC %s\n", destino); erros++; }
    const char *secoes[] = {"Identificação", "Objetivo", "Operações", "Payload de entrada", "Exemplo", "Saída esperada", "Programa de exemplo", "Credenciais", "Erros comuns", "Limites", "Segurança de uso", "Boas práticas"};
    for (size_t i = 0; i < sizeof(secoes)/sizeof(secoes[0]); i++) if (!contains_ci(html, secoes[i])) { sb_add(log, logsz, "manual do usuario sem secao obrigatoria: %s\n", secoes[i]); erros++; }
    const char *termos[] = {"payload.dados", "idmensagem", "catalogo", "destino SISC", "idempotencia"};
    for (size_t i = 0; i < sizeof(termos)/sizeof(termos[0]); i++) if (!contains_ci(html, termos[i])) { sb_add(log, logsz, "manual do usuario deve explicar: %s\n", termos[i]); erros++; }
    const char *internos[] = {"sandbox-handler", "validar-recebidos", "testar-sandbox", "instalar-aprovados", "/var/www/html/gitconectores"};
    for (size_t i = 0; i < sizeof(internos)/sizeof(internos[0]); i++) if (contains_ci(html, internos[i])) { sb_add(log, logsz, "manual do usuario nao deve citar detalhe operacional interno do servidor SISC: %s\n", internos[i]); erros++; }
    if (strstr(html, "PREENCHER") || strstr(html, "TODO")) { sb_add(log, logsz, "manual do usuario contem placeholder/TODO\n"); erros++; }
    return erros;
}

static int validar_exemplo_uso_c(const char *root, const char *nome, const char *cat, char *log, size_t logsz) {
    int erros = 0;
    char rel[PATH_MAX], abs[PATH_MAX];
    snprintf(rel, sizeof(rel), "siscconectores/%s-uso.php", nome ? nome : "");
    snprintf(abs, sizeof(abs), "%s/%s", root ? root : ".", rel);
    char *txt = read_file_alloc(abs, NULL);
    if (!txt) {
        snprintf(rel, sizeof(rel), "conectores/%s/exemplos/exemplo-uso.php", nome ? nome : "");
        snprintf(abs, sizeof(abs), "%s/%s", root ? root : ".", rel);
        txt = read_file_alloc(abs, NULL);
    }
    if (!txt) { sb_add(log, logsz, "programa de exemplo obrigatorio ausente: siscconectores/%s-uso.php\n", nome ? nome : ""); return 1; }
    if (!starts_with(txt, "#!") && !contains_ci(txt, "<?php")) { sb_add(log, logsz, "programa de exemplo PHP deve ter shebang ou <?php: %s\n", rel); erros++; }
    if (strstr(txt, "PREENCHER") || strstr(txt, "TODO")) { sb_add(log, logsz, "programa de exemplo contem placeholder/TODO: %s\n", rel); erros++; }
    if (!contains_ci(txt, "payload.dados") && !contains_ci(txt, "'dados'") && !contains_ci(txt, "\"dados\"")) { sb_add(log, logsz, "programa de exemplo deve montar payload/dados: %s\n", rel); erros++; }
    if (!contains_ci(txt, "idmensagem")) { sb_add(log, logsz, "programa de exemplo deve demonstrar idmensagem do catalogo: %s\n", rel); erros++; }
    if (!contains_ci(txt, nome ? nome : "")) { sb_add(log, logsz, "programa de exemplo deve referenciar o conector %s\n", nome ? nome : ""); erros++; }
    free(txt);

    char q[PATH_MAX * 2], cmd[PATH_MAX * 4], out[65536];
    shell_quote(abs, q, sizeof(q));
    snprintf(cmd, sizeof(cmd), "php -n -l %s 2>&1", q);
    if (capture_cmd(cmd, out, sizeof(out)) != 0) { sb_add(log, logsz, "programa de exemplo PHP com erro de sintaxe: %s %.1000s\n", rel, out); erros++; }
    snprintf(cmd, sizeof(cmd), "timeout 15s php -n %s --self-test 2>&1", q);
    if (capture_cmd(cmd, out, sizeof(out)) != 0) { sb_add(log, logsz, "programa de exemplo falhou no --self-test: %s %.1000s\n", rel, out); erros++; }
    else {
        char con[256] = "", idm[256] = "";
        if (!contains_key_bool_true(out, "sucesso")) { sb_add(log, logsz, "programa de exemplo --self-test deve retornar sucesso=true\n"); erros++; }
        if (!json_get_string(out, "conector", con, sizeof(con)) || strcmp(con, nome) != 0) { sb_add(log, logsz, "programa de exemplo --self-test deve retornar conector=%s\n", nome); erros++; }
        if (!json_get_string(out, "idmensagem", idm, sizeof(idm)) || idm[0] == '\0') { sb_add(log, logsz, "programa de exemplo --self-test deve retornar idmensagem\n"); erros++; }
        else if (!cat || !strstr(cat, idm)) { sb_add(log, logsz, "programa de exemplo retornou idmensagem nao presente no catalogo: %s\n", idm); erros++; }
        if (!strstr(out, "payload") || !strstr(out, "dados")) { sb_add(log, logsz, "programa de exemplo --self-test deve retornar payload.dados de exemplo\n"); erros++; }
    }
    if (erros == 0) sb_add(log, logsz, "programa de exemplo validado: %s\n", rel);
    return erros;
}

static int validar_exemplo_consumidor_c(const char *root, const char *nome, const char *cat, char *log, size_t logsz) {
    int erros = 0;
    char rel[PATH_MAX], abs[PATH_MAX];
    snprintf(rel, sizeof(rel), "siscconectores/%s-cliente.php", nome ? nome : "");
    snprintf(abs, sizeof(abs), "%s/%s", root ? root : ".", rel);

    char *txt = read_file_alloc(abs, NULL);
    if (!txt) {
        snprintf(rel, sizeof(rel), "conectores/%s/exemplos/exemplo-cliente.php", nome ? nome : "");
        snprintf(abs, sizeof(abs), "%s/%s", root ? root : ".", rel);
        txt = read_file_alloc(abs, NULL);
    }
    if (!txt) { sb_add(log, logsz, "programa consumidor obrigatorio ausente: siscconectores/%s-cliente.php; deve ser portatil e consumir por URL publica da API SISC\n", nome ? nome : ""); return 1; }
    if (!starts_with(txt, "#!") && !contains_ci(txt, "<?php")) { sb_add(log, logsz, "programa consumidor PHP deve ter shebang ou <?php: %s\n", rel); erros++; }
    if (strstr(txt, "PREENCHER") || strstr(txt, "TODO")) { sb_add(log, logsz, "programa consumidor contem placeholder/TODO: %s\n", rel); erros++; }
    if (!contains_ci(txt, nome ? nome : "")) { sb_add(log, logsz, "programa consumidor deve referenciar o conector %s\n", nome ? nome : ""); erros++; }
    if (!contains_ci(txt, "idmensagem")) { sb_add(log, logsz, "programa consumidor deve demonstrar idmensagem do catalogo: %s\n", rel); erros++; }
    if (!contains_ci(txt, "http") && !contains_ci(txt, "api") && !contains_ci(txt, "->enviar(")) { sb_add(log, logsz, "programa consumidor deve consumir a API HTTP publica do SISC: %s\n", rel); erros++; }
    if (!contains_ci(txt, "curl_") && !contains_ci(txt, "file_get_contents") && !contains_ci(txt, "stream_context_create") && !contains_ci(txt, "->enviar(")) { sb_add(log, logsz, "programa consumidor deve demonstrar chamada HTTP/API real: %s\n", rel); erros++; }
    const char *proibidos[] = {"handlers/", "/handlers/", "espaco/", "/espaco", "/var/www/html"};
    for (size_t i = 0; i < sizeof(proibidos)/sizeof(proibidos[0]); i++) if (contains_ci(txt, proibidos[i])) { sb_add(log, logsz, "programa consumidor nao deve acessar ou revelar caminhos internos; consuma somente via API SISC\n"); erros++; }
    free(txt);

    char q[PATH_MAX * 2], cmd[PATH_MAX * 4], out[65536];
    shell_quote(abs, q, sizeof(q));
    snprintf(cmd, sizeof(cmd), "php -n -l %s 2>&1", q);
    if (capture_cmd(cmd, out, sizeof(out)) != 0) { sb_add(log, logsz, "programa consumidor PHP com erro de sintaxe: %s %.1000s\n", rel, out); erros++; }
    snprintf(cmd, sizeof(cmd), "timeout 15s php -n %s --self-test 2>&1", q);
    if (capture_cmd(cmd, out, sizeof(out)) != 0) { sb_add(log, logsz, "programa consumidor falhou no --self-test: %s %.1000s\n", rel, out); erros++; }
    else {
        char con[256] = "", idm[256] = "";
        if (!contains_key_bool_true(out, "sucesso")) { sb_add(log, logsz, "programa consumidor --self-test deve retornar sucesso=true\n"); erros++; }
        if (!json_get_string(out, "conector", con, sizeof(con)) || strcmp(con, nome) != 0) { sb_add(log, logsz, "programa consumidor --self-test deve retornar conector=%s\n", nome); erros++; }
        if (!json_get_string(out, "idmensagem", idm, sizeof(idm)) || idm[0] == '\0') { sb_add(log, logsz, "programa consumidor --self-test deve retornar idmensagem\n"); erros++; }
        else if (!cat || !strstr(cat, idm)) { sb_add(log, logsz, "programa consumidor retornou idmensagem nao presente no catalogo: %s\n", idm); erros++; }
        if (!strstr(out, "dados")) { sb_add(log, logsz, "programa consumidor --self-test deve retornar dados de exemplo\n"); erros++; }
    }
    if (erros == 0) sb_add(log, logsz, "programa consumidor validado: %s\n", rel);
    return erros;
}

static int validar_projeto(const char *root, char *nome_out, size_t nome_tam, char *log, size_t logsz) {
    int erros = 0;
    char nome[256], manifesto_path[PATH_MAX];
    if (identify_connector(root, nome, sizeof(nome), manifesto_path, sizeof(manifesto_path), log, logsz) != 0) return -1;
    snprintf(nome_out, nome_tam, "%s", nome);
    size_t msz = 0; char *man = read_file_alloc(manifesto_path, &msz);
    if (!man) { sb_add(log, logsz, "nao foi possivel ler manifesto\n"); return -1; }
    char val[PATH_MAX];
    if (!json_get_string(man, "nome", val, sizeof(val)) || strcmp(val, nome) != 0) { sb_add(log, logsz, "manifesto.nome deve ser %s\n", nome); erros++; }
    if (!json_get_string(man, "tipo", val, sizeof(val)) || strcmp(val, "conector") != 0) { sb_add(log, logsz, "manifesto.tipo deve ser conector\n"); erros++; }
    if (strstr(man, "PREENCHER") || strstr(man, "TODO") || strstr(man, "EXEMPLO_INVALIDO")) { sb_add(log, logsz, "manifesto contem placeholder/TODO\n"); erros++; }
    if (!strstr(man, "\"executavel\"") || !strstr(man, "./escuta/runtime-conector")) { sb_add(log, logsz, "controlador.executavel invalido\n"); erros++; }
    if (!strstr(man, "\"metodoLeitura\"") || !strstr(man, "ler-mensagem")) { sb_add(log, logsz, "controlador.metodoLeitura invalido\n"); erros++; }
    if (!strstr(man, "\"metodoEnvio\"") || !strstr(man, "POST api.php")) { sb_add(log, logsz, "controlador.metodoEnvio invalido\n"); erros++; }
    if (!contains_key_bool_true(man, "fonteSeguraImportacao")) { sb_add(log, logsz, "dependencias.fonteSeguraImportacao deve ser true\n"); erros++; }
    const char *roles[] = {"manifesto", "formato", "handler", "teste-sandbox", "catalogo-mensagens", "manual-usuario", "exemplo-uso", "exemplo-consumidor"};
    for (size_t i = 0; i < sizeof(roles)/sizeof(roles[0]); i++) if (!strstr(man, roles[i])) { sb_add(log, logsz, "dependencias.arquivos sem papel %s\n", roles[i]); erros++; }

    char manual[PATH_MAX] = "";
    if (!json_get_string(man, "manualUsuario", manual, sizeof(manual))) snprintf(manual, sizeof(manual), "conectores/%s/manual-%s.html", nome, nome);
    char *murel = manual; if (starts_with(murel, "./")) murel += 2;
    char muabs[PATH_MAX]; snprintf(muabs, sizeof(muabs), "%s/%s", root, murel);
    char *mu = NULL;
    if (!safe_rel(murel) || !(mu = read_file_alloc(muabs, NULL))) { sb_add(log, logsz, "manual HTML do usuario ausente/inseguro: %s\n", murel); erros++; }
    else {
        erros += validar_manual_usuario_qualidade_c(mu, murel, nome, log, logsz);
        free(mu);
    }

    char handler[PATH_MAX] = "", formato[PATH_MAX] = "";
    if (!json_get_string(man, "handlerLerMensagem", handler, sizeof(handler))) { sb_add(log, logsz, "handlerLerMensagem ausente\n"); erros++; }
    if (!json_get_string(man, "formatoConector", formato, sizeof(formato))) { sb_add(log, logsz, "formatoConector ausente\n"); erros++; }
    char *hrel = handler; if (starts_with(hrel, "./")) hrel += 2;
    char hprefix[PATH_MAX]; snprintf(hprefix, sizeof(hprefix), "conectores/%s/handlers/", nome);
    if (handler[0] && (!safe_rel(hrel) || !starts_with(hrel, hprefix))) { sb_add(log, logsz, "handler deve ficar em %s\n", hprefix); erros++; }
    char habs[PATH_MAX]; snprintf(habs, sizeof(habs), "%s/%s", root, hrel);
    if (handler[0] && !is_exec_p(habs)) { sb_add(log, logsz, "handler ausente ou nao executavel: %s\n", hrel); erros++; }
    char *frel = formato; if (starts_with(frel, "./")) frel += 2;
    char fabs[PATH_MAX]; snprintf(fabs, sizeof(fabs), "%s/%s", root, frel);
    char *fmt = NULL;
    if (!safe_rel(frel) || !(fmt = read_file_alloc(fabs, NULL))) { sb_add(log, logsz, "formato ausente/inseguro: %s\n", frel); erros++; }
    else {
        if (!strstr(fmt, "formato-conector")) { sb_add(log, logsz, "formato.tipo invalido\n"); erros++; }
        if (!strstr(fmt, nome)) { sb_add(log, logsz, "formato nao referencia conector %s\n", nome); erros++; }
        if (!strstr(fmt, "\"entrada\"") || !strstr(fmt, "\"saida\"")) { sb_add(log, logsz, "formato deve declarar entrada e saida\n"); erros++; }
        if (strstr(fmt, "PREENCHER") || strstr(fmt, "TODO")) { sb_add(log, logsz, "formato contem placeholder/TODO\n"); erros++; }
        free(fmt);
    }
    char catalogo_path[PATH_MAX]; snprintf(catalogo_path, sizeof(catalogo_path), "%s/siscconectores/web-api/catalogo-%s.json", root, nome);
    char *cat = read_file_alloc(catalogo_path, NULL);
    if (!cat) {
        snprintf(catalogo_path, sizeof(catalogo_path), "%s/web-api/catalogo-%s.json", root, nome);
        cat = read_file_alloc(catalogo_path, NULL);
    }
    if (!cat) { sb_add(log, logsz, "catalogo modular ausente\n"); erros++; }
    else {
        char alvo[320]; snprintf(alvo, sizeof(alvo), "conector__%s", nome);
        if (!strstr(cat, "catalogo-modulo-mensagens-sisc")) { sb_add(log, logsz, "catalogo.tipo invalido\n"); erros++; }
        if (!strstr(cat, alvo)) { sb_add(log, logsz, "catalogo nao referencia %s\n", alvo); erros++; }
        if (!strstr(cat, "\"mensagens\"") || !strstr(cat, "\"ativo\"")) { sb_add(log, logsz, "catalogo sem mensagens/ativo\n"); erros++; }
        if (!strstr(cat, "\"ativo\": true") && !strstr(cat, "\"ativo\" : true")) { sb_add(log, logsz, "catalogo deve ter ao menos uma mensagem ativa\n"); erros++; }
        if (!strstr(cat, "\"idempotenciaObrigatoria\"") || !strstr(cat, "\"correlacaoObrigatoria\"")) { sb_add(log, logsz, "catalogo deve exigir idempotencia e correlacao\n"); erros++; }
        const char *fp = cat;
        while ((fp = strstr(fp, "\"front-api\"")) != NULL) {
            const char *prox = strstr(fp + 11, "\"front-api\"");
            size_t trecho_len = prox ? (size_t)(prox - fp) : strlen(fp);
            char trecho[8192];
            size_t cp = trecho_len < sizeof(trecho) - 1 ? trecho_len : sizeof(trecho) - 1;
            memcpy(trecho, fp, cp); trecho[cp] = '\0';
            if (contains_key_bool_true(trecho, "ativo")) {
                if (!strstr(trecho, "\"acao\"")) { sb_add(log, logsz, "front-api ativo deve declarar acao\n"); erros++; }
                if (!strstr(trecho, "POST")) { sb_add(log, logsz, "front-api ativo deve usar metodo POST\n"); erros++; }
                if (!contains_key_bool_true(trecho, "csrf")) { sb_add(log, logsz, "front-api ativo deve declarar csrf=true\n"); erros++; }
                if (!strstr(trecho, "\"autenticacao\"")) { sb_add(log, logsz, "front-api ativo deve declarar autenticacao\n"); erros++; }
                if (!strstr(trecho, "\"entrada\"")) { sb_add(log, logsz, "front-api ativo deve declarar entrada\n"); erros++; }
                if (!strstr(trecho, "\"dadosSisc\"")) { sb_add(log, logsz, "front-api ativo deve declarar dadosSisc\n"); erros++; }
            }
            fp += 11;
        }
        if (strstr(cat, "PREENCHER") || strstr(cat, "TODO")) { sb_add(log, logsz, "catalogo contem placeholder/TODO\n"); erros++; }
        erros += validar_exemplo_uso_c(root, nome, cat, log, logsz);
        erros += validar_exemplo_consumidor_c(root, nome, cat, log, logsz);
        free(cat);
    }
    char teste_path[PATH_MAX]; snprintf(teste_path, sizeof(teste_path), "%s/conectores/%s/testes/mensagem-exemplo.json", root, nome);
    char *tes = read_file_alloc(teste_path, NULL);
    if (!tes) { sb_add(log, logsz, "conectores/%s/testes/mensagem-exemplo.json ausente\n", nome); erros++; }
    else {
        char alvo[320]; snprintf(alvo, sizeof(alvo), "conector__%s", nome);
        if (!strstr(tes, "_sistema") || !strstr(tes, "_protocolo") || !strstr(tes, "payload") || !strstr(tes, "dados")) { sb_add(log, logsz, "mensagem de teste incompleta\n"); erros++; }
        if (!strstr(tes, alvo)) { sb_add(log, logsz, "mensagem de teste deve apontar para %s\n", alvo); erros++; }
        char proto_nome[64] = "";
        if (!json_get_string(tes, "nome", proto_nome, sizeof(proto_nome)) || strcmp(proto_nome, "siscore-protocolo-objetos") != 0) { sb_add(log, logsz, "mensagem de teste sem _protocolo.nome=siscore-protocolo-objetos\n"); erros++; }
        if (!strstr(tes, "\"versao\": 1") && !strstr(tes, "\"versao\":1") && !strstr(tes, "\"versao\" : 1") && !strstr(tes, "\"versao\" :1")) { sb_add(log, logsz, "mensagem de teste sem _protocolo.versao=1\n"); erros++; }
        char tipo_proto[64] = "";
        if (!json_get_string(tes, "tipo", tipo_proto, sizeof(tipo_proto))) { sb_add(log, logsz, "mensagem de teste sem _protocolo.tipo\n"); erros++; }
        else if (strcmp(tipo_proto,"solicitacao") && strcmp(tipo_proto,"resposta") && strcmp(tipo_proto,"evento") && strcmp(tipo_proto,"erro") && strcmp(tipo_proto,"comando") && strcmp(tipo_proto,"consulta")) { sb_add(log, logsz, "mensagem de teste com _protocolo.tipo invalido: %s\n", tipo_proto); erros++; }
        char prio_proto[64] = "";
        if (json_get_string(tes, "prioridade", prio_proto, sizeof(prio_proto)) && prio_proto[0] != '\0' && strcmp(prio_proto,"baixa") && strcmp(prio_proto,"normal") && strcmp(prio_proto,"alta") && strcmp(prio_proto,"critica")) { sb_add(log, logsz, "mensagem de teste com _protocolo.prioridade invalida: %s\n", prio_proto); erros++; }
        char idm_teste[160] = "";
        if (!json_get_string(tes, "idmensagem", idm_teste, sizeof(idm_teste)) || idm_teste[0] == '\0') { sb_add(log, logsz, "mensagem de teste sem payload.idmensagem\n"); erros++; }
        if (strstr(tes, "PREENCHER") || strstr(tes, "TODO")) { sb_add(log, logsz, "mensagem de teste contem placeholder/TODO\n"); erros++; }
        free(tes);
    }
    if (scan_secretos(root, "", log, logsz)) erros++;
    free(man);
    if (erros == 0) sb_add(log, logsz, "VALIDACAO C: APROVADO conector=%s\n", nome);
    else sb_add(log, logsz, "VALIDACAO C: REPROVADO erros=%d conector=%s\n", erros, nome);
    return erros == 0 ? 0 : -1;
}

static int validar_pacote_recebido(const char *pacote, char *nome, size_t ntam, char *log, size_t logsz) {
    char tmp[PATH_MAX]; snprintf(tmp, sizeof(tmp), GC_BASE "/tmp/validacao-c-%ld-%ld", (long)time(NULL), (long)getpid());
    int rc = -1;
    if (extrair_pacote(pacote, tmp, log, logsz) == 0) {
        char root[PATH_MAX];
        if (find_project_root(tmp, root, sizeof(root)) != 0) sb_add(log, logsz, "raiz do projeto nao encontrada no pacote\n");
        else rc = validar_projeto(root, nome, ntam, log, logsz);
    }
    rm_rf(tmp);
    return rc;
}

static int listar_pacotes(char arr[][PATH_MAX], int max) {
    DIR *d = opendir(GC_RECEBIDOS); if (!d) return 0;
    int n = 0; struct dirent *e;
    while ((e = readdir(d)) && n < max) {
        if (e->d_name[0] == '.') continue;
        if (!pacote_ext(e->d_name)) continue;
        snprintf(arr[n], PATH_MAX, "%s/%s", GC_RECEBIDOS, e->d_name);
        n++;
    }
    closedir(d);
    for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) if (strcmp(arr[i], arr[j]) > 0) { char t[PATH_MAX]; strcpy(t, arr[i]); strcpy(arr[i], arr[j]); strcpy(arr[j], t); }
    return n;
}

#endif
