system HF Staff commited on
Commit
1eddb26
·
verified ·
1 Parent(s): 117311e

CI: sync Gap-Finder Space (d1507c1)

Browse files
Files changed (7) hide show
  1. README.md +12 -7
  2. space/DEPLOY.md +37 -13
  3. space/README.md +12 -7
  4. space/app.py +21 -4
  5. space/pipeline.py +8 -0
  6. space/requirements.txt +13 -1
  7. space/reviewer.py +13 -3
README.md CHANGED
@@ -30,17 +30,23 @@ manifest, public key, and instructions beside it are convenience copies.
30
 
31
  ```
32
  unzip uofa-pack-*.zip
33
- uofa verify uofa.jsonld \
34
- --pubkey keys/uofa-issuer.pub \
35
- --decision-pubkey keys/demo-reviewer.pub
36
  ```
37
 
38
  **What a valid signature means here.** Only that the file is unmodified since
39
  this demo produced it. It is not a review and not an acceptance decision. The
40
  signing key is a *demonstration issuer key* held by the demo, not a research or
41
- production key, and not anyone's decision key — so a demo package can never be
42
- mistaken for a formally issued one (it does not verify against the default trust
43
- anchor; `--pubkey` is required, deliberately).
 
 
 
 
 
 
 
 
44
 
45
  The public key travels inside the zip so verification works offline. A trust
46
  anchor shipped inside the artifact it validates only proves self-consistency, so
@@ -48,7 +54,6 @@ compare it against this independent copy:
48
 
49
  ```
50
  keys/uofa-issuer.pub sha256:ead2e1e1068f8c6da14b2c9c384e4d00d8900308ad2e406fe294330ce0edd81d
51
- keys/demo-reviewer.pub sha256:3605a146f4880d9f7a29db6ef5629655091d2ecd0c2b9919cbe49d90d65d83c8
52
  ```
53
 
54
  ## Privacy
 
30
 
31
  ```
32
  unzip uofa-pack-*.zip
33
+ uofa verify uofa.jsonld --pubkey keys/uofa-issuer.pub
 
 
34
  ```
35
 
36
  **What a valid signature means here.** Only that the file is unmodified since
37
  this demo produced it. It is not a review and not an acceptance decision. The
38
  signing key is a *demonstration issuer key* held by the demo, not a research or
39
+ production key — so a demo package can never be mistaken for a formally issued
40
+ one (it does not verify against the default trust anchor; `--pubkey` is
41
+ required, deliberately).
42
+
43
+ **The Space applies an issuer seal only, and signs no decision.** A service key
44
+ cannot stand in for a human reviewer, so the pack carries no decision block and
45
+ `--decision-pubkey` has nothing here to check. Until 2026-09-08 this file
46
+ documented that flag against `keys/demo-reviewer.pub`, which is not one of the
47
+ pack's five members, so the command failed on a missing file and implied an
48
+ attestation the Space deliberately does not make. See
49
+ `docs/UofA_Spec_Unified_Signing_Surface_v1_0.md`.
50
 
51
  The public key travels inside the zip so verification works offline. A trust
52
  anchor shipped inside the artifact it validates only proves self-consistency, so
 
54
 
55
  ```
56
  keys/uofa-issuer.pub sha256:ead2e1e1068f8c6da14b2c9c384e4d00d8900308ad2e406fe294330ce0edd81d
 
57
  ```
58
 
59
  ## Privacy
space/DEPLOY.md CHANGED
@@ -84,34 +84,58 @@ never evidence content.
84
 
85
  ---
86
 
