Subject: P work in new charter

From: Alex Rousskov <[email-masked]>
Date: Sun, 27 Jun 2004 23:55:43 -0600 (MDT)

Hi,

	Here are a few thoughts regarding P-items in the new charter.

	I believe our biggest challenge with P remains its scope and
how P scope relates to WG work items. We had a few very important
discussions regarding P scope at the end of 2003, but they did not
result in a digestible conclusion (IMO). I am not sure we can scope P
and our P-work right now, without at least another round of
discussions.

	The two big P-questions that we may need to answer in the new
charter are:

	#1. How deep does P reach? (P scope)

	    a) just an interface (language) for rule writer
	       to use when configuring/selecting existing OPES
	       processor actions

	    b) also an interface (language) for OPES processor
	       vendor, administrator, or 3rd party to write
	       generic (parameterizable) OPES processor actions

	    c) also an interface (language) for OPES processor
	       vendor or 3rd party to write protocol-specific
	       modules to be used when defining OPES processor
	       actions

	#2. How deep does our charter reach? (WG work scope)

	    a) just the core language (like C without the standard
	       library or standard template library)

	    b) also HTTP and SMTP module interfaces for selecting
	       or writing HTTP- and SMTP-related actions (like
	       the standard C libraries interfaces)

	    c) also an interface between P interpreters and
	       module/services suppliers? (like Unix or MS
	       library loading conventions)

Note that the answer to the first question determines how much work we
would have to do when documenting language core. If we limit P scope a
lot, we will have to do less work. On the other hand, if we pick wrong
scope, it may be insufficient or too complex to interest vendors in
supporting it.

Is there a possibility to define a very simple P core while allowing,
in principle, feature-rich extensions to write P modules and such? Not
sure, but that could be the ideal approach. Kind of like Java applets
versus Java applications, but even more extreme. On the other hand, if
the core is really simple/basic, does it make sense to extend it at
all? Instead, it may be better to start from scratch for (a), (b), and
(c) in question #1 above!

FWIW, IRML, if further developed, would probably fit (1a) and (2b).

Thanks,

Alex.

---

From: Markus Hofmann <[email-masked]>
Date: Mon, 28 Jun 2004 21:32:32 -0400

> 	       modules to be used when defining OPES processor

Can you give practical examples of (b) and (c)?

What would be the benefits of having one language that can do all 
these, as opposed to having one language for (a) and another for (b) 
and/or (c)? To me, these things seem sufficiently different...

I'm pretty reluctant to go beyond (a), which was the original intent 
of the rules language.

> 	       library loading conventions)

I would consider (a) and (b) in scope.

> scope, it may be insufficient or too complex to interest vendors in

The problem to be solved with the rules language is "how do I tell the 
OPES processor which services have to be executed on what messages" - 
a solution to that problem alone would bring us a big step forward and 
would be considered valuable, I assume.

> (c) in question #1 above!

I would suggest to phrase a new charter to include (1a) and (2a,b). 
This does not preclude us from looking at the approach you mention 
above, but it allows us to focus on the essential part in case it 
turns out to be too complex.

> FWIW, IRML, if further developed, would probably fit (1a) and (2b).

While I agree (and while I personally like IRML :), we made a decision 
earlier to go with the "P" approach, and I would hesitate to revise 
that decision without a really strong reason.

-Markus

---

From: Alex Rousskov <[email-masked]>
Date: Tue, 29 Jun 2004 09:21:43 -0600 (MDT)

> Can you give practical examples of (b) and (c)?

Examples of (b) would be common actions like "strip ads for user X but
only when she is surfing with her mobile phone browser Y", or "do spam
filtering for user X unless the message is from Y"; where X and Y are
parameters. I would imagine vendors and user groups might want to
create collections of such common parameterized actions. P would need
to have something like user-defined functions to support this.

IMO, option (c) is too low-level for P, so I will not give an example.

> and/or (c)? To me, these things seem sufficiently different...

The advantage is that OPES processor will only need to support one,
albeit more complex, language instead of two or three. It is not clear
to me whether we can capture both (a) and (b) without significantly
increasing P implementation complexity.

> I'm pretty reluctant to go beyond (a), which was the original intent

The short definitions I provided are rather fuzzy. Please see if more
specific (b) examples above would make you a little more comfortable.

