Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ntnt"
version = "0.5.2"
version = "0.5.3"
edition = "2021"
authors = ["NTNT Language Team"]
description = "NTNT (Intent) - A programming language designed for AI-driven development"
Expand Down
28 changes: 14 additions & 14 deletions design-docs/dd-047-std-netmon.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ Ok(map {

`Counter64` values use decimal strings so ntnt's signed 64-bit `Int` cannot truncate them, including legal values above `i64::MAX`. Binary octet strings and opaque values use lowercase hex with explicit encoding metadata. Protocol exceptions such as `no_such_object` retain their type and use `None` as the value. Agent `error_status`/`error_index`, transport timeouts, malformed BER, and authentication mismatches return `Err(String)` rather than partial telemetry.

#### `snmp_walk(target, auth, oid, opts?) -> Result<Map, String>` *(next slice)*
#### `snmp_walk(target, auth, oid, opts?) -> Result<Map, String>` *(implemented in v0.5.3)*

Walk a numeric subtree with strict row, request, byte, and result caps. This follows GET rather than sharing its first compatibility commit. Low-level transport stays numeric and never resolves a mutable MIB symbol implicitly.

Expand Down Expand Up @@ -517,7 +517,7 @@ Ok(map {
})
```

`requests` counts logical GETNEXT cursors, including look-ahead. `attempts` counts every transmitted datagram, including retries. GETNEXT requests use one cursor and require exactly one response varbind. Every accepted OID must be lexicographically greater than the prior cursor. Equal, descending, or repeated OIDs are protocol errors.
`requests` counts logical GETNEXT cursors, including look-ahead. `attempts` counts every transmitted datagram, including retries. GETNEXT requests use one cursor and require exactly one response varbind. Every accepted ordinary-value OID must be lexicographically greater than the prior cursor. Equal, descending, or repeated OIDs are protocol errors. Terminal exception OIDs must equal the requested cursor. A same-agent datagram carrying an older request ID is ignored on the same connected socket while the current attempt deadline and cumulative receive-byte budget continue to apply.

The one global deadline begins before first request construction and covers every cursor, retry, decode, normalization step, mandatory look-ahead, and final result build. Terminal behavior is normative:

Expand Down Expand Up @@ -1019,7 +1019,7 @@ Some of these may become useful later, but they carry OS permissions, abuse risk

- [x] **Slice 0 — standard-library packaging and security contract**
- [x] **Slice 1A — bounded SNMPv2c GET**
- [ ] **Slice 1B — bounded numeric SNMP WALK**
- [x] **Slice 1B — bounded numeric SNMP WALK**
- [ ] **Slice 1C — canonical MIB catalog compiler, runtime registry, profiles, and plans**
- [ ] **PR 2 — device recognition and inventory execution**
- [ ] **PR 3 — interface inventory and counters**
Expand Down Expand Up @@ -1074,20 +1074,20 @@ Acceptance:

Scope:

- [ ] `snmp_walk(target, auth, oid, opts?) -> Result<Map, String>`.
- [ ] GETNEXT with one cursor and exactly one correlated response varbind.
- [ ] Strict subtree, monotonic-order, result, request, datagram, cumulative-byte, and normalized-output enforcement.
- [ ] Explicit `complete` and `stop_reason` output, including bounded look-ahead at `max_results`.
- [ ] Loop, equal/descending OID, malformed-order, `endOfMibView`, and out-of-subtree handling.
- [ ] One whole-operation deadline covering retries, decode, normalization, and result construction.
- [ ] Reuse Slice 1A auth, target policy, packet caps, and normalization contracts.
- [ ] GETBULK only after equivalent fixture coverage.
- [x] `snmp_walk(target, auth, oid, opts?) -> Result<Map, String>`.
- [x] GETNEXT with one cursor and exactly one correlated response varbind.
- [x] Strict subtree, monotonic-order, result, request, datagram, cumulative-byte, and normalized-output enforcement.
- [x] Explicit `complete` and `stop_reason` output, including bounded look-ahead at `max_results`.
- [x] Loop, equal/descending OID, malformed-order, `endOfMibView`, and out-of-subtree handling.
- [x] One whole-operation deadline covering retries, decode, normalization, and result construction.
- [x] Reuse Slice 1A auth, target policy, packet caps, and normalization contracts.
- [x] GETBULK remains deferred until equivalent fixture coverage exists.

Acceptance:

- [ ] No walk can silently return a truncated table as complete.
- [ ] Mid-walk transport/protocol failure returns `Err` rather than apparently complete telemetry.
- [ ] Independent UDP fixtures cover malicious loops, subtree escape, retries, caps, and valid termination.
- [x] No walk can silently return a truncated table as complete.
- [x] Mid-walk transport/protocol failure returns `Err` rather than apparently complete telemetry.
- [x] Independent UDP fixtures cover malicious loops, subtree escape, retries, caps, forged sources, delayed request IDs, and valid termination.

### Slice 1C — Canonical MIB Catalog Compiler and Runtime Registry

Expand Down
2 changes: 1 addition & 1 deletion docs/IAL_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Auto-generated from [ial.toml](ial.toml)** - Do not edit directly.
>
> Last updated: v0.5.2
> Last updated: v0.5.3

IAL is a term rewriting engine that translates natural language assertions into executable tests

Expand Down
1 change: 1 addition & 0 deletions docs/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Source files:
| Document | Description |
|----------|-------------|
| [Roadmap](../ROADMAP.md) | Implementation phases and progress |
| [v0.5.3 Release Notes](release-notes/v0.5.3.md) | Bounded numeric SNMPv2c GETNEXT WALK with strict completion and resource ceilings |
| [v0.5.2 Release Notes](release-notes/v0.5.2.md) | Gated, bounded SNMPv2c GET through the new explicitly imported `std/netmon` module |
| [v0.5.1 Release Notes](release-notes/v0.5.1.md) | Passwordless magic-link flow, opaque provider-neutral secrets, compatibility guarantees, and auth hardening |
| [v0.5.0 Release Notes](release-notes/v0.5.0.md) | Verification, validation, email, and multi-worker improvements |
Expand Down
2 changes: 1 addition & 1 deletion docs/RUNTIME_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Auto-generated from [runtime.toml](runtime.toml)** - Do not edit directly.
>
> Last updated: v0.5.2
> Last updated: v0.5.3

Runtime configuration, environment variables, and CLI commands for NTNT

Expand Down
43 changes: 41 additions & 2 deletions docs/STDLIB_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Auto-generated from source code doc comments** - Do not edit directly.
>
> Last updated: v0.5.2
> Last updated: v0.5.3

## Table of Contents

Expand Down Expand Up @@ -10301,14 +10301,15 @@ traceroute("example.com", map { "method": "tcp", "port": 443 }) // TCP-SYN trac
Bounded network-monitoring protocols with opaque credentials and normalized results

```ntnt
import { snmp_get } from "std/netmon"
import { snmp_get, snmp_walk } from "std/netmon"
```

### Functions

| Function | Description |
|----------|-------------|
| [`snmp_get`](#snmpget) | Reads one bounded set of numeric OIDs from an SNMP agent. Slice 1 supports SNMPv2c only. The strict auth map must contain `version: "2c"` and a `community` Secret, normally returned by `require_secret()`; plaintext community strings are rejected. Unknown auth and option keys are rejected. SNMPv2c does not encrypt or authenticate its community or payload; use this slice only on trusted management networks or protected tunnels. |
| [`snmp_walk`](#snmpwalk) | Walks one numeric OID subtree with bounded SNMPv2c GETNEXT requests. The community must be an opaque Secret. SNMPv2c is plaintext; use a trusted management network or protected tunnel. Every call requires `NTNT_NETMON_ENABLE=1`; private targets also require the shared process opt-in and `allow_private: true`. |

#### `snmp_get`

Expand Down Expand Up @@ -10347,6 +10348,44 @@ snmp_get("10.0.50.1", map { "version": "2c", "community": require_secret("SNMP_C

---

#### `snmp_walk`

```ntnt
snmp_walk(target: String, auth: Map, oid: String, opts?: Map) -> Result<Map, String>
```

Walks one numeric OID subtree with bounded SNMPv2c GETNEXT requests. The community must be an opaque Secret. SNMPv2c is plaintext; use a trusted management network or protected tunnel. Every call requires `NTNT_NETMON_ENABLE=1`; private targets also require the shared process opt-in and `allow_private: true`.

The closed options map accepts the common SNMP options plus `max_results` (default 256, hard maximum 2048) and `on_limit` (`"error"`, the default, or `"partial"`). One global deadline covers all cursors, retries, response validation, the mandatory limit look-ahead, normalization, and result construction. Requests and responses are capped at 8 KiB, cumulative received bytes at 8 MiB, and conservative normalized output at 4 MiB.

A successful map has exactly: `target: String`, `address: String`, `port: Int`, `version: String` (`"2c"`), `root_oid: String`, `duration_ms: Int`, `requests: Int`, `attempts: Int`, `complete: Bool`, `stop_reason: String`, and `values: Array<Map>`. Each value uses the same normalized `oid`, `type`, `value`, and optional `encoding` fields as `snmp_get`. `stop_reason` is one of `out_of_subtree`, `end_of_mib_view`, `no_such_object`, `no_such_instance`, or `max_results`. Only `max_results` has `complete: false`, and it is returned only when `on_limit: "partial"`; all transport, protocol, ordering, deadline, and byte-budget failures are `Err(String)` without prior-row telemetry.

**Parameters:**

- `target` — Literal IPv4 or IPv6 address without a port
- `auth` — Strict map with version (`"2c"`) and community (Secret)
- `oid` — Numeric root OID in dotted notation
- `opts` — Optional strict map with port, timeout_ms, retries, allow_private, max_results, and on_limit

**Returns:** Result whose Ok map has exactly target, address, port, version, root_oid, duration_ms, requests, attempts, complete, stop_reason, and values

**Examples:**

```ntnt
snmp_walk("10.0.50.1", map { "version": "2c", "community": require_secret("SNMP_COMMUNITY") }, "1.3.6.1.2.1.2.2", map { "allow_private": true, "max_results": 128 }) // Walk a bounded interface table
```

**Errors:**

- **RuntimeError**: std/netmon is disabled — *Fix: Set NTNT_NETMON_ENABLE=1 for the process*
- **RuntimeError**: snmp_walk() auth.community must be Secret — *Fix: Load it with std/secrets.require_secret()*

**See also:** `snmp_get`, `require_secret`, `net_capabilities`

*Since v0.5.3*

---

## std/path

File path manipulation and resolution
Expand Down
2 changes: 1 addition & 1 deletion docs/SYNTAX_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Auto-generated from [syntax.toml](syntax.toml)** - Do not edit directly.
>
> Last updated: v0.5.2
> Last updated: v0.5.3

## Table of Contents

Expand Down
38 changes: 38 additions & 0 deletions docs/release-notes/v0.5.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# NTNT v0.5.3 Release Notes

v0.5.3 adds bounded numeric SNMPv2c GETNEXT walks to the explicitly imported `std/netmon` module. It extends the v0.5.2 transport and credential boundary without adding MIB parsing, DNS targets, GETBULK, SNMP SET, or background network behavior.

## Added

- `snmp_walk(target, auth, oid, opts?) -> Result<Map, String>` for one numeric OID subtree.
- Strict GETNEXT cursor progression with exactly one correlated response varbind per logical request.
- Explicit successful completion reasons:
- `out_of_subtree`
- `end_of_mib_view`
- `no_such_object`
- `no_such_instance`
- Mandatory bounded look-ahead at `max_results`, distinguishing exact completion from true truncation.
- Optional `on_limit: "partial"` for result-limit truncation only. Transport, protocol, ordering, deadline, and byte-budget failures still return `Err(String)` without prior-row telemetry.
- Stable result metadata: target/address/port/version/root OID, duration, logical requests, datagram attempts, completion state, stop reason, and ordered normalized values.

## Bounds and security

- SNMPv2c communities remain opaque `Secret` values loaded through `std/secrets`.
- Live calls still require `NTNT_NETMON_ENABLE=1`.
- Private targets still require both `NTNT_NET_ALLOW_PRIVATE=1` and call-local `allow_private: true`.
- Targets remain literal IPv4/IPv6 addresses and use connected UDP sockets.
- One operation-wide timeout covers every cursor, retry, decode, normalization step, look-ahead, and result construction.
- Requests and individual responses are capped at 8 KiB.
- Cumulative received datagrams are capped at 8 MiB and conservative normalized output at 4 MiB.
- `max_results` is 1–2,048, retries are 0–3, and option preflight enforces `(max_results + 1) * (retries + 1) <= 4,096`.
- Equal, repeated, descending, mismatched terminal-exception, malformed, uncorrelated, and oversized responses fail closed.
- Same-agent packets carrying stale request IDs are ignored only within the current connected attempt, while deadline and receive-byte budgets continue to apply.

SNMPv2c remains plaintext on the wire. Use it only on trusted management networks, VPNs, or protected tunnels.

## Compatibility

- Existing `snmp_get` behavior and result normalization remain compatible.
- `std/netmon` remains explicitly imported and is not added to the prelude.
- No network traffic occurs on import.
- Numeric WALK does not load or resolve MIB symbols. The separately designed immutable MIB catalog pipeline remains a later delivery slice.
28 changes: 26 additions & 2 deletions examples/netmon_snmp.tnt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// std/netmon Slice 1: bounded SNMPv2c GET without performing traffic on import.
import { snmp_get } from "std/netmon"
// std/netmon Slice 1B: bounded SNMPv2c GET and GETNEXT WALK without traffic on import.
import { snmp_get, snmp_walk } from "std/netmon"

fn read_system_info(
target: String,
Expand Down Expand Up @@ -28,6 +28,30 @@ requires len(target) > 0
)
}

fn walk_interface_table(
target: String,
community: Secret,
allow_private: Bool
) -> Result<Map<String, Any>, String>
requires len(target) > 0
{
return snmp_walk(
target,
map {
"version": "2c",
"community": community
},
"1.3.6.1.2.1.2.2.1", // IF-MIB::ifEntry
map {
"timeout_ms": 5000,
"retries": 0,
"allow_private": allow_private,
"max_results": 512,
"on_limit": "partial"
}
)
}

// At the application boundary, enable protocol calls with NTNT_NETMON_ENABLE=1.
// Private targets also require NTNT_NET_ALLOW_PRIVATE=1 and allow_private: true.
// Slice 1 accepts literal IPv4 and IPv6 targets; hostname resolution is deferred.
Expand Down
Loading