87
- ## 3b. Package signing key (`UOFA_DEMO_SIGNING_KEY`)
88
-
89
- The "Download UofA package" control signs each package with a **dedicated demo
90
- issuer key**. Deliberately *not* `keys/research.key`: a demo artifact must never
91
- be cryptographically indistinguishable from a research package, so the demo key
92
- is its own identity. Signing now takes TWO keys, one per attestor kind:
93
- `UOFA_ISSUER_SIGNING_KEY` seals the measurement view (anchor
94
- `keys/uofa-issuer.pub`) and `UOFA_DEMO_SIGNING_KEY` signs the decision
95
- (anchor `keys/demo-reviewer.pub`). One signature may never span both.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  **Setup (one-time).** Generate the pair *outside the repo* and install the
98
  private half as a Space secret:
99
 
100
  ```bash
101
- uofa keygen ~/secure/uofa-demo.key # writes uofa-demo.key + uofa-demo.pub
102
- cp ~/secure/uofa-demo.pub keys/demo-reviewer.pub # public half is committed
103
  ```
104
 
105
  Space → **Settings → Variables and secrets → New secret**:
106
 
107
  | Name | Value |
108
  |---|---|
109
- | `UOFA_DEMO_SIGNING_KEY` | the full PEM contents of `uofa-demo.key` |
 
 
 
 
110
 
111
  The PEM is read into memory at request time and never written to the container
112
  filesystem — the process serves user downloads out of a temp directory, and a
113
  private key on that filesystem is one path bug away from being one of them. For
114
- local development, `UOFA_DEMO_SIGNING_KEY_FILE=/path/to/demo.key` works instead.
 
115
 
116
  **The key can never travel as a file.** `space/deploy_to_hf.py` filters `.key`,
117
  `.pem`, and `.env` out of the upload set *and* hard-refuses the deploy if one
 
84
 
85
  ---
86
 
87
+ ## 3b. Package signing key (`UOFA_ISSUER_SIGNING_KEY`)
88
+
89
+ The "Download UofA package" control seals each package with a **dedicated issuer
90
+ key**. Deliberately *not* `keys/research.key`: a demo artifact must never be
91
+ cryptographically indistinguishable from a research package, so the issuer key
92
+ is its own identity.
93
+
94
+ **One key, one scope: an issuer seal.** `UOFA_ISSUER_SIGNING_KEY` attests
95
+ integrity and origin — this package came from this service and has not been
96
+ altered since. Anchor: `keys/uofa-issuer.pub`. The signer renders as
97
+ `UofA issuer (keys/uofa-issuer.pub)`.
98
+
99
+ **The Space does not sign a decision, and must not.** A service key cannot stand
100
+ in for a human reviewer, so there is no second signature and no decision
101
+ attestor. Per `docs/UofA_Spec_Unified_Signing_Surface_v1_0.md`, which supersedes
102
+ the earlier two-attestor design, the web path applies an issuer seal only. A
103
+ valid signature here means *this artifact is intact and came from us*. It does
104
+ not mean anyone accepted the model.
105
+
106
+ > **Corrected 2026-09-08.** This section previously described a second secret,
107
+ > `UOFA_DEMO_SIGNING_KEY`, said to sign a decision against
108
+ > `keys/demo-reviewer.pub`. **No code has ever read that variable.**
109
+ > `space/pipeline.py` defines `UOFA_ISSUER_SIGNING_KEY` and
110
+ > `UOFA_ISSUER_SIGNING_KEY_FILE` and nothing else, and the Space's own tests
111
+ > already assert the emitted package carries no decision block. The document
112
+ > described a capability the implementation deliberately does not have, on the
113
+ > most sensitive question the tool answers. Pinned by
114
+ > `tests/space/test_deploy_doc_env_names.py` so the names cannot drift again.
115
 
116
  **Setup (one-time).** Generate the pair *outside the repo* and install the
117
  private half as a Space secret:
118
 
119
  ```bash
120
+ uofa keygen ~/secure/uofa-issuer.key # writes uofa-issuer.key + uofa-issuer.pub
121
+ cp ~/secure/uofa-issuer.pub keys/uofa-issuer.pub # public half is committed
122
  ```
123
 
124
  Space → **Settings → Variables and secrets → New secret**:
125
 
126
  | Name | Value |
127
  |---|---|