> The problem to be solved with the rules language is "how do I tell
> the OPES processor which services have to be executed on what
> messages" - a solution to that problem alone would bring us a big
> step forward and would be considered valuable, I assume.

I agree. This still leaves some room. For example, should P make it
easy for me to send you a piece of P code that you can use in your
proxy rules? Based on the message/action selection logic but without
the knowledge of site-specific parameters like user names or times of
restricted access.

Think about shell languages in Unix. The problem to be solved there is
which programs to execute with which parameters. Still, shell
languages differ a lot. I do not know of any shell language without
user-defined functions, but I suspect they do exist (MS command
language did not support them, right?). There are also shell languages
with sophisticated job controls. And then there is Perl that
originally was just a bit more than a shell language!

> While I agree (and while I personally like IRML :), we made a
> decision earlier to go with the "P" approach, and I would hesitate
> to revise that decision without a really strong reason.

I did not mean to suggest to switch back to fixing IRML. I just wanted
to support my fuzzy classification of choices with a well-understood
example.

Alex.

---

From: "The Purple Streak, Hilarie Orman" <[email-masked]>
Date: Tue, 29 Jun 2004 11:06:58 -0600

I can't find the P draft nor the message giving the URL for it, though

The most important thing about the OPES language is that it support a
superset of regular expression matching functions that can be composed
efficiently by a special compiler.  The second most important thing is
that it support composable, reusable actions.

I can't tell where these fall in the Rousskov a, b, c scope definitions,
and I would appreciate further amplication of them, especially c, for
writing modules for protocol-specific processor actions.  I cannot
quite imagine how a language would support protocol-nonspecific actions
but could not support protocol-specific actions.

Hilarie

---

From: Alex Rousskov <[email-masked]>
Date: Tue, 29 Jun 2004 14:12:02 -0600 (MDT)

> I can't find the P draft nor the message giving the URL for it,

Yes, IETF silly [non]archival policy makes that difficult. You can get
the latest copy at: http://www.measurement-factory.com/tmp/opes/

I was going to resubmit the draft once we have the new charter. Should
it be done sooner?

> The most important thing about the OPES language is that it support
> a superset of regular expression matching functions that can be
> composed efficiently by a special compiler.

I agree that regular expression matching would be nice, Fortunately,
REs should not affect the language specification much. That is,
language syntax and semantics are not going to be significantly
affected by support for regular expressions, unless we try really
hard. RE support should have no effect on the WG charter.

IIRC, somebody has objected to full RE support in P so the latest
draft only contains three basic primitives (contains, begins_with, and
ends_with). We should revisit RE issue once the WG charter is done.

> The second most important thing is that it support composable,
> reusable actions.

I agree. On one extreme, we have IRML-like approach where there is
virtually no room for human-friendly code composition and reuse. On
the other extreme, we have a programming language like Java or Perl
where the possibilities are endless. Since P implementations need to
be reasonably compact and secure, we most likely cannot afford
something like Java or Perl. If we can, we probably should not invent
a new language but just use Java or something!

> I can't tell where these fall in the Rousskov a, b, c scope
> definitions, and I would appreciate further amplication of them,
> especially c,

I have posted some examples for (b) in response to Markus' email. They
should illustrate the difference between (1a) and (1b) better. There
were also a couple of threads about this last year if you want to go
really deep. REs would be supported at (1a) composition and reuse at
(1b). And please see below for more.

> for writing modules for protocol-specific processor actions.  I
> cannot quite imagine how a language would support
> protocol-nonspecific actions but could not support protocol-specific
> actions.

Note that level (c) is about writing modules, not actions. Actions
would use those modules.

Let me try to explain with a longer example. Suppose we define a
language that lets us write:

	if (http.request.uri contains "xxx")
		pornfilter.apply(request);

	if (http.request.uri contains "yyy")
		pornfilter.apply(request);

This is level (a) in my original scope classification. That's what P
already supports, more or less. If you get to level (b), you can have
something like:

	action filterPorn(string badWord) {
	  if (http.request.usesChunkedEncoding())
	    return; // XXX: our filter cannot handle chunked requests
	  if (http.request.uri contains badWord)
	    pornfilter.apply(http.request);
	}
	...
	filterPorn("xxx");
	filterPorn("yyy");

The actual body of filterPorn() function would be more complex, of
course, justifying the encapsulation. The above are just simple
examples. Current P specification has very limited support for the
above, but some have argued that more support should be added.

If P supports level (c), you would be able to write something like
this (in P):

	module HTTP;
	use MIME;
	use URI;

	exports request, response;

	class request extends MIME.message with {
		...
		URI uri;
		...
		boolean usesChunkedEncoding() { ... }
		boolean expects100Continue() { ... }
		...
	};
	...

As you can see the HTTP module written in P would have to deal with
low-level, usually protocol-specific manipulations of messages. It
will not, most likely, invoke any actions. However, actions will use
imported-from-module objects, functions, methods to do the adaptation.

In the extreme, one can probably even write a full-features adaptation
server (e.g., a virus scanner) in P if P supports level (c)
complexity!

Once again, I am skeptical that we should support level (c), at least
not in the first revisions of P.

Does this clarify?

Thanks,

Alex.

---

From: Markus Hofmann <[email-masked]>
Date: Tue, 29 Jun 2004 22:26:12 -0400

Alex Rousskov wrote:

> [...]

Thanks for the example, that helped. But I still look at (a) and (b) 
as two separate things. And having two simple languages for each of 
those things seems to be preferable over having a more complex 
language that tries to tie these things together.

I would still lean towards limiting the scope of the charter to (a). 
This would allow us to define a simple and efficient solution for the 
important problem of how to invoke services.

-Markus

---

From: Markus Hofmann <[email-masked]>
Date: Tue, 29 Jun 2004 22:35:55 -0400

Folks,

this is getting far to complex for my taste and beyond the original 
scope. It moves functionality I would expect from a service itself 
into the rules language!

Rather than building all these fine-grained capabilities into the 
rules language itself, I would prefer a very simple rules langueage 
that allows me to invoke services - all the logic and more 
fine-grained rules are part of the service.

The whole reason we want to have callout servers is to move services 
out to a separate server. Are we now trying to move some of the 
processing complexity back into the OPES processor in form of a highly 
programmable, compelx rules language that allows "programming" of actions?

-Markus

---

From: Alex Rousskov <[email-masked]>
Date: Wed, 30 Jun 2004 00:13:00 -0600 (MDT)

On Tue, 29 Jun 2004, Markus Hofmann wrote:

> It moves functionality I would expect from a service itself into the
> rules language!

I believe I understand your concerns.

The outcome of this discussion will have a paramount effect on initial
P specs and, with some luck, on P success. So let's proceed with care.

We are trying to find the right scope for P. We all agree that P is
used for selecting services based on messages. We all have an
understanding of what a typical service does to a message (block,
filter, or modify). We all realize that a lot of customizations (i.e.,
using logic and data specific to a local service invocation
environment and to the message itself) may be required to adapt the
message correctly; some of those customizations might be quite
complex. We may disagree on where some of those customizations should
be taking place and how they should expressed.

> processing complexity back into the OPES processor in form of a
> highly programmable, compelx rules language that allows
> "programming" of actions?

I believe you highlighted the core of our problems and also hinted at
the solution!

Here is a key question: Why do we have OPES service as a distinct
entity? Why do we not simply talk about OPES processors that somehow
adapt messages? Calling a piece of code a "service" does not make it
more efficient. Placing a piece of code on a separate box is not
always a good idea (or we would not have software libraries and
integrated appliances).

I believe the primary reason we logically isolate services from
processors is that we expect and want many services to be standardized
and commoditized (based on their functionality, separately from OPES
processors). Folks will be able to plug in the "best" virus filtering
service, the "best" translation service, the "best" mobile rendering
service, etc., all selected among many standardized offerings.

If services are not easily pluggable/interchangeable, if there are
only a few vendors building a few services, then we are wasting our
time here. IETF WG should not be concerned with technologies that
support just a few vendor products.

So what? Well, if our operating environment is a large set of
standardized and commoditized services, then those services cannot be
efficiently customized by service _manufacturers_!

For example, most customers today cannot buy a customized version of a
disk drive or motherboard for their PC. They can select from a variety
of models that will all comply with a few standards for size, voltage,
and functionality. Manufacturers of drives and boards cannot know
exact user needs and have to ship with reasonable defaults.

Now, if customizations are essential, but OPES services cannot be
customized by their manufacturers, and the number of manufacturers is
large, there are two sane customization options for OPES
administrators:

  - customize OPES processor using a common rule language:
    if (message meets some condition) then
        apply(some service to message)

  - customize OPES service using a common service-customization language:
    if (message meets some condition) then
        apply(some service function to message)