128
+ | `UOFA_ISSUER_SIGNING_KEY` | the full PEM contents of `uofa-issuer.key` |
129
+
130
+ Without that secret the Space still runs, but the result reports
131
+ `Authenticity: Unverified (demo)` and **no download control appears** — an
132
+ unsigned package is not offered rather than offered unsigned.
133
 
134
  The PEM is read into memory at request time and never written to the container
135
  filesystem — the process serves user downloads out of a temp directory, and a
136
  private key on that filesystem is one path bug away from being one of them. For
137
+ local development, `UOFA_ISSUER_SIGNING_KEY_FILE=/path/to/issuer.key` works
138
+ instead.
139
 
140
  **The key can never travel as a file.** `space/deploy_to_hf.py` filters `.key`,
141
  `.pem`, and `.env` out of the upload set *and* hard-refuses the deploy if one
space/README.md CHANGED
@@ -30,17 +30,23 @@ manifest, public key, and instructions beside it are convenience copies.
30
 
31
  ```
32
  unzip uofa-pack-*.zip
33
- uofa verify uofa.jsonld \
34
- --pubkey keys/uofa-issuer.pub \
35
- --decision-pubkey keys/demo-reviewer.pub
36
  ```
37
 
38
  **What a valid signature means here.** Only that the file is unmodified since
39
  this demo produced it. It is not a review and not an acceptance decision. The
40
  signing key is a *demonstration issuer key* held by the demo, not a research or
41
- production key, and not anyone's decision key — so a demo package can never be
42
- mistaken for a formally issued one (it does not verify against the default trust
43
- anchor; `--pubkey` is required, deliberately).
 
 
 
 
 
 
 
 
44
 
45
  The public key travels inside the zip so verification works offline. A trust
46
  anchor shipped inside the artifact it validates only proves self-consistency, so
@@ -48,7 +54,6 @@ compare it against this independent copy:
48
 
49
  ```
50
  keys/uofa-issuer.pub sha256:ead2e1e1068f8c6da14b2c9c384e4d00d8900308ad2e406fe294330ce0edd81d
51
- keys/demo-reviewer.pub sha256:3605a146f4880d9f7a29db6ef5629655091d2ecd0c2b9919cbe49d90d65d83c8
52
  ```
53
 
54
  ## Privacy
 
30
 
31
  ```
32
  unzip uofa-pack-*.zip
33
+ uofa verify uofa.jsonld --pubkey keys/uofa-issuer.pub
 
 
34
  ```
35
 
36
  **What a valid signature means here.** Only that the file is unmodified since
37
  this demo produced it. It is not a review and not an acceptance decision. The
38
  signing key is a *demonstration issuer key* held by the demo, not a research or
39
+ production key — so a demo package can never be mistaken for a formally issued
40
+ one (it does not verify against the default trust anchor; `--pubkey` is
41
+ required, deliberately).
42
+
43
+ **The Space applies an issuer seal only, and signs no decision.** A service key
44
+ cannot stand in for a human reviewer, so the pack carries no decision block and
45
+ `--decision-pubkey` has nothing here to check. Until 2026-09-08 this file
46
+ documented that flag against `keys/demo-reviewer.pub`, which is not one of the
47
+ pack's five members, so the command failed on a missing file and implied an
48
+ attestation the Space deliberately does not make. See
49
+ `docs/UofA_Spec_Unified_Signing_Surface_v1_0.md`.
50
 
51
  The public key travels inside the zip so verification works offline. A trust
52
  anchor shipped inside the artifact it validates only proves self-consistency, so
 
54
 
55
  ```
56
  keys/uofa-issuer.pub sha256:ead2e1e1068f8c6da14b2c9c384e4d00d8900308ad2e406fe294330ce0edd81d
 
57
  ```
58
 
59
  ## Privacy
space/app.py CHANGED
@@ -20,7 +20,7 @@ import gradio as gr
20
  from space import (curated, leadcapture, llm_env, pipeline, ratelimit, reviewer,
21
  solver_panel, wizard)
22
  from space.gloss import gloss_for, load_gloss
23
- from uofa_cli import paths
24
 
25
  PACK_LABELS = {"vv40": "ASME V&V 40", "nasa-7009b": "NASA-STD-7009B"}
26
  PACK_CHOICES = [(label, pid) for pid, label in PACK_LABELS.items()]
@@ -370,14 +370,25 @@ def _render_results(p):
370
  gaps.append("**Weakeners fired:**")
371
  for w in p["weakeners"]:
372
  fac = f", {', '.join(w['factors'])}" if w.get("factors") else ""
373
- gaps.append(f"- `{w['patternId']}` [{w.get('severity')}] ×{w.get('hits')}{fac}")
 
 
 
374
  else:
375
  gaps.append("**Weakeners:** none fired. 🎉")
376
  if c["missing"]:
377
  gaps.append("\n**Not assessed:** " + ", ".join(c["missing"]))
378
  gaps_md = "\n".join(gaps)
379
 
380
- tail = [f"**Completeness:** {c['n_assessed']} of {c['n_expected']} factors assessed."]
 
 
 
 
 
 
 
 
381
  if c["excluded"]:
382
  tail.append("**Excluded (scoped-out / N/A):** " + ", ".join(c["excluded"]))
383
  struct = p["structural"]
@@ -651,8 +662,14 @@ def build() -> gr.Blocks:
651
  def render_factors(result):
652
  rows = pipeline.factor_rows(result) if result else []
653
  for row in rows:
 
 
 
 
 
 
654
  rad = gr.Radio(choices=STATUS_CHOICES, value=row["status"],
655
- label=_factor_label(row))
656
  # The factor name is already in the radio label above, so the
657
  # accordion just says "what we read" (no redundant name echo).
658
  # Lead with the shared gloss so a non-expert understands the factor.
 
20
  from space import (curated, leadcapture, llm_env, pipeline, ratelimit, reviewer,
21
  solver_panel, wizard)
22
  from space.gloss import gloss_for, load_gloss
23
+ from uofa_cli import paths, report_state
24
 
25
  PACK_LABELS = {"vv40": "ASME V&V 40", "nasa-7009b": "NASA-STD-7009B"}
26
  PACK_CHOICES = [(label, pid) for pid, label in PACK_LABELS.items()]
 
370
  gaps.append("**Weakeners fired:**")
371
  for w in p["weakeners"]:
372
  fac = f", {', '.join(w['factors'])}" if w.get("factors") else ""
373
+ # sev_label, not the raw key: a reader must never see "Medium" here
374
+ # and "Moderate" in the Reviewer view for the same weakener.
375
+ sev = report_state.sev_label(w.get("severity"))
376
+ gaps.append(f"- `{w['patternId']}` [{sev}] ×{w.get('hits')}{fac}")
377
  else:
378
  gaps.append("**Weakeners:** none fired. 🎉")
379
  if c["missing"]:
380
  gaps.append("\n**Not assessed:** " + ", ".join(c["missing"]))
381
  gaps_md = "\n".join(gaps)
382
 
383
+ # Say WHICH count this is. The Reviewer view reports a different, also
384
+ # correct number (it demotes any factor an open High/Moderate concern
385
+ # disputes), and side by side the two read as a contradiction unless each
386
+ # names its own basis. Observed live 2026-09-08: Author "13 of 13" against
387
+ # Reviewer "11 of 13" on the same run, with nothing explaining the gap.
388
+ tail = [f"**Completeness (raw extracted statuses):** {c['n_assessed']} of "
389
+ f"{c['n_expected']} factors assessed. The Reviewer view reports a "
390
+ "lower count because it does not treat a factor as evidenced while "
391
+ "an open High or Moderate concern disputes it."]
392
  if c["excluded"]:
393
  tail.append("**Excluded (scoped-out / N/A):** " + ", ".join(c["excluded"]))