Note that an OPES administrator will end up writing pretty much the
same kind of rules in either case. That is, regardless of what is
customized (processor or service), the same rule language is needed!

In summary, we cannot assume that commoditized services will be
customized by their manufactures and, hence, we cannot "offload"
customization complexities from P to manufacture-specific
languages/solutions. Same for OPES processors. We have to support
those customizations in P, which may be used to customize OPES
processors and/or services.

Do you agree with the above logic?

If yes, here is the next logical step. Given a set of commoditized
OPES processors, commoditized OPES services, and a single rule
language to configure/customize them, what should the minimal scope of
that language be? I believe the answer is that "the language should be
good enough for expressing all common OPES customizations" simply
because that language is the _only_ customization interface an OPES
administrator will have.

For our work to make sense in IETF setting, we must accommodate common
customizations _natively_ in P. We must not rely on processors or
services to provide OPES administrators with non-standard ways of
doing some part of that customization. Commoditized processors and
services will not have non-standard interfaces, and an IETF WG should
not care about markets where there are just a few vendors and those
vendors are using a few proprietary interfaces.

Is there a flaw in the above logic?

Please note that the above does not give us the answer for, say,
whether P should support user-defined functions or full regular
expressions. However, it may give us a framework to derive those
answers:

	- If user-defined functions are essential for
	  matching messages with services, then P must have them

	- If full regular expressions are essential for
	  matching messages with services, then P must have them

We just need to debate whether X is essential for a good matching
interface. If it is essential, it must be supported natively in P.

In the absence of such a framework, we now have to debate two things:
	- whether X is essential for a good matching interface
	- whether X has to be supported natively in P or as
	  a part of some custom service or processor knob

Sorry for such a long message. I could not find a shorter way to
express this idea. Hope it makes sense.

Please comment.

Thanks,

Alex.

---

From: Geetha Manjunath <[email-masked]>
Date: Wed, 30 Jun 2004 18:51:19 +0530

Hi,
I think Alex has put the scoping of 'P' discussion in a nice way... I mostly
agree with his agruments..

In terms of  what 'P' would enable:
* Rule Specification : The filtering services themselves would be provided by
vendors (say, virus scanners) while the configuring conditions (rules) will
be  described/specified using the 'P' rule language.
* Writing simple filters/actions: While modifying a message to be able to
call several commoditized filters may be one of the examples (Alex), I would
see the need for writing actions more when the actions themselves are very
simple - may be to just put some additional authentication controls onto
services, where the OPES processor would just return a 40x response. It could
also be used to compose output of multiple filters (I remember a discussion
we had on this mailing list about how pipelining of filters should function
and so on. This now leaves the decision to the rule writer). There will be
many more use cases I am sure.
* Writing modules in 'P':  Since a set of rules themselves may need to be
reused many a times, we may want to create customized reusable modules in 'P'
.

While the above are under the category of  "#1. How deep does P reach? (P
scope)", I want to just add on additional option to "#2. How deep does our
charter reach? (WG work scope)" or an extension to 2c.
(d) defining mechanisms by which a user can communicate rulesets to the OPES
processor.

Comments Welcome.
(sorry for joining in late on this thread - I was on a business trip could
not see my emails earlier )

Thanks and regards
Geetha

---

From: Alex Rousskov <[email-masked]>
Date: Wed, 30 Jun 2004 11:12:40 -0600 (MDT)

Geetha,

	I think there are two very different panes of
discussion/scope, and I failed to identify them clearly in my original
classification.

1) One pane is whether P can be used to write user-defined
P functions and user-defined collections of P functions.

Markus concerns are that user-defined functions add too much
complexity. Markus suggests that such complexity can be implemented in
services using some service-specific interfaces. My longish response
tried to argue that this service-specific loophole is not feasible
because services will be commoditized and will need to express the
same kind of selection rules using some standard language. Since what
we need to express would be the same, we should use one language for
that, regardless of whether the future will place complex rules on
processors, services, or both.

2) The other pane is whether P can be used to implement services
themselves.

I think the answer is that "P can orchestrate adaptations, but cannot
perform adaptations natively". In other words, all message adaptations
are performed outside of P. For example, the following code should not
cause any problems:

	if (message meets some criteria) then {
	    services.adaptation1(message); // apply adaptation one
	    services.adaptation2(message); // then adaptation two
	    services.adaptation3(message); // then adaptation three
	}

The following is still acceptable if HTTP module supports header
updating operations (which is something we have to decide):

	if (http.message meets some criteria) then {
	    http.message.headers.delete("Content-Length");
	}

But implementing http.message.headers.delete() method natively in P
will not be possible because of P's single-assignment semantics, lack
of loops, data structures, etc.

Alex.

---

From: Markus Hofmann <[email-masked]>
Date: Wed, 30 Jun 2004 14:43:32 -0400

Alex Rousskov wrote:

> filter, or modify). We all realize that a lot of customizations
> (i.e., using logic and data specific to a local service invocation 
>  message correctly;

There is "customaziation" with respect to service invocation and
"customaziation" of the services themselves. In my opinion, the former
one is in scope for "P", the latter not. Customaziation of the actual
action is part of the service, and not of the rules.

> processors is that we expect and want many services to be
> standardized and commoditized (based on their functionality,
> separately from OPES processors). Folks will be able to plug in the
> "best" virus filtering service, the "best" translation service, the
> "best" mobile rendering service, etc., all selected among many
> standardized offerings.

The scope of OPES is to enable such separation "over a distance", i.e.
define the protocols and mechanisms that will allow you to have the
services remote from the OPES processor. The WG is not in the business
of specifying a "local runtime environment".

> Do you agree with the above logic?

Why does customaziation of the services have to happen through "P"? I
don't disagree with the need to customize services, I just see that
separate from specifying when to invoke a service. This allows me to 
keep my rules language simpler, and for someone else to come up with a 
highly efficient "service-customization language".

-Markus

---

From: Markus Hofmann <[email-masked]>
Date: Wed, 30 Jun 2004 14:50:17 -0400

> be  described/specified using the 'P' rule language.

Agreed.

> services, where the OPES processor would just return a 40x response.

These functions are services themselves and should be invoked by the 
rules, rather then trying to "programm" them in the rules.

If a function like "adding of authentication controls" is needed, a 
little service doing this can be written, and a rule can be specified 
to invoke this service.

> reused many a times, we may want to create customized reusable modules in 'P'

Thats sounds worthwhile to consider (but already goes beyond the 
charter discussion perse, I would assume).

> (d) defining mechanisms by which a user can communicate rulesets to the OPES

Such mechanism is needed (one might default to existing ones), but out 
of scope of the WG.

-Markus

---

From: Alex Rousskov <[email-masked]>
Date: Wed, 30 Jun 2004 14:07:24 -0600 (MDT)

On Wed, 30 Jun 2004, Markus Hofmann wrote:

> "customaziation" of the services themselves.

Agreed!

> In my opinion, the former one is in scope for "P", the latter not.
> Customaziation of the actual action is part of the service, and not
> of the rules.

I agree that it would be nice to separate the two, but I am not sure
it is possible.

Can you come up with an example of OPES processor selecting a service
using some simple P rule and then configuring that service using
something other than P? I would like to see if the task of configuring
the service can be somehow kept separate from invoking that service...

Let's call the service-configuration language "S". Suppose we have
service "foo", with three parameters: "a", "b", and "z".

You do not foresee something like the "encapsulated" code below, do
you?

    // P code starts is here
    if (message meets criteria) then {
        service := services.find("foo"); // still P code
        service.configure(<![SCODE[>     // P code ends here
            a = 1;               // S code to be passed to the service
            b = 2;               // S code
            z = message.size;    // S code accessing P objects??
	<]SCODE]>);                      // P code resumes here
	call service(message.body);      // P code
    }

Or perhaps you assume that we can configure services statically,
regardless of the message context?

   // services configuration file written in S
   foo.a = 1;
   foo.b = 2;
   //foo.z = message.size; // cannot be done, no context!

   -----------

   // rules file written in P
   if (message meets criteria) then {
	service := services.find("foo");
	call service(message); // S config loaded; how to set foo.z?
   }

Or is there a better way? Please give an example of how to configure a
dynamically selected service in S without tainting P.

> Why does customaziation of the services have to happen through "P"?
> I don't disagree with the need to customize services, I just see
> that separate from specifying when to invoke a service. This allows
> me to keep my rules language simpler, and for someone else to come
> up with a highly efficient "service-customization language".

The customaziation of the services have to happen in P (natively or
through embedding S code as in the first example above) because P code
is the only place where we know both the service and its invocation
context. We cannot configure the service before we know what the
service is. And even if we have a static list of services, service
configuration may require knowing the invocation context.

Here is another example calling (and configuring) an HTTP-unaware
content scanning service that needs to know the content MIME type:

  if (http.message.mime == "application/exe") then
    call services.scan("Windows binary", http.message.body);
  else
  if (http.message.mime == "text/html") then
    call services.scan("HTML page", http.message.body);

How can the above selection/configuration be done without tainting P?

Thanks,

Alex.

P.S.
> The scope of OPES is to enable such separation "over a distance",
> i.e. define the protocols and mechanisms that will allow you to have
> the services remote from the OPES processor. The WG is not in the
> business of specifying a "local runtime environment".

OPES deals with communication protocols (which could be used between
agents on the same "host", different "hosts", or "distant hosts",
whatever the "host" means). This should not affect our discussion
though.

---

From: Alex Rousskov <[email-masked]>
Date: Wed, 30 Jun 2004 14:13:07 -0600 (MDT)

> little service doing this can be written, and a rule can be specified

I agree.

> > * Writing modules in 'P':  Since a set of rules themselves may
> > need to be reused many a times, we may want to create customized
> > reusable modules in 'P'
>

We would benefit from explicitly including code reuse (user-defined
functions and modules) in P scope, I think. It makes P more complex
and more powerful/useful. If this is in the charter, then we would not
have to argue again whether the added complexity is worth the
benefits.

Please note that user-defined functions are NOT user-defined actions.
I know I was sloppy about this terminology in the past.

> > (d) defining mechanisms by which a user can communicate rulesets
> > to the OPES processor.
>
> Such mechanism is needed (one might default to existing ones), but
> out of scope of the WG.

Agreed, at least for this iteration of the charter.

Alex.

---

From: Markus Hofmann <[email-masked]>
Date: Wed, 30 Jun 2004 17:50:20 -0400

> service "foo", with three parameters: "a", "b", and "z".

What about something like

     // P code starts here
     if (message meets criteria) then {
         service := services.find("foo");
	call service(message.body, 1, 2, message.size);
     }

assuming the service is defined as a function with four input 
parameters, e.g foo(body, a, b, z).

> 	call service(message.body);      // P code

The approach with "encapsulated" code would allow me to program the 
entire service in "P", i.e. using "encapsulated" code to write a virus 
scanner or anything I want, and then just call a service that does 
nothing else then executing the "encapsulated" code fragment - not a 
good idea, IMHO.

> dynamically selected service in S without tainting P.

Maybe we were talking about diferent things... When you talk about 
"customizing a service", do you mean "passing dynamic input parameters 
to a service"? If yes, I think we're aligned - I would assume that 
this is possible (see my example above). If it also includes passing 
code fragments to the service for execution, I start feeling 
uncomfortable.

> How can the above selection/configuration be done without tainting P?

That makes sense to me and seems to be pretty much in line with the 
example I gave above. I would consider this ok - this is just calinbg 
a service with the correct parameters.

-Markus

---

From: Markus Hofmann <[email-masked]>
Date: Wed, 30 Jun 2004 17:57:15 -0400

> have to argue again whether the added complexity is worth the

Not sure whether that kind of detail is needed in a charter - it 
already gives parts of the solution and might actually make it harder 
to get it approved.

-Markus

---

From: Alex Rousskov <[email-masked]>
Date: Wed, 30 Jun 2004 16:30:07 -0600 (MDT)

On Wed, 30 Jun 2004, Markus Hofmann wrote:

> "customizing a service", do you mean "passing dynamic input
> parameters to a service"? If yes, I think we're aligned - I would
> assume that this is possible (see my example above).

Yes, that is what I meant.

> If it also includes passing code fragments to the service for
> execution, I start feeling uncomfortable.

Me too. Glad we are on the same page now.

Alex.

---

From: Alex Rousskov <[email-masked]>
Date: Wed, 30 Jun 2004 16:31:35 -0600 (MDT)

> already gives parts of the solution and might actually make it harder

OK. I have no strong feelings about this. Please include whatever you
think is appropriate, and we will go from there.

Thanks,

Alex.