394
  struct = p["structural"]
 
662
  def render_factors(result):
663
  rows = pipeline.factor_rows(result) if result else []
664
  for row in rows:
665
+ # interactive=True is explicit, not decorative. Gradio infers
666
+ # interactivity from whether a component is an input to some
667
+ # event, and that inference does not reach components built
668
+ # inside @gr.render: every factor radio shipped DISABLED, so
669
+ # the confirm step -- the only surface a human may correct --
670
+ # could not be corrected. Pinned by test_confirm_step_editable.
671
  rad = gr.Radio(choices=STATUS_CHOICES, value=row["status"],
672
+ label=_factor_label(row), interactive=True)
673
  # The factor name is already in the radio label above, so the
674
  # accordion just says "what we read" (no redundant name echo).
675
  # Lead with the shared gloss so a non-expert understands the factor.
space/pipeline.py CHANGED
@@ -398,6 +398,14 @@ def _authenticity_block(*, signed: bool = False, package_hash: str | None = None
398
  "package_hash": package_hash,
399
  "signer": signer or SIGNER_LABEL,
400
  "statement": _SIGNED_STATEMENT,
 
 
 
 
 
 
 
 
401
  }
402
 
403
 
 
398
  "package_hash": package_hash,
399
  "signer": signer or SIGNER_LABEL,
400
  "statement": _SIGNED_STATEMENT,
401
+ # Built from the same constants that name the zip's members, so the
402
+ # command a reader is shown cannot drift from the files they receive.
403
+ # The reviewer view previously hardcoded `keys/demo.pub`, which is not
404
+ # a member of the pack, so copying the on-screen command failed on a
405
+ # missing file. Carried in the payload rather than assembled in the
406
+ # renderer because that module renders state and interprets nothing.
407
+ "verify_command": (f"uofa verify {PACK_MEMBER_JSONLD} "
408
+ f"--pubkey {PACK_MEMBER_PUBKEY}"),
409
  }
410
 
411
 
space/requirements.txt CHANGED
@@ -1,5 +1,17 @@
1
  # App-only deps installed on top of the uofa[extract] wheel (see space/Dockerfile).
2
  # The uofa package itself and its extract extras (litellm, pdfplumber, etc.) come
3
  # from the wheel, not from here.
4
- gradio>=6.0
 
 
 
 
 
 
 
 
 
 
 
 
5
  huggingface_hub>=0.23 # lead capture -> private HF Dataset (S4)
 
1
  # App-only deps installed on top of the uofa[extract] wheel (see space/Dockerfile).
2
  # The uofa package itself and its extract extras (litellm, pdfplumber, etc.) come
3
  # from the wheel, not from here.
4
+ # Upper bound is deliberate. This was `>=6.0` with no cap, so the Space
5
+ # installed whatever 6.x existed at build time and the UI could change with no
6
+ # commit in this repo. It did: Gradio infers a component's interactivity from
7
+ # whether it feeds an event, that inference does not reach components built
8
+ # inside `@gr.render`, and every credibility-factor radio on the deployed tool
9
+ # shipped DISABLED. The confirm step, the one surface a human may correct, was
10
+ # read-only in production. See the fix in space/app.py (interactive=True) and
11
+ # the browser test that pins it, tests/space/test_ui_e2e.py.
12
+ #
13
+ # 6.24.0 is the version the browser tests were run against on 2026-09-08. Raise
14
+ # this cap only alongside a run of those tests, since they are the only thing
15
+ # that exercises rendered interactivity.
16
+ gradio>=6.0,<6.25
17
  huggingface_hub>=0.23 # lead capture -> private HF Dataset (S4)
space/reviewer.py CHANGED
@@ -109,7 +109,13 @@ def _reconcile_clause(s: ReviewerState) -> str:
109
  bits.append(f"{m} high-severity {_plural(m, 'concern', 'concerns')} "
110
  f"{_plural(m, 'remains', 'remain')} open")
111
  tail = "; ".join(bits) + " before this is review-ready."
112
- return f"{s.completeness_pct}% of all factors evidenced; {tail}"
 
 
 
 
 
 
113
 
114
 
115
  def _section_glance(s: ReviewerState) -> str:
@@ -127,7 +133,7 @@ def _section_glance(s: ReviewerState) -> str:
127
  <h2>At a glance</h2>
128
  <dl class="ri-glance">
129
  <div><dt>Completeness</dt><dd>{s.completeness_pct}%</dd></div>
130
- <div><dt>Factors evidenced</dt><dd>{s.n_evidenced} of {s.n_expected}</dd></div>
131
  <div><dt>Concerns (weakeners)</dt><dd>{_e(sev_txt)}</dd></div>
132
  <div><dt>Authenticity verified</dt><dd>{auth_txt}</dd></div>
133
  <div><dt>Gate checks passed</dt><dd>{s.gates['passed']} of {s.gates['total']}</dd></div>
@@ -220,7 +226,11 @@ def _section_authenticity(s: ReviewerState) -> str:
220
  detail = (f"<ul><li><b>Signed by:</b> {_e(auth.get('signer'))}</li>"
221
  f"<li><b>Content hash:</b> <code>{_e(auth.get('package_hash'))}</code></li></ul>\n"
222
  f" <p>{_e(auth.get('statement'))}</p>")
223
- cmd = "uofa verify uofa.jsonld --pubkey keys/demo.pub"
 
 
 
 
224
  howto = ("Download the package below and re-check it yourself. The public "
225
  "key and these instructions travel inside the zip:")
226
  else:
 
109
  bits.append(f"{m} high-severity {_plural(m, 'concern', 'concerns')} "
110
  f"{_plural(m, 'remains', 'remain')} open")
111
  tail = "; ".join(bits) + " before this is review-ready."
112
+ # "after open concerns" is load-bearing. This percentage counts a factor as
113
+ # evidenced only while no open High or Moderate concern disputes it, so it
114
+ # can sit below the Author view's raw count of assessed statuses. Both are
115
+ # correct; unlabelled they read as a contradiction. Observed live
116
+ # 2026-09-08: this view said 11 of 13 while the Author view said 13 of 13.
117
+ return (f"{s.completeness_pct}% of all factors evidenced after open "
118
+ f"concerns; {tail}")
119
 
120
 
121
  def _section_glance(s: ReviewerState) -> str:
 
133
  <h2>At a glance</h2>
134
  <dl class="ri-glance">
135
  <div><dt>Completeness</dt><dd>{s.completeness_pct}%</dd></div>
136
+ <div><dt>Evidenced, after concerns</dt><dd>{s.n_evidenced} of {s.n_expected}</dd></div>
137
  <div><dt>Concerns (weakeners)</dt><dd>{_e(sev_txt)}</dd></div>
138
  <div><dt>Authenticity verified</dt><dd>{auth_txt}</dd></div>
139
  <div><dt>Gate checks passed</dt><dd>{s.gates['passed']} of {s.gates['total']}</dd></div>
 
226
  detail = (f"<ul><li><b>Signed by:</b> {_e(auth.get('signer'))}</li>"
227
  f"<li><b>Content hash:</b> <code>{_e(auth.get('package_hash'))}</code></li></ul>\n"
228
  f" <p>{_e(auth.get('statement'))}</p>")
229
+ # From the payload, not typed here. The packed pubkey member is named by
230
+ # pipeline.PACK_MEMBER_PUBKEY, and a second hardcoded copy of that name
231
+ # in this file drifted: it read `keys/demo.pub`, which the zip does not
232
+ # contain, so a reader who copied this command hit a missing file.
233
+ cmd = auth.get("verify_command") or "uofa verify --help"
234
  howto = ("Download the package below and re-check it yourself. The public "
235
  "key and these instructions travel inside the zip:")
236
  else: